
// APP_USER will be defined in layout
const APP_USER = window.APP_USER || { role: 'guest', didar_id: null, name: '' };
const APP_DEBUG = window.APP_DEBUG !== false;
const APP_DIDAR_UI_ENABLED = window.APP_DIDAR_UI_ENABLED === true;
const FEATURE_HISTORY_V1_DEFAULT = true;
const FEATURE_MODAL_FRESHNESS_V1_DEFAULT = true;
const FEATURE_HISTORY_STORAGE_KEY = 'feature_spa_history_v1';
const FEATURE_MODAL_FRESHNESS_STORAGE_KEY = 'feature_modal_freshness_v1';

// Reduce noisy debug logs in production while keeping warnings/errors visible.
// This only filters clearly-marked debug messages and common dev-only traces.
(function patchDebugConsoleLogging() {
    if (APP_DEBUG || typeof console === 'undefined' || console.__appDebugPatched) {
        return;
    }
    const originalLog = typeof console.log === 'function' ? console.log.bind(console) : null;
    if (!originalLog) {
        return;
    }
    const suppressedPrefixes = [
        '[DEBUG]',
        '[CONTACT_SWITCH]',
        '[DEAL_SAVE',
        'Tehran Gregorian:',
        'response received',
        'get_didar_person response',
        'get_lead_didar_data response',
        'get_legacy_deals response'
    ];
    console.log = function (...args) {
        const first = args.length > 0 ? args[0] : '';
        if (typeof first === 'string') {
            for (let index = 0; index < suppressedPrefixes.length; index++) {
                if (first.startsWith(suppressedPrefixes[index])) {
                    return;
                }
            }
        }
        return originalLog(...args);
    };
    if (typeof console.debug === 'function') {
        console.debug = function () {};
    }
    console.__appDebugPatched = true;
})();

(function ensureSweetAlertOverlayZIndex() {
    if (typeof document === 'undefined') return;
    if (document.getElementById('app-swal-zindex-fix')) return;
    const style = document.createElement('style');
    style.id = 'app-swal-zindex-fix';
    style.textContent = '.swal2-container{z-index:7000 !important;}';
    document.head.appendChild(style);
})();

// Tehran Timezone Helper Functions
function getTehranNow() {
    // Get current date/time in Tehran timezone
    const now = new Date();
    const formatter = new Intl.DateTimeFormat('en-CA', {
        timeZone: 'Asia/Tehran',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    });
    const parts = formatter.formatToParts(now);
    
    const year = parseInt(parts.find(p => p.type === 'year').value);
    const month = parseInt(parts.find(p => p.type === 'month').value) - 1; // JS months are 0-indexed
    const day = parseInt(parts.find(p => p.type === 'day').value);
    const hour = parseInt(parts.find(p => p.type === 'hour').value);
    const minute = parseInt(parts.find(p => p.type === 'minute').value);
    const second = parseInt(parts.find(p => p.type === 'second').value);
    
    return new Date(year, month, day, hour, minute, second);
}

function getTehranDate() {
    // Get current date in Tehran timezone (without time)
    const now = new Date();
    const formatter = new Intl.DateTimeFormat('en-CA', {
        timeZone: 'Asia/Tehran',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit'
    });
    const parts = formatter.formatToParts(now);
    
    const year = parseInt(parts.find(p => p.type === 'year').value);
    const month = parseInt(parts.find(p => p.type === 'month').value) - 1;
    const day = parseInt(parts.find(p => p.type === 'day').value);
    
    return new Date(year, month, day);
}

function getTehranPersianDate() {
    // Use moment-jalaali for accurate date conversion (fixes leap year bug)
    if (typeof moment !== 'undefined') {
        const testMoment = moment();
        if (typeof testMoment.jYear === 'function') {
            // Get current date in Tehran timezone
            const now = new Date();
            const formatter = new Intl.DateTimeFormat('en-CA', {
                timeZone: 'Asia/Tehran',
                year: 'numeric',
                month: '2-digit',
                day: '2-digit'
            });
            const parts = formatter.formatToParts(now);

            const gYear = parseInt(parts.find(p => p.type === 'year').value);
            const gMonth = parseInt(parts.find(p => p.type === 'month').value);
            const gDay = parseInt(parts.find(p => p.type === 'day').value);

            // Use moment-jalaali to convert gregorian to persian
            const gregorianMoment = moment(`${gYear}-${String(gMonth).padStart(2, '0')}-${String(gDay).padStart(2, '0')}`, 'YYYY-MM-DD');

            // Get Persian date components using moment-jalaali API
            const persianYear = gregorianMoment.jYear();
            const persianMonth = gregorianMoment.jMonth() + 1; // moment-jalaali months are 0-indexed
            const persianDay = gregorianMoment.jDate();

            // Create persianDate object from array format for compatibility with persianDatepicker
            if (typeof persianDate !== 'undefined') {
                const pDate = new persianDate([persianYear, persianMonth, persianDay]);

                // Debug log (only once)
                if (!window._tehranDateLogged) {
                    console.log('Tehran Gregorian:', gYear, gMonth, gDay, '-> Persian (moment-jalaali):', pDate.format('YYYY/MM/DD'));
                    window._tehranDateLogged = true;
                    setTimeout(() => { window._tehranDateLogged = false; }, 1000);
                }

                return pDate;
            }
        }
    }

    // Fallback to persianDate if moment-jalaali not available
    if (typeof persianDate === 'undefined') {
        console.warn('Neither jalali-moment nor persianDate available');
        return null;
    }

    // Get current date in Tehran timezone
    const now = new Date();
    const formatter = new Intl.DateTimeFormat('en-CA', {
        timeZone: 'Asia/Tehran',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit'
    });
    const parts = formatter.formatToParts(now);

    const gYear = parseInt(parts.find(p => p.type === 'year').value);
    const gMonth = parseInt(parts.find(p => p.type === 'month').value);
    const gDay = parseInt(parts.find(p => p.type === 'day').value);

    return new persianDate(new Date(gYear, gMonth - 1, gDay));
}

function parseAppDateValue(dateString) {
    const raw = String(dateString || '').trim();
    if (!raw) return null;

    const dateOnlyMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
    if (dateOnlyMatch) {
        return new Date(
            parseInt(dateOnlyMatch[1], 10),
            parseInt(dateOnlyMatch[2], 10) - 1,
            parseInt(dateOnlyMatch[3], 10)
        );
    }

    const localDateTimeMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})[ ](\d{2}):(\d{2})(?::(\d{2}))?/);
    if (localDateTimeMatch) {
        return new Date(
            parseInt(localDateTimeMatch[1], 10),
            parseInt(localDateTimeMatch[2], 10) - 1,
            parseInt(localDateTimeMatch[3], 10),
            parseInt(localDateTimeMatch[4], 10) || 0,
            parseInt(localDateTimeMatch[5], 10) || 0,
            parseInt(localDateTimeMatch[6], 10) || 0
        );
    }

    return new Date(raw);
}

function toPersianDate(dateString) {
    if (!dateString) return '';
    try {
        // Use moment-jalaali for accurate conversion (fixes leap year bug)
        if (typeof moment !== 'undefined') {
            const testMoment = moment();
            if (typeof testMoment.jYear === 'function') {
                let date = parseAppDateValue(dateString);
                
                const m = moment(date);
                return toEnglishDigits(m.format('jYYYY/jMM/jDD'));
            }
        }

        // Fallback to persianDate
        if (typeof persianDate === 'undefined') {
            console.warn('persianDate library not loaded');
            return dateString.substring(0, 10);
        }

        // Parse date-only and local datetime values as local calendar values.
        let date = parseAppDateValue(dateString);

        const pDate = new persianDate(date);
        return toEnglishDigits(pDate.format('YYYY/MM/DD'));
    } catch(e) {
        console.warn('Error in toPersianDate:', e, dateString);
        return dateString.substring(0, 10);
    }
}

function toPersianDateTime(dateString) {
    if (!dateString) return '';
    try {
        // Use moment-jalaali for accurate conversion (fixes leap year bug)
        if (typeof moment !== 'undefined' && typeof moment.jYear === 'function') {
            let date = parseAppDateValue(dateString);
            
            const m = moment(date);
            return toEnglishDigits(m.format('jYYYY/jMM/jDD HH:mm'));
        }

        // Fallback to persianDate
        if (typeof persianDate === 'undefined') {
            console.warn('Neither jalali-moment nor persianDate available');
            return dateString;
        }

        let date = parseAppDateValue(dateString);

        const pDate = new persianDate(date);
        return toEnglishDigits(pDate.format('YYYY/MM/DD HH:mm'));
    } catch(e) {
        console.warn('Error in toPersianDateTime:', e, dateString);
        return dateString;
    }
}

function persianToGregorian(jalaliDate, time = '00:00') {
    if (!jalaliDate || !jalaliDate.trim()) return null;
    try {
        // Check if persianDate is available
        if (typeof persianDate === 'undefined') {
            console.warn('persianDate library not loaded');
            return null;
        }
        
        const parts = jalaliDate.trim().split('/');
        if (parts.length !== 3) return null;
        const year = parseInt(parts[0]);
        const month = parseInt(parts[1]);
        const day = parseInt(parts[2]);
        
        // Validate date parts
        if (isNaN(year) || isNaN(month) || isNaN(day)) return null;
        if (year < 1300 || year > 1599) return null; // Keep browser bounds aligned with server DateNormalizer.
        if (month < 1 || month > 12) return null;
        if (day < 1 || day > 31) return null;
        
        // Create Persian date object
        let pDate;
        try {
            pDate = new persianDate([year, month, day]);
            
            // Convert to native JavaScript Date object (this handles the conversion automatically)
            const nativeDate = pDate.toDate();
            
            // Validate the date
            if (!nativeDate || isNaN(nativeDate.getTime())) {
                return null;
            }
            
            // Extract time parts
            const timeParts = time.split(':');
            const hours = parseInt(timeParts[0] || 0);
            const minutes = parseInt(timeParts[1] || 0);
            
            // IMPORTANT: Create final date using local timezone
            // The date should be treated as local time, not UTC
            // Format: YYYY-MM-DD HH:mm:ss (for local timezone storage)
            const year4 = nativeDate.getFullYear();
            const month2 = String(nativeDate.getMonth() + 1).padStart(2, '0');
            const day2 = String(nativeDate.getDate()).padStart(2, '0');
            const hour2 = String(hours).padStart(2, '0');
            const min2 = String(minutes).padStart(2, '0');
            
            // Return in local timezone format (not ISO/UTC)
            return `${year4}-${month2}-${day2} ${hour2}:${min2}:00`;
        } catch(e) {
            console.error('Error in persianDate conversion:', e);
            return null;
        }
    } catch(e) {
        console.error('Error converting Persian date:', e, 'Input:', jalaliDate);
        return null;
    }
}

function parseOffset(offsetValue) {
    const match = offsetValue.match(/GMT([+-])(\d{1,2}):?(\d{2})?/);
    if (!match) return null;
    const sign = match[1] === '-' ? -1 : 1;
    const hours = parseInt(match[2], 10) || 0;
    const minutes = parseInt(match[3] || '0', 10);
    return sign * (hours * 60 + minutes);
}

function getTehranNow() {
    const formatter = new Intl.DateTimeFormat('en-US', {
        timeZone: 'Asia/Tehran',
        hour12: false,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        timeZoneName: 'shortOffset'
    });

    const now = new Date();
    const parts = formatter.formatToParts(now);
    const mapped = {};
    let offsetMinutes = null;

    for (const part of parts) {
        if (part.type === 'year' || part.type === 'month' || part.type === 'day' ||
            part.type === 'hour' || part.type === 'minute' || part.type === 'second') {
            mapped[part.type] = parseInt(part.value, 10);
        }
        if (part.type === 'timeZoneName' && offsetMinutes === null) {
            offsetMinutes = parseOffset(part.value);
        }
    }

    const tehranOffset = offsetMinutes !== null ? offsetMinutes : -210;
    const localOffset = now.getTimezoneOffset();
    const diff = tehranOffset - localOffset;
    const tehranTimestamp = now.getTime() + diff * 60000;

    return new Date(tehranTimestamp);
}

// Validation Helper Functions
function validatePersianText(text, fieldName) {
    if (!text || text.trim() === '') return { valid: true }; // Allow empty
    // فقط حروف فارسی، فاصله و اعراب
    const persianRegex = /^[\u0600-\u06FF\s\u200C\u200D]+$/;
    if (!persianRegex.test(text.trim())) {
        return { valid: false, message: `${fieldName} باید فقط شامل حروف فارسی باشد` };
    }
    return { valid: true };
}

function validateNationalId(nationalId) {
    if (!nationalId || nationalId.trim() === '') return { valid: true }; // Allow empty
    const cleaned = nationalId.trim().replace(/\D/g, ''); // فقط اعداد
    if (cleaned.length !== 10) {
        return { valid: false, message: 'کد ملی باید دقیقاً 10 رقم باشد' };
    }
    return { valid: true };
}

function validatePhone(phone, fieldName) {
    if (!phone || phone.trim() === '') return { valid: true }; // Allow empty
    const cleaned = phone.trim().replace(/\D/g, ''); // فقط اعداد
    if (cleaned.length !== 10) {
        return { valid: false, message: `${fieldName} باید دقیقاً 10 رقم باشد (بدون صفر اول)` };
    }
    if (cleaned.startsWith('0')) {
        return { valid: false, message: `${fieldName} نباید با صفر شروع شود` };
    }
    return { valid: true };
}

// تبدیل اعداد فارسی به انگلیسی
function toEnglishDigits(str = '') {
    const map = {
        '۰':'0','۱':'1','۲':'2','۳':'3','۴':'4','۵':'5','۶':'6','۷':'7','۸':'8','۹':'9',
        '٠':'0','١':'1','٢':'2','٣':'3','٤':'4','٥':'5','٦':'6','٧':'7','٨':'8','٩':'9'
    };
    return String(str).replace(/[۰-۹٠-٩]/g, d => map[d] || d);
}

// تبدیل ارقام انگلیسی به فارسی (فقط برای نمایش)
const PERSIAN_DIGIT_GLYPHS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
function toPersianDigits(str = '') {
    return String(str).replace(/[0-9]/g, d => PERSIAN_DIGIT_GLYPHS[d]);
}

/*
 * English-digit display layer.
 *
 * App-wide policy: every NUMBER the user SEES is rendered with English (Latin)
 * digits, even if the source produced Persian/Arabic digits (formatNumber,
 * Jalali dates, data coming from the API, or text the user typed). We convert
 * Persian/Arabic digits to English in-place inside *display* text nodes and watch
 * the DOM so dynamically rendered content is converted too. Inputs/textareas are
 * handled separately (see enforceEnglishDigitsInInputs) so typing shows English.
 * Opt a subtree out with the `data-no-en` attribute.
 */
(function englishDigitDisplayLayer() {
    const PERSIAN_ARABIC_DIGIT = /[۰-۹٠-٩]/;

    // Ancestors whose text must stay byte-exact (editable / executable / opted-out).
    function isInsideSkippedRegion(el) {
        for (let node = el; node && node.nodeType === 1; node = node.parentNode) {
            const tag = node.tagName;
            if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' || tag === 'TEXTAREA' || tag === 'INPUT') {
                return true;
            }
            if (node.isContentEditable) return true;
            if (node.hasAttribute && node.hasAttribute('data-no-en')) return true;
        }
        return false;
    }

    function localizeTextNode(textNode) {
        const val = textNode.nodeValue;
        if (!val || !PERSIAN_ARABIC_DIGIT.test(val)) return;
        if (isInsideSkippedRegion(textNode.parentNode)) return;
        const converted = toEnglishDigits(val);
        // Same length (1:1 glyph swap); replace in place — no span wrapping, no re-layout.
        if (converted !== val) textNode.nodeValue = converted;
    }

    function localizeSubtree(root) {
        if (!root) return;
        if (root.nodeType === 3) { localizeTextNode(root); return; }
        if (root.nodeType !== 1 && root.nodeType !== 11) return; // element or document-fragment only
        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
            acceptNode(n) {
                return n.nodeValue && PERSIAN_ARABIC_DIGIT.test(n.nodeValue)
                    ? NodeFilter.FILTER_ACCEPT
                    : NodeFilter.FILTER_REJECT;
            }
        });
        const pending = [];
        while (walker.nextNode()) pending.push(walker.currentNode);
        pending.forEach(localizeTextNode);
    }

    function start() {
        if (!document.body) return;
        localizeSubtree(document.body); // initial pass over whatever is already rendered
        const observer = new MutationObserver(mutations => {
            for (const m of mutations) {
                if (m.type === 'childList') {
                    m.addedNodes.forEach(localizeSubtree);
                } else if (m.type === 'characterData') {
                    localizeTextNode(m.target);
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true, characterData: true });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', start);
    } else {
        start();
    }
})();

/*
 * Force English digits inside form fields too: if the user types (or paste/IME
 * inserts) Persian/Arabic digits, swap them to English live. The swap is a 1:1
 * glyph replacement so caret position is preserved and downstream parsing (which
 * already expects ASCII digits) keeps working.
 */
(function enforceEnglishDigitsInInputs() {
    const PERSIAN_ARABIC_DIGIT = /[۰-۹٠-٩]/;
    function convert(el) {
        if (!el || (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA')) return;
        const v = el.value;
        if (!v || !PERSIAN_ARABIC_DIGIT.test(v)) return;
        const eng = toEnglishDigits(v);
        if (eng === v) return;
        const start = el.selectionStart;
        const end = el.selectionEnd;
        el.value = eng; // identical length → caret stays valid
        try { if (start !== null) el.setSelectionRange(start, end); } catch (e) { /* number/email inputs */ }
    }
    document.addEventListener('input', e => convert(e.target), true);
    document.addEventListener('focusout', e => convert(e.target), true);
})();

// نرمال‌سازی تاریخ به فرمت YYYY/MM/DD با ارقام انگلیسی
function normalizeDate(val = '') {
    const clean = toEnglishDigits(val).trim().replace(/-/g, '/');
    if (!clean) return '';
    if (/^\d{4}\/\d{1,2}\/\d{1,2}$/.test(clean)) return clean;
    return '';
}

// Money formatting helpers (###,### while typing)
function parseMoneyValue(val) {
    const s = toEnglishDigits(String(val ?? '')).replace(/,/g, '').trim();
    if (!s) return 0;
    const cleaned = s.replace(/[^\d.]/g, '');
    const n = parseFloat(cleaned);
    return Number.isFinite(n) ? n : 0;
}

function formatMoneyString(val) {
    const s = toEnglishDigits(String(val ?? '')).replace(/,/g, '');
    const cleaned = s.replace(/[^\d]/g, '').replace(/^0+/, '');
    if (!cleaned) return '';
    return cleaned.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

function formatMoneyInput(el) {
    if (!el) return;
    el.setAttribute('dir', 'ltr');
    el.style.textAlign = 'right';
    const raw = String(el.value ?? '');
    const start = typeof el.selectionStart === 'number' ? el.selectionStart : raw.length;
    const digitsLeft = toEnglishDigits(raw.slice(0, start)).replace(/[^\d]/g, '').length;
    const formatted = formatMoneyString(raw);
    el.value = formatted;
    if (typeof el.setSelectionRange === 'function') {
        let pos = 0;
        let digitCount = 0;
        while (pos < formatted.length && digitCount < digitsLeft) {
            if (/\d/.test(formatted[pos])) digitCount++;
            pos++;
        }
        el.setSelectionRange(pos, pos);
    }
}

const app = {
	    state: { 
	        user: APP_USER,
        leads: [],
        leadsPagination: {
            currentPage: 1,
            perPage: 50,
            totalCount: 0,
            totalPages: 0
        }, 
        deals: [],
        activityTypes: [],
        didarUsers: [],
        referrals: [],
        currentLead: null,
        currentDeals: [],
        dealHasActivity: false,
        originalDealTitleOptions: null,
        crmOwnerId: null,
        crmOwnerName: '',
        lookups: {},
        lookupsLoaded: false,
        lookupGroups: [],
        lookupItems: [],
        lookupSelectedGroup: '',
        lookupProductsManagerRows: [],
        didarDeals: [], // Cache for External CRM deals to avoid API inconsistency
        products: [],
        kpiUsers: [],
        kpiProducts: [],
        kpiFilters: {
            date_from: '',
            date_to: '',
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: [],
            product_ids: [],
            deal_statuses: [],
            deal_lead_types: []
        },
        performanceFilters: {
            date_from: '',
            date_to: '',
            owner_didar_ids: [],
            product_ids: []
        },
        performanceDashboard: null,
        performanceActiveTab: 'activity',
        performanceCharts: {},
        performanceChartSource: {},
        performanceRenderedRows: {},
        performanceDragEnabled: {
            sales: false,
            activity: false
        },
        performanceChartWindow: {
            salesSize: 14,
            salesOffset: 0,
            activitySize: 14,
            activityOffset: 0
        },
        kpiAdvancedTables: {},
        kpiAdvancedUi: {},
        kpiAdvancedRenderedRows: {},
        kpiAdvancedVisibleTables: {},
        kpiDailyOperationalTables: {},
        kpiDailyOperationalUi: {},
        kpiDrilldown: {
            tableId: '',
            metricKey: '',
            row: null,
            data: null,
            loading: false
        },
        kpiDrilldownExport: {
            datasets: {},
            selectedDataset: ''
        },
        kpiPickerOpenKey: null,
        showKpiDealDetails: false,
        activityStagePrev: '',
        saleStatusMap: {
            success: { title: 'موفق', color: 'border-green-500', bg: 'bg-green-600', icon: '✅' },
            failed: { title: 'ناموفق', color: 'border-red-500', bg: 'bg-red-600', icon: '❌' },
            unknown: { title: 'نامشخص', color: 'border-gray-500', bg: 'bg-gray-600', icon: '➖' }
        },
        pipelineStages: [
            'بلاتکلیف',
            'معامله جدید',
            'تماس عدم پاسخ',
            'تماس پیگیری',
            'ارسال لینک پرداخت',
            'پرداخت',
            'پیگیری مانده حساب',
            'برگزاری دوره',
            'پشتیبانی',
            'ارجاع به تیم فروش'
        ],
	        paymentsLocked: false,
        dealInfoLocked: false,
        dealInfoAdminUnlocked: false,
	        lastPaymentsCache: [],
        editingPaymentIndex: null,
        savingPaymentHistoryMutation: false,
	        exportCatalog: null,
	        exportCurrentDatasetId: null
	    },
    featureFlags: {
        historyV1: false,
        modalFreshnessV1: true
    },
    _telemetryBound: false,
    _requestSeq: 0,
    _contextTokenSeq: 0,
    _inFlightControllers: {},
    _historyBound: false,
    _historyUiBound: false,
    _isApplyingHistoryState: false,
    _lastHistoryState: null,
    _historyUiSyncTimer: null,
    _scrollSyncTimer: null,
    _dirtyTrackingBound: false,
    _beforeUnloadHandler: null,
    _beforeUnloadBound: false,
    dirtyForms: {},
    _activeContextTokens: {
        profile: null,
        dealModal: null
    },

    initFeatureFlags() {
        const getStoredBool = (key) => {
            try {
                const raw = localStorage.getItem(key);
                if (raw === 'true') return true;
                if (raw === 'false') return false;
            } catch (_) {}
            return null;
        };
        const storedHistoryFlag = getStoredBool(FEATURE_HISTORY_STORAGE_KEY);
        const storedFreshnessFlag = getStoredBool(FEATURE_MODAL_FRESHNESS_STORAGE_KEY);
        const historyFlag = (storedHistoryFlag !== null)
            ? storedHistoryFlag
            : ((typeof window.FEATURE_SPA_HISTORY_V1 !== 'undefined')
                ? window.FEATURE_SPA_HISTORY_V1 === true
                : FEATURE_HISTORY_V1_DEFAULT);
        const freshnessFlag = (storedFreshnessFlag !== null)
            ? storedFreshnessFlag
            : ((typeof window.FEATURE_MODAL_FRESHNESS_V1 !== 'undefined')
                ? window.FEATURE_MODAL_FRESHNESS_V1 !== false
                : FEATURE_MODAL_FRESHNESS_V1_DEFAULT);
        this.featureFlags.historyV1 = historyFlag;
        this.featureFlags.modalFreshnessV1 = freshnessFlag;

        window.FEATURE_SPA_HISTORY_V1 = this.featureFlags.historyV1;
        window.FEATURE_MODAL_FRESHNESS_V1 = this.featureFlags.modalFreshnessV1;
        this.logTelemetry('flags_init', {
            history_v1: this.featureFlags.historyV1,
            modal_freshness_v1: this.featureFlags.modalFreshnessV1
        });
    },

    persistFeatureFlags() {
        try {
            localStorage.setItem(FEATURE_HISTORY_STORAGE_KEY, this.featureFlags.historyV1 ? 'true' : 'false');
            localStorage.setItem(FEATURE_MODAL_FRESHNESS_STORAGE_KEY, this.featureFlags.modalFreshnessV1 ? 'true' : 'false');
            return { ok: true };
        } catch (error) {
            return { ok: false, error };
        }
    },

    logTelemetry(event, payload = {}, level = 'info') {
        const entry = {
            event,
            level,
            ts: new Date().toISOString(),
            ...payload
        };
        if (APP_DEBUG || level !== 'debug') {
            console.log('[APP_TELEMETRY]', entry);
        }
        if (typeof Logger !== 'undefined' && typeof Logger.log === 'function') {
            try {
                Logger.log('AppTelemetry', entry, 'app_telemetry', null);
            } catch (_) {}
        }
    },

    bindGlobalTelemetryHandlers() {
        if (this._telemetryBound || typeof window === 'undefined') return;
        this._telemetryBound = true;

        window.addEventListener('error', (event) => {
            this.logTelemetry('window_error', {
                message: event?.message || '',
                filename: event?.filename || '',
                lineno: event?.lineno || 0,
                colno: event?.colno || 0
            }, 'error');
        });

        window.addEventListener('unhandledrejection', (event) => {
            const reason = event?.reason;
            this.logTelemetry('window_unhandled_rejection', {
                message: reason?.message || String(reason || '')
            }, 'error');
        });
    },

    createContextToken(scope, meta = {}) {
        this._contextTokenSeq += 1;
        const token = `${scope}:${Date.now()}:${this._contextTokenSeq}`;
        this._activeContextTokens[scope] = token;
        this.logTelemetry('context_token_created', { scope, token, ...meta }, 'debug');
        return token;
    },

    isContextTokenActive(scope, token) {
        return this._activeContextTokens[scope] === token;
    },

    dropIfStaleContext(scope, token, meta = {}) {
        if (!this.featureFlags.modalFreshnessV1) return false;
        const stale = !this.isContextTokenActive(scope, token);
        if (stale) {
            this.logTelemetry('stale_context_dropped', { scope, token, ...meta }, 'debug');
        }
        return stale;
    },

    composeAbortSignal(signals = []) {
        const controller = new AbortController();
        const list = (signals || []).filter(Boolean);
        const abort = (reason) => {
            if (!controller.signal.aborted) controller.abort(reason);
        };
        list.forEach((sig) => {
            if (sig.aborted) {
                abort(sig.reason);
                return;
            }
            sig.addEventListener('abort', () => abort(sig.reason), { once: true });
        });
        return controller.signal;
    },

    cancelInFlightRequest(requestKey) {
        if (!requestKey) return;
        const existing = this._inFlightControllers[requestKey];
        if (existing && !existing.signal.aborted) {
            existing.abort();
            this.logTelemetry('request_aborted_previous', { requestKey }, 'debug');
        }
    },

    getViewCapabilityMap() {
        return {
            'dashboard': 'menu.dashboard',
            'performance-dashboard': 'menu.performance_dashboard',
            'kpi-dashboard': 'menu.kpi_dashboard',
            'leads': 'menu.leads',
            'operation-manager': 'menu.operation_manager',
            'blataklif-leads': 'menu.blataklif_leads',
            'deals': 'menu.deals',
            'legacy-deals': 'menu.legacy_deals',
            'referrals': 'menu.referrals',
            'persons': 'menu.persons',
            'reminders': 'menu.reminders',
            'settings': 'menu.settings',
            'lookups': 'menu.lookups',
            'exports': 'menu.exports',
            'cheques': 'menu.cheques',
            'audit-logs': 'menu.audit_logs',
            'users': 'menu.users',
            'maintenance': 'menu.maintenance',
            'commissions': 'menu.commissions',
            'finance-inbox': 'menu.commissions',
            'commission-calc': 'menu.commissions',
            'customer-access': 'menu.customer_access'
        };
    },

    getKnownViews() {
        return [...Object.keys(this.getViewCapabilityMap()), 'phone-logs'];
    },

    normalizeView(view) {
        const fallback = 'dashboard';
        let normalized = String(view || '').trim();
        normalized = normalized.replace(/^#\/?/, '');
        normalized = normalized.replace(/^view-/, '');
        if (normalized === 'commission') {
            normalized = 'commissions';
        }
        if (normalized === 'kpi') {
            normalized = 'kpi-dashboard';
        }
        if (normalized === 'finance') {
            normalized = 'finance-inbox';
        }
        return this.getKnownViews().includes(normalized) ? normalized : fallback;
    },

    canAccessView(view, options = {}) {
        const opts = options || {};
        const map = this.getViewCapabilityMap();
        const requiredCap = map[view];
        if (!requiredCap) return true;
        const allowed = this.canCurrentUserCapabilityClient(requiredCap);
        if (!allowed && opts.showWarning) {
            Swal.fire('عدم دسترسی', 'دسترسی شما به این بخش مجاز نیست', 'warning');
        }
        return allowed;
    },
    getFirstAccessibleView(fallback = 'dashboard') {
        const preferred = this.normalizeView(fallback);
        if (this.canAccessView(preferred)) return preferred;
        const map = this.getViewCapabilityMap();
        for (const view of Object.keys(map)) {
            if (this.canAccessView(view)) return view;
        }
        return '';
    },

    getCurrentView() {
        const active = document.querySelector('.sidebar-item.active');
        if (active && active.id) return active.id.replace('menu-', '');
        const visible = Array.from(document.querySelectorAll('.view-section'))
            .find(el => !el.classList.contains('hidden') && String(el.id || '').startsWith('view-'));
        if (visible) return String(visible.id).replace('view-', '');
        return 'dashboard';
    },

    getOpenModalId() {
        const openModal = document.querySelector('[id^="modal"]:not(.hidden)');
        return openModal ? String(openModal.id || '') : '';
    },

    buildHistoryHash(view) {
        return `#/${this.normalizeView(view)}`;
    },

    parseViewFromHash() {
        const hash = String(window.location.hash || '').trim();
        const match = hash.match(/^#\/([a-zA-Z0-9\-_]+)$/);
        if (!match) return '';
        return this.normalizeView(match[1]);
    },

    collectHistoryUiState(view) {
        const ui = { scrollY: Number(window.scrollY || 0) };
        const readFilters = (ids) => {
            const out = {};
            (ids || []).forEach((id) => {
                const el = document.getElementById(id);
                if (el) out[id] = el.value ?? '';
            });
            return out;
        };

        if (view === 'leads') {
            ui.filters = readFilters([
                'leads-search', 'leads-customer-code-search', 'leads-stage-filter', 'leads-owner-filter', 'leads-product-filter',
                'leads-customer-level-filter', 'leads-register-from', 'leads-register-to',
                'leads-activity-from', 'leads-activity-to'
            ]);
            ui.pagination = { currentPage: Number(this.state.leadsPagination?.currentPage || 1) };
        } else if (view === 'deals') {
            ui.filters = readFilters([
                'deals-search', 'deals-customer-code-search', 'deals-pipeline-filter', 'deals-status-filter', 'deals-owner-filter',
                'deals-register-from', 'deals-register-to', 'deals-activity-from', 'deals-activity-to'
            ]);
        } else if (view === 'lookups') {
            ui.lookupSelectedGroup = this.state.lookupSelectedGroup || '';
        }
        return ui;
    },

    applyHistoryUiState(view, ui = {}) {
        const data = ui || {};
        const applyFilters = (filters) => {
            Object.entries(filters || {}).forEach(([id, value]) => {
                const el = document.getElementById(id);
                if (!el) return;
                el.value = value ?? '';
            });
        };

        if (view === 'leads') {
            applyFilters(data.filters || {});
            if (data.pagination?.currentPage && this.state.leadsPagination) {
                this.state.leadsPagination.currentPage = Number(data.pagination.currentPage) || 1;
            }
        } else if (view === 'deals') {
            applyFilters(data.filters || {});
        } else if (view === 'lookups') {
            if (data.lookupSelectedGroup) this.state.lookupSelectedGroup = String(data.lookupSelectedGroup);
        }
    },

    buildHistoryState(params = {}) {
        const p = params || {};
        return {
            v: 1,
            ts: Date.now(),
            view: this.normalizeView(p.view || this.getCurrentView()),
            modal: p.modal || null,
            ui: p.ui || this.collectHistoryUiState(this.normalizeView(p.view || this.getCurrentView()))
        };
    },

    isValidHistoryState(state) {
        if (!state || typeof state !== 'object') return false;
        if (Number(state.v || 0) !== 1) return false;
        if (!this.getKnownViews().includes(String(state.view || ''))) return false;
        if (state.modal && typeof state.modal !== 'object') return false;
        return true;
    },

    syncHistoryState(state, options = {}) {
        if (!this.featureFlags.historyV1 || typeof history === 'undefined') return;
        const opts = options || {};
        const safe = this.isValidHistoryState(state) ? state : this.buildHistoryState(state || {});
        const replace = opts.replace === true;
        const hash = this.buildHistoryHash(safe.view);
        if (replace) history.replaceState(safe, '', hash);
        else history.pushState(safe, '', hash);
        this._lastHistoryState = safe;
        this.logTelemetry('history_state_sync', {
            replace,
            source: String(opts.source || ''),
            view: safe.view,
            modal: safe.modal?.id || ''
        }, 'debug');
    },

    replaceCurrentHistoryUiState(source = 'ui_sync') {
        if (!this.featureFlags.historyV1) return;
        if (this._isApplyingHistoryState) return;
        const view = this.normalizeView(this.getCurrentView());
        const current = this.isValidHistoryState(history.state)
            ? history.state
            : this.buildHistoryState({ view, modal: null });
        const next = this.buildHistoryState({
            view,
            modal: current.modal || null,
            ui: this.collectHistoryUiState(view)
        });
        this.syncHistoryState(next, { replace: true, source });
    },

    getDirtyFormConfig() {
        return [
            { key: 'lead_form', selector: '#lead-form' },
            { key: 'deal_form', selector: '#deal-form' },
            { key: 'activity_form', selector: '#modalActivity form' },
            { key: 'support_form', selector: '#modalSupport form' },
            { key: 'satisfaction_form', selector: '#modalSatisfaction form' }
        ];
    },

    bindDirtyFormTracking() {
        if (this._dirtyTrackingBound) return;
        this._dirtyTrackingBound = true;
        this._beforeUnloadHandler = (event) => {
            if (!this.isAnyFormDirty()) return;
            event.preventDefault();
            event.returnValue = '';
        };
        this.getDirtyFormConfig().forEach((cfg) => {
            this.dirtyForms[cfg.key] = false;
            const form = document.querySelector(cfg.selector);
            if (!form) return;
            const markDirty = () => this.setFormDirty(cfg.key, true);
            form.addEventListener('input', markDirty, true);
            form.addEventListener('change', markDirty, true);
        });
    },

    setFormDirty(key, dirty = true) {
        this.dirtyForms[String(key)] = !!dirty;
        this.syncBeforeUnloadGuard();
    },

    clearDirtyForm(key) {
        this.setFormDirty(key, false);
    },

    clearDirtyFormsByModal(id) {
        const map = {
            modalLead: 'lead_form',
            modalDeal: 'deal_form',
            modalActivity: 'activity_form',
            modalSupport: 'support_form',
            modalSatisfaction: 'satisfaction_form'
        };
        const key = map[String(id || '')];
        if (key) this.clearDirtyForm(key);
    },

    getDirtyKeyByModal(id) {
        const map = {
            modalLead: 'lead_form',
            modalDeal: 'deal_form',
            modalActivity: 'activity_form',
            modalSupport: 'support_form',
            modalSatisfaction: 'satisfaction_form'
        };
        return map[String(id || '')] || '';
    },

    isAnyFormDirty(keys = null) {
        if (Array.isArray(keys) && keys.length) {
            return keys.some((k) => !!this.dirtyForms[String(k)]);
        }
        return Object.values(this.dirtyForms || {}).some(Boolean);
    },

    syncBeforeUnloadGuard() {
        if (!this._beforeUnloadHandler) return;
        const shouldBind = this.isAnyFormDirty();
        if (shouldBind && !this._beforeUnloadBound) {
            window.addEventListener('beforeunload', this._beforeUnloadHandler);
            this._beforeUnloadBound = true;
        } else if (!shouldBind && this._beforeUnloadBound) {
            window.removeEventListener('beforeunload', this._beforeUnloadHandler);
            this._beforeUnloadBound = false;
        }
    },

    async confirmDiscardChanges(context = 'navigation', keys = null) {
        if (!this.isAnyFormDirty(keys)) return true;
        if (typeof Swal === 'undefined') {
            return window.confirm('تغییرات ذخیره‌نشده دارید. ادامه می‌دهید؟');
        }
        const res = await Swal.fire({
            title: 'تغییرات ذخیره‌نشده',
            text: 'در این بخش تغییرات ذخیره‌نشده دارید. آیا مایل به ادامه هستید؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'ادامه',
            cancelButtonText: 'انصراف'
        });
        this.logTelemetry('dirty_confirm', { context, confirmed: !!res.isConfirmed }, 'debug');
        return !!res.isConfirmed;
    },

    async navigateToView(view, options = {}) {
        const opts = options || {};
        let target = this.normalizeView(view);
        const currentBefore = this.normalizeView(this.getCurrentView());
        if (!this.canAccessView(target, { showWarning: !opts.silentDenied })) {
            target = this.getFirstAccessibleView('dashboard');
            if (!target) return false;
            if (!this.canAccessView(target, { showWarning: !opts.silentDenied })) {
                return false;
            }
        }

        if (!opts.skipUnsavedCheck && !opts.fromPop) {
            const ok = await this.confirmDiscardChanges('navigate_view');
            if (!ok) return false;
        }

        this.cleanupPersianDatepickers(document, { removeOrphans: true });
        if (opts.ui) this.applyHistoryUiState(target, opts.ui);
        const rendered = this.setView(target, { source: opts.source || 'navigate', silentDenied: true, skipInitialLoad: opts.skipInitialLoad === true });
        if (!rendered) return false;

        if (this.featureFlags.historyV1 && !opts.skipHistory && !opts.fromPop && !this._isApplyingHistoryState) {
            const isViewChange = target !== currentBefore;
            const state = this.buildHistoryState({
                view: target,
                modal: null,
                ui: this.collectHistoryUiState(target)
            });
            this.syncHistoryState(state, {
                replace: opts.replace === true || !isViewChange,
                source: opts.source || (isViewChange ? 'navigate_push' : 'navigate_replace')
            });
        }
        return true;
    },

    async applyHistoryState(state, options = {}) {
        const opts = options || {};
        const safeState = this.isValidHistoryState(state) ? state : this.buildHistoryState({ view: 'dashboard', modal: null });
        if (this._isApplyingHistoryState) return;
        this._isApplyingHistoryState = true;
        try {
            const targetModalId = safeState.modal?.id || '';
            const currentModalId = this.getOpenModalId();

            if (currentModalId && currentModalId !== targetModalId) {
                this.closeModal(currentModalId, { source: 'history', skipHistory: true });
            }
            if (targetModalId && targetModalId !== currentModalId) {
                const entityId = String(safeState.modal?.entityId || '');
                if (targetModalId === 'modalProfile' && entityId) {
                    await this.openPersonProfile(entityId);
                } else if (targetModalId === 'modalDeal' && entityId) {
                    await this.openDealModal(entityId);
                } else {
                    this.openModal(targetModalId, {
                        source: 'history',
                        skipHistory: true,
                        entityId
                    });
                }
            }

            const currentView = this.normalizeView(this.getCurrentView());
            const targetView = this.normalizeView(safeState.view);
            if (targetView !== currentView) {
                await this.navigateToView(targetView, {
                    replace: true,
                    source: opts.source || 'history',
                    fromPop: true,
                    skipHistory: true,
                    skipUnsavedCheck: true,
                    silentDenied: true,
                    ui: safeState.ui || {}
                });
            } else if (safeState.ui) {
                this.applyHistoryUiState(targetView, safeState.ui);
            }

            if (safeState.ui && Number.isFinite(Number(safeState.ui.scrollY))) {
                const y = Number(safeState.ui.scrollY) || 0;
                setTimeout(() => window.scrollTo({ top: y, behavior: 'auto' }), 0);
            }
            this._lastHistoryState = safeState;
        } finally {
            this._isApplyingHistoryState = false;
        }
    },

    bindHistoryUiSync() {
        if (this._historyUiBound || !this.featureFlags.historyV1) return;
        this._historyUiBound = true;
        const ids = [
            'leads-search', 'leads-customer-code-search', 'leads-stage-filter', 'leads-owner-filter', 'leads-product-filter',
            'leads-customer-level-filter', 'leads-register-from', 'leads-register-to',
            'leads-activity-from', 'leads-activity-to',
            'deals-search', 'deals-customer-code-search', 'deals-pipeline-filter', 'deals-status-filter', 'deals-owner-filter',
            'deals-register-from', 'deals-register-to', 'deals-activity-from', 'deals-activity-to'
        ];
        ids.forEach((id) => {
            const el = document.getElementById(id);
            if (!el) return;
            const handler = () => {
                clearTimeout(this._historyUiSyncTimer);
                this._historyUiSyncTimer = setTimeout(() => this.replaceCurrentHistoryUiState('filter_change'), 180);
            };
            el.addEventListener('input', handler);
            el.addEventListener('change', handler);
        });

        window.addEventListener('scroll', () => {
            clearTimeout(this._scrollSyncTimer);
            this._scrollSyncTimer = setTimeout(() => this.replaceCurrentHistoryUiState('scroll_sync'), 200);
        }, { passive: true });
    },

    bindHistoryRouter() {
        if (this._historyBound || !this.featureFlags.historyV1) return;
        this._historyBound = true;
        try {
            if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
        } catch (_) {}

        window.addEventListener('popstate', async (event) => {
            if (!this.featureFlags.historyV1) return;
            const target = this.isValidHistoryState(event.state) ? event.state : this.buildHistoryState({ view: 'dashboard', modal: null });
            if (!this.isValidHistoryState(event.state)) {
                this.syncHistoryState(target, { replace: true, source: 'pop_invalid_fallback' });
            }
            const allow = await this.confirmDiscardChanges('history_popstate');
            if (!allow) {
                const restore = this._lastHistoryState || this.buildHistoryState({ view: this.getCurrentView(), modal: null });
                this.syncHistoryState(restore, { replace: true, source: 'pop_cancel_restore' });
                return;
            }
            await this.applyHistoryState(target, { source: 'popstate' });
        });
    },

    async bootstrapRouter() {
        if (!this.featureFlags.historyV1) {
            const initial = this.getFirstAccessibleView('dashboard');
            if (initial) this.setView(initial, { source: 'bootstrap_no_history', silentDenied: true });
            return;
        }
        this.bindHistoryRouter();
        this.bindHistoryUiSync();
        const hashView = this.parseViewFromHash();
        const initial = hashView && this.canAccessView(hashView) ? hashView : this.getFirstAccessibleView('dashboard');
        if (!initial) return;
        await this.navigateToView(initial, {
            replace: true,
            source: 'bootstrap',
            skipUnsavedCheck: true,
            silentDenied: true
        });
    },

    /**
     * بررسی وجود فعالیت برای معامله (دیتابیس داخلی)
     */
    async checkDealHasActivity(dealId) {
        const fallbackId =
            dealId ||
            this.state.currentDeal?.didar_deal_id ||
            this.state.currentDeal?.id ||
            this.state.currentDeal?.deal_id ||
            document.getElementById('deal-id')?.value ||
            '';
        if (!fallbackId) return false;
        try {
            const res = await this.req('has_deal_activity', { deal_id: fallbackId });
            const has = res.status === 'success' && !!res.has_activity;
            this.state.dealHasActivity = has;
            return has;
        } catch (e) {
            console.warn('has_deal_activity failed', e);
            this.state.dealHasActivity = false;
            return false;
        }
    },

    toggleDealActivityForm(hasActivity) {
        this.state.dealHasActivity = !!hasActivity;
        const formWrapper = document.getElementById('deal-activity-form-wrapper');
        const lockMsg = document.getElementById('deal-activity-locked-message');
        const submitBtn = document.getElementById('deal-activity-submit-btn');
        const form = document.getElementById('deal-activity-form');
        const fields = form ? form.querySelectorAll('input, select, textarea, button') : [];
        const chips = document.getElementById('deal-activity-service-chips');
        const svcSelect = document.getElementById('deal-activity-service-select');
        const svcAdd = document.getElementById('deal-activity-service-add');

        if (hasActivity) {
            lockMsg?.classList.remove('hidden');
            formWrapper?.classList.remove('hidden'); // نمایش برای مشاهده
            fields.forEach(el => {
                if (el === submitBtn) {
                    el.classList.add('hidden');
                    return;
                }
                if (el.tagName === 'BUTTON') {
                    el.disabled = true;
                    el.classList.add('cursor-not-allowed', 'opacity-60');
                } else {
                    if ('readOnly' in el) el.readOnly = true;
                    if ('disabled' in el) el.disabled = true;
                    el.classList.add('bg-gray-900', 'cursor-not-allowed');
                }
            });
            if (chips) chips.classList.add('pointer-events-none', 'opacity-70');
            if (svcSelect) {
                svcSelect.disabled = true;
                svcSelect.classList.add('cursor-not-allowed', 'bg-gray-900');
            }
            if (svcAdd) {
                svcAdd.disabled = true;
                svcAdd.classList.add('cursor-not-allowed', 'opacity-60');
            }
        } else {
            lockMsg?.classList.add('hidden');
            formWrapper?.classList.remove('hidden');
            fields.forEach(el => {
                if (el === submitBtn) {
                    el.classList.remove('hidden');
                    return;
                }
                if (el.tagName === 'BUTTON') {
                    el.disabled = false;
                    el.classList.remove('cursor-not-allowed', 'opacity-60');
                } else {
                    if ('readOnly' in el) el.readOnly = false;
                    if ('disabled' in el) el.disabled = false;
                    el.classList.remove('bg-gray-900', 'cursor-not-allowed');
                }
            });
            if (chips) chips.classList.remove('pointer-events-none', 'opacity-70');
            if (svcSelect) {
                svcSelect.disabled = false;
                svcSelect.classList.remove('cursor-not-allowed', 'bg-gray-900');
            }
            if (svcAdd) {
                svcAdd.disabled = false;
                svcAdd.classList.remove('cursor-not-allowed', 'opacity-60');
            }
        }
    },

    // UTILITIES
    toEnglishDigits(str = '') {
        const map = {
            '۰':'0','۱':'1','۲':'2','۳':'3','۴':'4','۵':'5','۶':'6','۷':'7','۸':'8','۹':'9',
            '٠':'0','١':'1','٢':'2','٣':'3','٤':'4','٥':'5','٦':'6','٧':'7','٨':'8','٩':'9'
        };
        return String(str).replace(/[۰-۹٠-٩]/g, d => map[d] || d);
    },

    normalizeDate(val = '') {
        const clean = this.toEnglishDigits(val).trim().replace(/-/g, '/');
        if (!clean) return '';
        if (/^\d{4}\/\d{1,2}\/\d{1,2}$/.test(clean)) return clean;
        return '';
    },

    quickPipelineChange(dealId, stage, status) {
        if (!stage) return;
        // برای مراحل دوره/پشتیبانی/اتمام/مرجوعی از مودال استفاده کن
        if (stage === 'برگزاری دوره' || stage === 'پشتیبانی' || stage === 'اتمام' || stage === 'مرجوعی') {
            // نیاز به اطلاعات معامله فعلی برای contact_id
            if (this.state.currentDeal && (this.state.currentDeal.didar_deal_id === dealId || String(this.state.currentDeal.id) === String(dealId))) {
                this.openCourseModal(this.state.currentDeal, stage);
            } else {
                // اگر در state نبود، از API بگیر
                this.req('get_deal', {deal_id: dealId}).then(res => {
                    if (res.status === 'success' && res.deal) {
                        this.openCourseModal(res.deal, stage);
                    }
                });
            }
            return;
        }
        // سایر مراحل سریع
        this.req('set_deal_pipeline_stage', {deal_id: dealId, pipeline_stage: stage}).then(res => {
            if (res.status === 'success') {
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'مرحله به‌روزرسانی شد', timer:1500, showConfirmButton:false});
                this.loadDeals();
            } else {
                Swal.fire('خطا', res.message || 'به‌روزرسانی مرحله انجام نشد', 'error');
            }
        });
    },

    openCourseModal(deal, presetStage = '') {
        if (!deal) return;
        const modal = document.getElementById('modalCourse');
        if (!modal) return;
        document.getElementById('course-deal-id').value = deal.didar_deal_id || deal.id || '';
        document.getElementById('course-contact-id').value = deal.local_person_id || deal.contact_didar_id || '';
        document.getElementById('course-virtual').checked = false;
        document.getElementById('course-online').checked = false;
        document.getElementById('course-access-created').checked = false;
        document.getElementById('course-link-sent').checked = false;
        document.getElementById('course-date-jalali').value = '';
        document.getElementById('course-refund').checked = false;
        document.getElementById('course-refund-reason').value = '';
        document.getElementById('refund-section').classList.add('hidden');
        const nextStageSelect = document.getElementById('course-next-stage');
        if (presetStage === 'پشتیبانی') nextStageSelect.value = 'پشتیبانی';
        else nextStageSelect.value = 'اتمام';
        this.openModal('modalCourse');
    },

    toggleRefundSection() {
        const refundCheckbox = document.getElementById('course-refund');
        const section = document.getElementById('refund-section');
        if (refundCheckbox && section) {
            if (refundCheckbox.checked) section.classList.remove('hidden');
            else section.classList.add('hidden');
        }
    },

    async submitCourseStatus(e) {
        e.preventDefault();
        const form = e.target;
        const dealId = form.deal_id.value;
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله نامعتبر است', 'error');
            return;
        }
        let courseDate = form.course_date.value || '';
        let gregDate = '';
        if (courseDate) {
            gregDate = persianToGregorian(courseDate);
        }
        const data = {
            deal_id: dealId,
            contact_id: form.contact_id.value || '',
            course_virtual: form.course_virtual.checked ? '1' : '',
            course_online: form.course_online.checked ? '1' : '',
            access_created: form.access_created.checked ? '1' : '',
            link_sent: form.link_sent.checked ? '1' : '',
            course_date: gregDate,
            next_stage: form.next_stage.value,
            refund: form.refund.checked ? '1' : '',
            refund_reason: form.refund_reason.value || ''
        };
        const res = await this.req('set_course_status', data);
        if (res.status === 'success') {
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'به‌روز شد', timer:1500, showConfirmButton:false});
            this.closeModal('modalCourse');
            this.loadDeals();
        } else {
            Swal.fire('خطا', res.message || 'ذخیره نشد', 'error');
        }
    },

    async addProductPrompt() {
        const { value: name } = await Swal.fire({
            title: 'افزودن محصول',
            input: 'text',
            inputLabel: 'نام محصول',
            inputPlaceholder: 'نام محصول را وارد کنید',
            showCancelButton: true,
            confirmButtonText: 'ذخیره',
            cancelButtonText: 'لغو',
            inputValidator: (value) => {
                if (!value || !value.trim()) {
                    return 'نام محصول الزامی است';
                }
            }
        });
        if (!name) return;
        try {
            const res = await this.req('save_product', { name });
            if (res.status === 'success') {
                await this.loadProducts();
                Swal.fire('موفق', 'محصول ذخیره شد', 'success');
            } else {
                Swal.fire('خطا', res.message || 'ذخیره محصول ناموفق بود', 'error');
            }
        } catch (e) {
            Swal.fire('خطا', 'ذخیره محصول ناموفق بود', 'error');
        }
    },

    renderProductOptions(selectedIds = []) {
        const container = document.getElementById('deal-products-list');
        if (!container) return;
        const selectedSet = new Set((selectedIds || []).map(id => String(id)));
        if (!this.state.products || this.state.products.length === 0) {
            container.innerHTML = '<div class="text-xs text-gray-500">محصولی ثبت نشده است.</div>';
            return;
        }
        container.innerHTML = this.state.products.map(p => {
            const checked = selectedSet.has(String(p.id)) ? 'checked' : '';
            return `
                <label class="flex items-center gap-2 text-sm text-gray-300">
                    <input type="checkbox" name="product_ids[]" value="${p.id}" class="form-checkbox text-yellow-500" ${checked}>
                    <span>${p.name}</span>
                </label>
            `;
        }).join('');
    },

    renderDealTitleOptions(selectedValue = null) {
        const select = document.getElementById('deal-title');
        if (!select) return;
        const current = selectedValue !== null ? selectedValue : (select.value || '');
        const generated = Array.from(select.options || []).filter(opt => opt.dataset && opt.dataset.generated === '1');

        select.innerHTML = '<option value="">-- انتخاب خدمات قابل ارائه --</option>';
        const allowedServices = this.getAllowedServiceLookupItemsDetailed();
        if (allowedServices.length) {
            allowedServices.forEach(item => {
                const serviceValue = (item.value ?? item.title ?? '').trim ? (item.value ?? item.title ?? '').trim() : (item.value ?? item.title ?? '');
                if (!serviceValue) return;
                const linkedProductIds = this.getProductsLinkedToServiceLookupItem(item.id).map(p => String(p.id));
                const primaryProductId = linkedProductIds[0] || '';
                const opt = document.createElement('option');
                opt.value = serviceValue;
                opt.textContent = item.title || serviceValue;
                if (primaryProductId) opt.dataset.productId = primaryProductId;
                if (linkedProductIds.length) opt.dataset.productIds = linkedProductIds.join(',');
                if (item.id) opt.dataset.serviceLookupItemId = String(item.id);
                select.appendChild(opt);
            });
        } else {
            const products = Array.isArray(this.state.products) ? this.state.products : [];
            products.forEach(p => {
                if (!p || !p.name) return;
                const opt = document.createElement('option');
                opt.value = p.name;
                opt.textContent = p.name;
                opt.dataset.productId = p.id;
                select.appendChild(opt);
            });
        }

        generated.forEach(generatedOpt => {
            if (!generatedOpt.value) return;
            const exists = Array.from(select.options || []).some(o => o.value === generatedOpt.value);
            if (exists) return;
            const opt = document.createElement('option');
            opt.value = generatedOpt.value;
            opt.textContent = generatedOpt.textContent;
            opt.dataset.generated = '1';
            select.appendChild(opt);
        });

        if (current) {
            const hasCurrent = Array.from(select.options || []).some(o => o.value === current);
            if (hasCurrent) {
                select.value = current;
            }
        }
    },
    syncDealProductsFromSelectedService() {
        const titleSelect = document.getElementById('deal-title');
        const productsContainer = document.getElementById('deal-products-list');
        if (!titleSelect || !productsContainer) return;
        const selectedOption = titleSelect.selectedOptions?.[0];
        if (!selectedOption) return;
        const raw = String(selectedOption.dataset?.productIds || '').trim();
        if (raw !== '') {
            const ids = raw.split(',').map(v => v.trim()).filter(Boolean);
            this.renderProductOptions(ids);
            return;
        }
        if (selectedOption.dataset?.serviceLookupItemId) {
            // Service selected but no linked products configured: clear stale auto-selected values.
            this.renderProductOptions([]);
        }
    },

    parseRequestedServicesForDisplay(value) {
        if (!value) return [];
        if (Array.isArray(value)) {
            return value.map(v => String(v || '').trim()).filter(Boolean);
        }
        const raw = String(value).trim();
        if (!raw) return [];
        try {
            const parsed = JSON.parse(raw);
            if (Array.isArray(parsed)) {
                return parsed.map(v => String(v || '').trim()).filter(Boolean);
            }
        } catch (_) {}
        return raw
            .split(/[,،]/)
            .map(v => String(v || '').trim())
            .filter(Boolean);
    },

    getLeadProductsDisplayValue(lead) {
        if (!lead || typeof lead !== 'object') return '-';
        const customerProducts = String(lead.customer_products || '').trim();
        if (customerProducts) return customerProducts;
        const requested = this.parseRequestedServicesForDisplay(lead.requested_services);
        return requested.length ? requested.join('، ') : '-';
    },

    getLeadProductFilterEntries() {
        const entries = [];
        const seenServiceValues = new Set();
        const products = Array.isArray(this.state.products) ? this.state.products : [];
        const services = this.getAllowedServiceLookupItemsDetailed().filter(item => String(item.is_active ?? 1) === '1');

        services.forEach(service => {
            const serviceId = parseInt(service?.id ?? 0, 10);
            if (!serviceId) return;
            const linkedProducts = this.getProductsLinkedToServiceLookupItem(serviceId);
            if (!linkedProducts.length) return;
            const serviceValue = String(service.value ?? service.title ?? '').trim();
            const serviceLabel = String(service.title ?? service.value ?? '').trim();
            if (!serviceValue || seenServiceValues.has(serviceValue)) return;
            seenServiceValues.add(serviceValue);
            entries.push({
                value: `svc::${serviceValue}`,
                label: serviceLabel || serviceValue,
                sort_key: String(serviceLabel || serviceValue).toLowerCase()
            });
        });

        products.forEach(product => {
            const productId = parseInt(product?.id ?? 0, 10);
            if (!productId) return;
            const linkedServiceId = parseInt(product?.service_lookup_item_id ?? 0, 10);
            if (linkedServiceId > 0) return;
            const name = String(product?.name || '').trim();
            if (!name) return;
            entries.push({
                value: `prod::${productId}`,
                label: name,
                sort_key: String(name).toLowerCase()
            });
        });

        entries.sort((a, b) => String(a.sort_key).localeCompare(String(b.sort_key), 'fa'));
        return entries;
    },

    renderLeadProductFilter() {
        const select = document.getElementById('leads-product-filter');
        if (!select) return;
        const current = select.value;
        select.innerHTML = '<option value="">محصول / خدمت</option>';

        const appendOption = (value, label) => {
            const opt = document.createElement('option');
            opt.value = value;
            opt.textContent = label;
            select.appendChild(opt);
        };

        const entries = this.getLeadProductFilterEntries();
        entries.forEach(entry => appendOption(entry.value, entry.label));

        if (current) {
            select.value = current;
        }
    },

    initLeadFiltersDatePickers() {
        // Filter date ranges: max = today (no future), with from<=to coupling, on the shared component.
        this.initJalaliRangeFilter('leads-register-from', 'leads-register-to');
        this.initJalaliRangeFilter('leads-activity-from', 'leads-activity-to');
        this.initJalaliRangeFilter('deals-register-from', 'deals-register-to');
        this.initJalaliRangeFilter('deals-activity-from', 'deals-activity-to');
    },

    async renderDealActivitiesInline(contactId) {
        const container = document.getElementById('deal-activities-inline');
        if (!container) return;
        container.innerHTML = '<div class="text-center text-gray-500 py-6">در حال بارگذاری...</div>';
        try {
            const dealId =
                document.getElementById('deal-id')?.value ||
                this.state.currentDeal?.didar_deal_id ||
                this.state.currentDeal?.id ||
                this.state.currentDeal?.deal_id ||
                '';

            if (dealId) {
                const res = await this.req('get_deal_activities', { deal_id: dealId });
                if (res.status === 'success') {
                    const acts = res.activities || [];
                    this.state.lastDealActivitiesCount = acts.length || 0;
                    this.setDealInlineActivityCreateMode(acts.length === 0);
                    if (acts.length === 0) {
                        this.renderDealActivitiesEmptyState(container);
                    } else {
                        this.renderDidarActivities(acts, container);
                    }
                    this.updateDealActivitiesBadge(acts.length || 0);
                    this.syncDealCancelButton();
                return;
            }
            }

            // برای معاملات جدید، فعالیت‌ها نمایش داده نشود
            this.state.lastDealActivitiesCount = 0;
            this.setDealInlineActivityCreateMode(true);
            this.renderDealActivitiesEmptyState(container);
            this.updateDealActivitiesBadge(0);
            this.syncDealCancelButton();
        } catch (e) {
            console.error('Error rendering inline activities:', e);
            this.state.lastDealActivitiesCount = 0;
            this.setDealInlineActivityCreateMode(false);
            container.innerHTML = '<div class="text-center text-red-400 py-6">خطا در دریافت فعالیت‌ها</div>';
            this.updateDealActivitiesBadge(0);
            this.syncDealCancelButton();
        }
    },

    setDealInlineActivityCreateMode(empty = false) {
        document.querySelectorAll('.deal-add-activity-btn').forEach(btn => {
            btn.style.display = empty ? 'none' : '';
        });
    },

    renderDealActivitiesEmptyState(container) {
        if (!container) return;
        container.innerHTML = `
            <div class="text-center text-gray-500 py-8">فعالیتی یافت نشد</div>
            <button type="button" class="btn-primary w-full py-3 rounded-lg text-base font-bold flex items-center justify-center gap-2" onclick="app.startActivityForDeal()">
                <i class="fa-solid fa-plus"></i>
                ثبت فعالیت جدید
            </button>
        `;
    },
    leadSources: [
        {value:'instagram', label:'اینستاگرام'},
        {value:'telegram', label:'تلگرام'},
        {value:'book', label:'کتاب'},
        {value:'website', label:'سایت'},
        {value:'whatsapp', label:'واتساپ'},
        {value:'campaign', label:'کمپین'},
        {value:'event', label:'ایونت (event)'},
        {value:'support', label:'پشتیبانی'},
        {value:'incoming_call', label:'تماس ورودی'},
        {value:'visit', label:'مراجعه حضوری'},
        {value:'referral', label:'معرف'},
        {value:'other', label:'سایر'}
    ],
    leadSourceDetails: {
        instagram: [
            {value:'instagram_direct', label:'اینستاگرام - دایرکت'},
            {value:'instagram_box', label:'اینستاگرام - باکس'},
            {value:'instagram_highlight', label:'اینستاگرام - هایلایت'},
            {value:'instagram_channel', label:'اینستاگرام - کانال'},
            {value:'instagram_auto', label:'اینستاگرام - اتوماسیون'}
        ],
        whatsapp: [
            {value:'whatsapp_marketing', label:'واتساپ مارکتینگ'}
        ],
        website: [
            {value:'website_signup', label:'سایت - ثبت نام در سایت'},
            {value:'website_course_signup', label:'سایت - ثبت نام دوره'},
            {value:'website_failed_purchase', label:'سایت - خرید ناموفق'}
        ],
        campaign: [
            {value:'campaign_sms', label:'کمپین - پیامک'},
            {value:'campaign_divar', label:'کمپین - دیوار'},
            {value:'campaign_porsline', label:'کمپین - پرسلاین'},
            {value:'campaign_web', label:'کمپین وب'}
        ],
        event: [
            {value:'event_live', label:'ایونت - لایو'},
            {value:'event_meetup', label:'ایونت - دورهمی'},
            {value:'event_course', label:'ایونت - دوره'}
        ],
        referral: [
            {value:'referral_internal', label:'معرف - همکاران داخلی'},
            {value:'referral_external', label:'معرف - همکاران خارج از سازمان'},
            {value:'referral_teacher', label:'معرف - استاد مطلبی'},
            {value:'referral_customer', label:'معرف - مشتری قدیم'}
        ],
        default: [
            {value:'generic', label:'عمومی'}
        ]
    },
    contentTopics: [
        {value:'car_intelligence', label:'هوش خودرو'},
        {value:'in_person_inspection', label:'کارشناسی حضوری'},
        {value:'business', label:'کسب و کار'},
        {value:'mini_course', label:'مینی دوره هوش خودرو'},
        {value:'vip_channel', label:'کانال VIP'},
        {value:'budgeting', label:'بودجه بندی'},
        {value:'online_exhibition', label:'نمایشگاه آنلاین'},
        {value:'consulting', label:'مشاوره با استاد'},
        {value:'inspection_device', label:'دستگاه کارشناسی'},
        {value:'car_intelligence_test', label:'تست هوش خودرو'},
        {value:'financial_personality', label:'تست تیپ شخصیت مالی'},
        {value:'smart_trader', label:'معامله گر هوشمند'},
        {value:'detailing_training', label:'آموزش دیتیلینگ'},
        {value:'divar_car', label:'دیوار خودرو'},
        {value:'inspection_test', label:'تست کارشناسی'},
        {value:'book', label:'کتاب هوش خودرو'},
        {value:'cip', label:'CIP'}
    ],
        getLeadSourceLabel(value) {
        return this.getLookupLabel('lead_sources', value);
    },
    getLeadDetailLabel(value) {
        return this.getLookupLabel('lead_source_details', value);
    },
    getContentTopicLabel(value) {
        return this.getLookupLabel('content_topics', value);
    },
    escapeHtml(str) {
        if(!str) return '';
        return str.replace(/&/g, '&amp;')
                  .replace(/</g, '&lt;')
                  .replace(/>/g, '&gt;')
                  .replace(/"/g, '&quot;')
                  .replace(/'/g, '&#39;');
    },

    /**
     * Bidi-isolate a value (phone, email, شناسه خارجی, Latin code) so its
     * characters don't visually reorder when embedded in Persian/RTL text.
     * Escapes the value first, then wraps it in <bdi>.
     */
    bidi(value) {
        const s = (value === null || value === undefined) ? '' : String(value);
        if (s === '') return '';
        return '<bdi>' + this.escapeHtml(s) + '</bdi>';
    },

    /**
     * Strip HTML tags and convert HTML entities to plain text
     * @param {string} html - HTML string to clean
     * @param {boolean} preserveLineBreaks - If true, convert <br> and <p> to line breaks
     * @returns {string} Clean text
     */
    stripHtml(html, preserveLineBreaks = true) {
        if (!html) return '';
        
        // Create a temporary div element to parse HTML
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = html;
        
        // Get text content (automatically strips HTML tags)
        let text = tempDiv.textContent || tempDiv.innerText || '';
        
        // Replace common HTML entities
        text = text.replace(/&nbsp;/g, ' ')
                   .replace(/&amp;/g, '&')
                   .replace(/&lt;/g, '<')
                   .replace(/&gt;/g, '>')
                   .replace(/&quot;/g, '"')
                   .replace(/&#39;/g, "'")
                   .replace(/&apos;/g, "'");
        
        // Clean up multiple spaces and line breaks
        if (preserveLineBreaks) {
            // Replace multiple spaces with single space, but preserve line breaks
            text = text.replace(/[ \t]+/g, ' ')
                       .replace(/\n\s*\n/g, '\n\n') // Preserve paragraph breaks
                       .trim();
        } else {
            // Replace all whitespace with single space
            text = text.replace(/\s+/g, ' ').trim();
        }
        
        return text;
    },

    getEffectiveUserRoleClient() {
        const loadedRole = this.state.rbacCurrentPermissionsLoaded
            ? String(this.state.rbacCurrentPermissions?.current_role_key || this.state.rbacCurrentPermissions?.role?.role_key || '').trim()
            : '';
        return loadedRole || String(this.state.user?.role || '').trim();
    },

    isAdmin() {
        const role = this.getEffectiveUserRoleClient().toLowerCase();
        return role === 'admin' || role === 'super_admin';
    },
    isSystemRole() {
        return this.getEffectiveUserRoleClient() === 'system';
    },
    isAccountant() {
        return this.getEffectiveUserRoleClient() === 'accountant';
    },
    canDeletePaymentsClient() {
        const role = this.getEffectiveUserRoleClient().toLowerCase();
        return ['admin', 'super_admin', 'system'].includes(role);
    },
    isAdminOrCrmSpecialist() {
        const role = this.getEffectiveUserRoleClient();
        return role === 'admin' || role === 'super_admin' || role === 'crm_specialist' || role === 'system';
    },

    canUseGlobalBulkDelete() {
        const role = this.getEffectiveUserRoleClient().toLowerCase();
        return role === 'admin' || role === 'super_admin' || role === 'system';
    },
    isAdminLikeForAnalytics() {
        return this.isAdmin() || this.isSystemRole();
    },
    canAccessCeoPerformanceReportClient() {
        if (this.isAdminLikeForAnalytics()) return true;
        return this.canCurrentUserCapabilityClient('performance.ceo_report');
    },
    getRbacDataScopeClient(resourceKey) {
        const key = String(resourceKey || '').trim();
        if (!key) return null;
        const scopes = this.state.rbacCurrentPermissions?.scopes;
        if (!Array.isArray(scopes)) return null;
        return scopes.find(s => String(s?.resource_key || '') === key) || null;
    },
    hasCrossUserScopeClient(resourceKey) {
        if (this.isAdminLikeForAnalytics()) return true;
        const scope = this.getRbacDataScopeClient(resourceKey);
        if (!scope) return false;
        const type = String(scope.scope_type || 'self');
        if (type === 'all') return true;
        if (type === 'selected_users') {
            return Array.isArray(scope.selected_user_ids) && scope.selected_user_ids.length > 0;
        }
        return false;
    },
    shouldLoadDidarUsersForFilters() {
        if (this.isAdminOrCrmSpecialist()) return true;
        return ['dashboard', 'leads', 'deals', 'payments', 'cheques', 'kpi', 'performance', 'commissions', 'operation_manager'].some(key => this.hasCrossUserScopeClient(key));
    },
    getPreferredDidarUsersResourceKey(preferred = '') {
        const requested = String(preferred || '').trim();
        if (requested) return requested;
        if (this.isAdminOrCrmSpecialist()) return 'users';
        return ['performance', 'kpi', 'dashboard', 'payments', 'cheques', 'deals', 'leads', 'commissions', 'operation_manager']
            .find(key => this.hasCrossUserScopeClient(key)) || 'users';
    },
    currentUserDidarUserOption() {
        const user = this.state.user || {};
        const didarId = String(user.didar_id || user.didar_user_id || '').trim();
        if (!didarId) return null;
        const name = String(user.name || user.display_name || user.email || didarId).trim();
        return {
            id: user.id || '',
            owner_id: didarId,
            didar_user_id: didarId,
            display_name: name || didarId,
            email: user.email || '',
            is_active: 1
        };
    },
    setDidarUsersFallbackToCurrentUser() {
        const currentUserOption = this.currentUserDidarUserOption();
        this.state.didarUsers = currentUserOption ? [currentUserOption] : [];
    },
    async loadCurrentRbacPermissions(force = false) {
        if (!this.state.user || !this.state.user.role || this.state.user.role === 'guest') return;
        if (!force && this.state.rbacCurrentPermissionsLoaded) return;
        const res = await this.req('rbac_current_permissions');
        if (res.status !== 'success') return;
        this.state.rbacCurrentPermissionsLoaded = true;
        this.state.rbacCurrentPermissions = res;
        const effectiveRole = String(res.current_role_key || res.role?.role_key || '').trim();
        if (effectiveRole && this.state.user) {
            this.state.user.role = effectiveRole;
            if (window.APP_USER) window.APP_USER.role = effectiveRole;
        }
    },
    canCurrentUserCapabilityClient(capabilityKey) {
        const role = this.getEffectiveUserRoleClient() || 'agent';
        // admin (مدیر عامل) and system are full-access operator roles — never gate them
        // client-side (the server enforces the same). Prevents write buttons from hiding
        // when permissions are still loading or were reset to the read-only catalog default.
        if (role === 'system' || role === 'admin') return true;
        const caps = this.state.rbacCurrentPermissions?.capabilities;
        if (Array.isArray(caps) && this.state.rbacCurrentPermissionsLoaded) {
            const aliases = {
                'action.leads.create': 'leads.create',
                'action.leads.edit': 'leads.edit',
                'action.leads.delete': 'leads.delete',
                'action.deals.create': 'deals.create',
                'action.deals.edit': 'deals.edit',
                'action.deals.delete': 'deals.delete',
                'action.payments.create': 'payments.create',
                'action.payments.edit': 'payments.edit',
                'action.payments.delete': 'payments.delete'
            };
            const candidates = new Set([capabilityKey]);
            if (aliases[capabilityKey]) candidates.add(aliases[capabilityKey]);
            Object.entries(aliases).forEach(([legacy, canonical]) => {
                if (canonical === capabilityKey) candidates.add(legacy);
            });
            const delegated = {
                'users.view': ['users.admin.view'],
                'users.edit': ['users.admin.edit'],
                'users.delete': ['users.admin.delete'],
                'users.roles.edit': ['users.admin.roles.edit'],
                'users.passwords.change': ['users.admin.passwords.change'],
                'users.transfer_data': ['users.admin.transfer_data'],
                'users.sync_didar': ['users.admin.sync_didar'],
                'referrals.view': ['referrals.delegated_view'],
                'audit_logs.view': ['audit_logs.admin.view'],
                'audit_logs.recover': ['audit_logs.admin.recover'],
                'settings.didar_write.manage': ['settings.admin.didar_write.manage'],
                'settings.auto_migration.manage': ['settings.admin.auto_migration.manage'],
                'settings.external_api.manage': ['settings.admin.external_api.manage'],
                'settings.master_password.manage': ['settings.admin.master_password.manage'],
                'notifications.manage': ['notifications.admin.test'],
                'maintenance.view': ['maintenance.admin.view'],
                'maintenance.backup': ['maintenance.admin.backup'],
                'maintenance.restore': ['maintenance.admin.restore'],
                'maintenance.backfill.preview': ['maintenance.admin.backfill.preview'],
                'maintenance.backfill.run': ['maintenance.admin.backfill.run'],
                'maintenance.backfill.verify': ['maintenance.admin.backfill.verify']
            };
            Object.entries(delegated).forEach(([base, delegatedKeys]) => {
                if (capabilityKey === base) delegatedKeys.forEach(key => candidates.add(key));
            });
            if (['deals.status.edit', 'deals.pipeline.edit', 'deals.course.edit', 'deals.reactivate', 'deals.force_success', 'deals.reconcile'].includes(capabilityKey)) {
                candidates.add('deals.edit');
                candidates.add('action.deals.edit');
            }
            if (['leads.transfer_owner', 'leads.backfill_products'].includes(capabilityKey)) {
                candidates.add('leads.edit');
                candidates.add('action.leads.edit');
            }
            return Array.from(candidates).some(key => caps.includes(key));
        }
        const fallback = {
            accountant: ['menu.exports', 'export.catalog', 'export.dataset.payments', 'menu.cheques', 'cheques.view', 'cheques.review', 'cheques.collect', 'financial_payments.view', 'financial_payments.sepidar'],
            crm_specialist: ['menu.dashboard', 'menu.leads', 'menu.operation_manager', 'operation_manager.view', 'operation_manager.preview', 'operation_manager.rules.view', 'lead_pools.view', 'lead_owner_history.view', 'menu.blataklif_leads', 'menu.deals', 'menu.legacy_deals', 'menu.referrals', 'menu.persons', 'menu.exports', 'menu.reminders', 'menu.settings', 'action.deals.edit', 'action.payments.create', 'action.payments.edit'],
            agent: ['menu.dashboard', 'menu.performance_dashboard', 'menu.leads', 'menu.blataklif_leads', 'menu.deals', 'menu.legacy_deals', 'menu.persons', 'menu.reminders', 'menu.settings', 'action.leads.edit', 'action.deals.create', 'action.deals.edit', 'action.payments.create', 'action.payments.edit'],
            system: ['menu.dashboard', 'menu.performance_dashboard', 'menu.leads', 'menu.operation_manager', 'operation_manager.view', 'operation_manager.preview', 'operation_manager.execute', 'operation_manager.rules.view', 'operation_manager.rules.manage', 'operation_manager.automation.run', 'lead_pools.view', 'lead_pools.manage', 'lead_owner_history.view', 'menu.blataklif_leads', 'menu.deals', 'menu.legacy_deals', 'menu.referrals', 'menu.persons', 'menu.reminders', 'menu.exports', 'menu.cheques', 'cheques.view', 'cheques.review', 'cheques.collect', 'financial_payments.view', 'financial_payments.sepidar', 'menu.kpi_dashboard', 'action.leads.create', 'action.leads.edit', 'action.deals.create', 'action.deals.edit', 'action.payments.create', 'action.payments.edit', 'kpi.view', 'kpi.drilldown', 'kpi.deals.details', 'kpi.targets.manage', 'performance.view', 'performance.drilldown']
        };
        return (fallback[role] || []).includes(capabilityKey);
    },
    canActionClient(actionKey) {
        return this.canCurrentUserCapabilityClient(`action.${actionKey}`);
    },
    roleHasAnyCapabilityPrefix(prefix) {
        const caps = this.state.rbacCurrentPermissions?.capabilities;
        return Array.isArray(caps) && caps.some(c => String(c || '').startsWith(prefix));
    },
    canPerformanceFeatureClient(capabilityKey) {
        const globallyDisabledPerfCaps = [
            'performance.chart.deal_status',
            'performance.chart.conversion_funnel'
        ];
        if (globallyDisabledPerfCaps.includes(capabilityKey)) {
            return false;
        }
        if (capabilityKey === 'performance.ceo_report' || String(capabilityKey || '').startsWith('performance.table.ceo_')) {
            return this.canAccessCeoPerformanceReportClient();
        }
        const nonAdminHiddenPerfCaps = [
            'performance.table.activity_summary',
            'performance.table.activity_type_details'
        ];
        if (!this.isAdminLikeForAnalytics() && nonAdminHiddenPerfCaps.includes(capabilityKey)) {
            return false;
        }
        if (this.isAdminLikeForAnalytics()) return true;
        const hasPerfSpecificCaps =
            this.roleHasAnyCapabilityPrefix('performance.') ||
            this.roleHasAnyCapabilityPrefix('performance.chart.') ||
            this.roleHasAnyCapabilityPrefix('performance.table.') ||
            this.roleHasAnyCapabilityPrefix('performance.card.');
        if (!hasPerfSpecificCaps) {
            return this.canCurrentUserCapabilityClient('menu.performance_dashboard');
        }
        return this.canCurrentUserCapabilityClient(capabilityKey);
    },
    canKpiFeatureClient(capabilityKey) {
        if (this.isAdminLikeForAnalytics()) return true;
        const hasKpiSpecificCaps = this.roleHasAnyCapabilityPrefix('kpi.');
        if (!hasKpiSpecificCaps) {
            return this.canCurrentUserCapabilityClient('menu.kpi_dashboard');
        }
        return this.canCurrentUserCapabilityClient(capabilityKey);
    },
    applyRbacActionVisibility() {
        const setHidden = (id, hidden) => {
            const el = document.getElementById(id);
            if (!el) return;
            el.classList.toggle('hidden', !!hidden);
            if ('disabled' in el) el.disabled = !!hidden;
        };
        setHidden('leads-add-btn', !this.canActionClient('leads.create'));
        setHidden('profile-add-deal-btn', !this.canActionClient('deals.create'));

        const leadEditBtn = document.getElementById('lead-edit-btn');
        if (leadEditBtn) leadEditBtn.classList.toggle('hidden', !this.canActionClient('leads.edit'));

        const leadDeleteBtn = document.getElementById('lead-delete-btn');
        if (leadDeleteBtn) {
            const canDeleteLead = this.canActionClient('leads.delete');
            leadDeleteBtn.classList.toggle('hidden', !canDeleteLead);
            leadDeleteBtn.disabled = !canDeleteLead;
        }

        const dealInfoSubmitBtn = document.getElementById('deal-info-submit-btn');
        if (dealInfoSubmitBtn) {
            const canSaveDeal = this.canActionClient('deals.create') || this.canActionClient('deals.edit');
            dealInfoSubmitBtn.classList.toggle('hidden', !canSaveDeal);
            dealInfoSubmitBtn.disabled = !canSaveDeal;
        }
        const dealPaymentsSubmitBtn = document.getElementById('deal-payments-submit-btn');
        if (dealPaymentsSubmitBtn) {
            const canSavePaymentsViaDeal = this.canActionClient('payments.create') || this.canActionClient('payments.edit') || this.canActionClient('payments.delete');
            dealPaymentsSubmitBtn.classList.toggle('hidden', !canSavePaymentsViaDeal);
            dealPaymentsSubmitBtn.disabled = !canSavePaymentsViaDeal;
            this.updatePaymentsSubmitState();
        }
        const addPaymentBtn = document.getElementById('deal-add-payment-btn');
        if (addPaymentBtn) {
            const canCreatePayment = this.canActionClient('payments.create');
            addPaymentBtn.classList.toggle('hidden', !canCreatePayment);
            addPaymentBtn.disabled = !canCreatePayment;
        }
    },
    applyRbacMenuVisibility() {
        const menuCapMap = {
            'menu-dashboard': 'menu.dashboard',
            'menu-ai-assistant': 'menu.ai_assistant',
            'menu-performance-dashboard': 'menu.performance_dashboard',
            'menu-leads': 'menu.leads',
            'menu-operation-manager': 'menu.operation_manager',
            'menu-blataklif-leads': 'menu.blataklif_leads',
            'menu-deals': 'menu.deals',
            'menu-legacy-deals': 'menu.legacy_deals',
            'menu-referrals': 'menu.referrals',
            'menu-persons': 'menu.persons',
            'menu-reminders': 'menu.reminders',
            'menu-settings': 'menu.settings',
            'menu-lookups': 'menu.lookups',
            'menu-exports': 'menu.exports',
            'menu-audit-logs': 'menu.audit_logs',
            'menu-users': 'menu.users',
            'menu-maintenance': 'menu.maintenance',
            'menu-commissions': 'menu.commissions',
            'menu-commission-calc': 'menu.commissions',
            'menu-cheques': 'menu.cheques',
            'menu-kpi': 'menu.kpi_dashboard',
            'menu-customer-access': 'menu.customer_access'
        };
        Object.entries(menuCapMap).forEach(([menuId, cap]) => {
            const el = document.getElementById(menuId);
            if (el && !el.dataset.rbacCapability) el.dataset.rbacCapability = cap;
        });

        const menuNodes = Array.from(document.querySelectorAll('[data-rbac-menu][data-rbac-capability]'));
        const permissionsLoaded = this.state.rbacCurrentPermissionsLoaded === true;
        menuNodes.forEach(el => {
            const cap = String(el.dataset.rbacCapability || '').trim();
            const allowed = permissionsLoaded && cap !== '' && this.canCurrentUserCapabilityClient(cap);
            el.classList.toggle('hidden', !allowed);
            el.dataset.rbacVisible = allowed ? '1' : '0';
            if ('disabled' in el) el.disabled = !allowed;
        });

        const adminSection = document.getElementById('admin-section');
        if (adminSection) {
            const hasVisibleItem = Array.from(adminSection.querySelectorAll('[data-rbac-menu]'))
                .some(el => el.dataset.rbacVisible === '1');
            adminSection.classList.toggle('hidden', !hasVisibleItem);
        }
        const kpiSection = document.getElementById('kpi-menu-section');
        if (kpiSection) {
            const hasVisibleItem = Array.from(kpiSection.querySelectorAll('[data-rbac-menu]'))
                .some(el => el.dataset.rbacVisible === '1');
            kpiSection.classList.toggle('hidden', !hasVisibleItem);
        }
    },
    isLeadOwner(lead) {
        if(!lead || !lead.owner_didar_id) return false;
        return this.state.user?.didar_id && this.state.user.didar_id === lead.owner_didar_id;
    },
    async req(action, data = {}, options = {}) {
        const opts = options || {};
        const requestKey = String(opts.requestKey || '').trim();
        const externalSignal = opts.signal || null;
        const silentAbort = opts.silentAbort !== false;
        const shouldCancelByKey = this.featureFlags.modalFreshnessV1 && !!requestKey;
        const reqId = ++this._requestSeq;

        const fd = new FormData();
        for(let k in data) {
            if(data[k] !== null && data[k] !== undefined) {
                fd.append(k, data[k]);
            }
        }
        this.logTelemetry('request_start', { reqId, action, requestKey }, 'debug');
        console.log(`[DEBUG] API Request: ${action}`, data);
        try {
            const timeoutController = new AbortController();
            if (shouldCancelByKey) {
                this.cancelInFlightRequest(requestKey);
            }
            const keyController = shouldCancelByKey ? new AbortController() : null;
            if (shouldCancelByKey && keyController) {
                this._inFlightControllers[requestKey] = keyController;
            }
            const signal = this.composeAbortSignal([timeoutController.signal, keyController?.signal, externalSignal]);
            const timeoutId = setTimeout(() => timeoutController.abort(), 180000); // 3 minutes timeout
             
            const authToken = String(
                this.state?.authToken ||
                window.APP_AUTH_TOKEN ||
                localStorage.getItem('auth_token') ||
                ''
            ).trim();
            const csrfToken = String(
                window.APP_CSRF_TOKEN ||
                document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
                ''
            ).trim();
            const headers = {};
            if (authToken !== '') {
                headers['Authorization'] = `Bearer ${authToken}`;
            }
            if (csrfToken !== '') {
                headers['X-CSRF-TOKEN'] = csrfToken;
            }

            const r = await fetch('?api=' + action, {
                method:'POST',
                body:fd,
                headers,
                signal
            });
             
            clearTimeout(timeoutId);
            if (shouldCancelByKey && this._inFlightControllers[requestKey] === keyController) {
                delete this._inFlightControllers[requestKey];
            }
             
            if (!r.ok) {
                // Try to parse detailed error from server
                try {
                    const errRes = await r.json();
                    if (errRes && errRes.message) {
                        console.log(`[DEBUG] API Error Response: ${action}`, errRes);
                        return { status: 'error', message: errRes.message };
                    }
                } catch(_) {}
                throw new Error(`HTTP ${r.status}: ${r.statusText}`);
            }
             
            const response = await r.json();
            this.logTelemetry('request_success', { reqId, action, requestKey, status: response?.status || '' }, 'debug');
            console.log(`[DEBUG] API Response: ${action}`, response);
            return response;
        } catch(e) { 
            if (shouldCancelByKey) {
                const c = this._inFlightControllers[requestKey];
                if (c && c.signal && c.signal.aborted) {
                    delete this._inFlightControllers[requestKey];
                }
            }
            if (e && e.name === 'AbortError') {
                this.logTelemetry('request_abort', { reqId, action, requestKey }, 'debug');
                if (silentAbort) {
                    return { status: 'aborted', aborted: true, message: 'REQUEST_ABORTED' };
                }
            }
            console.error(`[DEBUG] API Error for ${action}:`, e);
             
            // Check if it's a network error or timeout
            if (e.name === 'AbortError' || e.message.includes('fetch')) {
                throw new Error('NETWORK_ERROR'); // Throw to be caught by setupSync
            }
            
            return {status:'error', message: 'خطا در ارتباط با سرور: ' + e.message}; 
        }
    },

    closeBlockingSwal() {
        if (typeof Swal === 'undefined') return;
        try {
            if (Swal.isVisible && !Swal.isVisible()) return;
        } catch (e) {}
        try {
            Swal.close();
        } catch (e) {}
    },

    cleanupPersianDatepickers(root = document, options = {}) {
        const opts = options || {};
        const scope = root || document;
        if (typeof $ !== 'undefined' && typeof $.fn?.persianDatepicker === 'function') {
            try {
                $(scope).find('input').addBack('input').each(function () {
                    const $input = $(this);
                    if ($input.data('persianDatepicker') || this._neoJdp) {
                        try {
                            $input.persianDatepicker('destroy');
                        } catch (e) {}
                    }
                    $input.removeData('perfDatepickerInit');
                });
            } catch (e) {}
        }
        if (opts.removeOrphans) {
            try {
                document.querySelectorAll('.neo-jdp').forEach(el => el.remove());
            } catch (e) {}
        }
    },

    toggleSidebar() {
        const sidebar = document.getElementById('main-sidebar');
        const backdrop = document.getElementById('sidebar-backdrop');
        if (!sidebar || !backdrop) return;
        
        const isClosed = sidebar.classList.contains('translate-x-full');
        if (isClosed) {
            sidebar.classList.remove('translate-x-full');
            sidebar.classList.add('translate-x-0');
            backdrop.classList.remove('hidden');
        } else {
            sidebar.classList.add('translate-x-full');
            sidebar.classList.remove('translate-x-0');
            backdrop.classList.add('hidden');
        }
    },

    closeSidebar() {
        const sidebar = document.getElementById('main-sidebar');
        const backdrop = document.getElementById('sidebar-backdrop');
        if (!sidebar || !backdrop) return;
        
        // Only if it's open (mobile)
        if (!sidebar.classList.contains('translate-x-full')) {
            sidebar.classList.add('translate-x-full');
            sidebar.classList.remove('translate-x-0');
            backdrop.classList.add('hidden');
        }
    },

    initTheme() {
        const theme = localStorage.getItem('theme') || 'dark';
        const body = document.body;
        const icon = document.getElementById('theme-icon');
        
        if (theme === 'light') {
            body.classList.add('light-theme');
            if (icon) {
                icon.classList.remove('fa-sun');
                icon.classList.add('fa-moon');
            }
        } else {
            body.classList.remove('light-theme');
            if (icon) {
                icon.classList.remove('fa-moon');
                icon.classList.add('fa-sun');
            }
        }
    },

    toggleTheme() {
        const body = document.body;
        const icon = document.getElementById('theme-icon');
        const isLight = body.classList.contains('light-theme');
        
        if (isLight) {
            body.classList.remove('light-theme');
            localStorage.setItem('theme', 'dark');
            if (icon) {
                icon.classList.remove('fa-moon');
                icon.classList.add('fa-sun');
            }
        } else {
            body.classList.add('light-theme');
            localStorage.setItem('theme', 'light');
            if (icon) {
                icon.classList.remove('fa-sun');
                icon.classList.add('fa-moon');
            }
        }
    },

    async init() {
        if (typeof Logger !== 'undefined') Logger.init();
        this.initFeatureFlags();
        this.bindGlobalTelemetryHandlers();
        console.log('[DEBUG] App init started');
        console.log('[DEBUG] Current user state:', this.state.user);
        console.log('[DEBUG] isAdmin:', this.isAdmin());
        console.log('[DEBUG] isAdminOrCrmSpecialist:', this.isAdminOrCrmSpecialist());

        // Fail closed before any async bootstrap work. Some deployed PHP views or
        // cached HTML can render sidebar items visible; RBAC will reveal allowed
        // menus after rbac_current_permissions loads below.
        this.applyRbacMenuVisibility();
        
        await this.loadLookups();
        await this.loadCurrentRbacPermissions();
        this.applyLookupsToStaticSelects();
        this.applyRbacMenuVisibility();
        this.applyRbacActionVisibility();
        this.initMoneyInputFormatting();
        this.initTheme();
        this.loadActivityTypes();
        this.applyDidarUiDisabledState();
        this.bindDirtyFormTracking();
        
        if (this.shouldLoadDidarUsersForFilters()) {
            console.log('[DEBUG] User is eligible for cross-user filters, loading External CRM users...');
            await this.loadDidarUsers();
        } else {
            console.log('[DEBUG] User is not eligible for cross-user filters, skipping External CRM users');
        }
        
        // Bootstrap view router (history-aware when feature flag is enabled)
        await this.bootstrapRouter();
        if (this.isAdmin()) {
            this.loadReferrals();
        }
        
        console.log('[DEBUG] App init complete');
        
        // Initialize notifications
        this.loadNotifications();
        this.startNotificationPolling();

        // Request notification permission after a short delay
        setTimeout(() => {
            if ('Notification' in window) {
                console.log('Notification support detected, permission:', Notification.permission);

                // Check if we're on HTTPS (required for notifications in some browsers)
                if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
                    console.log('⚠️ Desktop notifications require HTTPS. Current protocol:', location.protocol);
                }

                if (Notification.permission === 'default') {
                    console.log('Requesting notification permission on startup');
                    this.requestNotificationPermission();
                } else if (Notification.permission === 'denied') {
                    console.log('Notifications are blocked by user');
                } else {
                    console.log('Notifications already granted');
                }
            } else {
                console.log('This browser does not support desktop notifications');
            }
        }, 2000);

        // Load notification settings (default to enabled)
        this.notificationSoundEnabled = localStorage.getItem('notificationSoundEnabled') !== 'false';
        this.notificationPopupEnabled = localStorage.getItem('notificationPopupEnabled') !== 'false';
        this.notificationBrowserEnabled = localStorage.getItem('notificationBrowserEnabled') !== 'false';

        // Update sound button
        this.updateSoundButton();

        // Initialize reminder offset calculators
        this.initReminderOffsetListeners();

        console.log('Notification settings loaded:', {
            sound: this.notificationSoundEnabled,
            popup: this.notificationPopupEnabled,
            browser: this.notificationBrowserEnabled
        });
        
        // Close notifications dropdown when clicking outside
        document.addEventListener('click', (e) => {
            const container = document.getElementById('notifications-container');
            const dropdown = document.getElementById('notifications-dropdown');
            if (container && dropdown && !container.contains(e.target)) {
                dropdown.classList.add('hidden');
            }
        });
    },

    initMoneyInputFormatting() {
        if (this.state._moneyInputFormattingInitialized) return;
        this.state._moneyInputFormattingInitialized = true;

        document.addEventListener('input', (e) => {
            const el = e.target;
            if (!el || !(el instanceof HTMLElement)) return;
            if (!el.classList || !el.classList.contains('money-input')) return;
            formatMoneyInput(el);
        });

        document.querySelectorAll('.money-input').forEach((el) => {
            if (el instanceof HTMLElement) {
                el.setAttribute('dir', 'ltr');
                el.style.textAlign = 'right';
            }
        });
    },
    
    // SETUP
    async setupInit(e) {
        e.preventDefault();
        const apiKey = document.getElementById('setup-api-key').value;
        Swal.showLoading();
        let res;
        try {
            res = await this.req('setup_init', {api_key: apiKey});
        } catch (err) {
            console.error('setupInit failed', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
            return;
        }
        this.closeBlockingSwal();
        
        if(res.status === 'success') {
            document.getElementById('setup-step1').classList.add('hidden');
            const step2 = document.getElementById('setup-step2');
            step2.classList.remove('hidden');
            
            const list = document.getElementById('setup-users-list');
            list.innerHTML = '';
            res.users.forEach(u => {
                const div = document.createElement('div');
                div.className = 'p-3 bg-gray-800 rounded cursor-pointer hover:bg-gray-700 transition';
                div.innerHTML = `
                    <input type="radio" name="admin_user" value="${u.Id || u.UserId}" id="user-${u.Id || u.UserId}" class="mr-2">
                    <label for="user-${u.Id || u.UserId}" class="cursor-pointer">${u.DisplayName} (${u.UserName})</label>
                `;
                list.appendChild(div);
            });
            
            window.setupApiKey = apiKey;
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    async setupComplete() {
        const selected = document.querySelector('input[name="admin_user"]:checked');
        if(!selected) {
            Swal.fire('خطا', 'لطفاً کاربر ادمین را انتخاب کنید', 'error');
            return;
        }
        
        Swal.showLoading();
        let res;
        try {
            res = await this.req('setup_complete', {
                api_key: window.setupApiKey,
                admin_didar_id: selected.value
            });
        } catch (err) {
            console.error('setupComplete failed', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
            return;
        }
        this.closeBlockingSwal();
        
        if(res.status === 'success') {
            document.getElementById('setup-step2').classList.add('hidden');
            document.getElementById('setup-step3').classList.remove('hidden');
            
            // Check if there's existing data to resume
            const statusRes = await this.req('get_sync_status', {});
            if(statusRes.status === 'success' && statusRes.counts.deals > 0) {
                const resumeBtn = document.getElementById('setup-resume-btn');
                resumeBtn.classList.remove('hidden');
                resumeBtn.innerHTML = `<i class="fa-solid fa-play ml-1"></i> ادامه همگام‌سازی (${statusRes.counts.deals} معامله sync شده)`;
            } else {
                this.setupSync();
            }
        } else {
            Swal.close();
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    async setupSync(step = 'users', from = 0) {
        try {
            const res = await this.req('setup_sync', {step, from});
            
        if(res.status === 'success') {
                document.getElementById('setup-progress-bar').style.width = res.progress + '%';
                document.getElementById('setup-progress-text').textContent = res.message;
                
                // نمایش پیشرفت دقیق‌تر
                let progressDetail = `مرحله: ${step} | پیشرفت: ${res.progress}%`;
                if (res.total_count && res.from !== undefined) {
                    const synced = Math.min(res.from + (res.limit || 500), res.total_count);
                    progressDetail += ` | ${synced} از ${res.total_count}`;
                }
                document.getElementById('setup-progress-detail').textContent = progressDetail;
                
                // Hide resume button during successful sync
                document.getElementById('setup-resume-btn').classList.add('hidden');
                
                if(res.next_step === 'complete') {
                    // Calculate virtual stages in batch after sync completes
                    Swal.fire({icon:'info', title:'در حال محاسبه مراحل مجازی...', text:'لطفاً صبر کنید', allowOutsideClick: false});
                    try {
                        // virtual stages deprecated
                    } catch (e) {
                        console.error('Error updating virtual stages:', e);
                    }
                    Swal.fire({icon:'success', title:'نصب تکمیل شد', text:'در حال انتقال...', timer:2000});
                    setTimeout(() => location.reload(), 2000);
                } else {
                    // Fix: محاسبه nextFrom برای جلوگیری از لوپ بی‌نهایت
                    let nextFrom = 0;
                    if(res.next_step === step) {
                        // اگر همان step است، from را افزایش بده
                        nextFrom = (res.from || 0) + (res.limit || 500);
                    }
                    
                    setTimeout(() => this.setupSync(res.next_step, nextFrom), 500);
                }
            } else {
                // Show resume button on API error
                document.getElementById('setup-resume-btn').classList.remove('hidden');
                Swal.fire('خطا', res.message, 'error');
            }
        } catch (error) {
            // Network error handling
            console.error('Network error in setupSync:', error);
            document.getElementById('setup-resume-btn').classList.remove('hidden');
            
            // Check if it's a network error
            const isNetworkError = error.message === 'NETWORK_ERROR' || 
                                   error.message.includes('fetch') || 
                                   error.message.includes('network') ||
                                   error.message.includes('Failed to fetch');
            
            Swal.fire({
                icon: 'warning',
                title: 'خطا در ارتباط',
                html: isNetworkError 
                    ? 'ارتباط با سرور قطع شد.<br>داده‌های sync شده حفظ شده‌اند.<br>می‌توانید با دکمه "ادامه همگام‌سازی" از همانجا ادامه دهید.'
                    : 'خطا در پردازش درخواست.<br>لطفاً دوباره تلاش کنید.',
                showCancelButton: true,
                confirmButtonText: 'ادامه همگام‌سازی',
                cancelButtonText: 'بستن',
                allowOutsideClick: false
            }).then((result) => {
                if (result.isConfirmed) {
                    this.resumeSync();
                }
            });
        }
    },
    
    async resumeSync() {
        try {
            const res = await this.req('resume_sync', {});
            if (res.status === 'success') {
                this.setupSync(res.step || 'users', res.from || 0);
            } else {
                Swal.fire('خطا', res.message || 'خطا در ادامه همگام‌سازی', 'error');
            }
        } catch (error) {
            console.error('Error in resumeSync:', error);
            Swal.fire('خطا', 'خطا در ادامه همگام‌سازی: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    // LOGIN
    async login(e) {
        e.preventDefault();
        const username = document.getElementById('login-user-select').value;
        const password = document.getElementById('login-password').value;
        
        if (!username || !password) {
            Swal.fire('خطا', 'لطفاً نام کاربری و رمز عبور را وارد کنید', 'error');
            return;
        }
        
        const res = await this.req('login', {username: username, password: password});
        if(res.status === 'success') {
            location.reload();
        } else {
            Swal.fire('خطا', res.message || 'نام کاربری یا رمز عبور اشتباه است', 'error');
        }
    },

    refresh() {
        const active = document.querySelector('.sidebar-item.active');
        if(active) {
            const view = active.id.replace('menu-', '');
            if(view === 'dashboard') this.loadDashboard();
            if(view === 'leads') this.loadLeads();
            if(view === 'deals') this.loadDeals();
            if(view === 'referrals') this.loadReferrals();
            if(view === 'persons') this.loadPersons();
            if(view === 'settings') this.loadSettings();
        }
    },
    
    // SETTINGS
    loadSettings() {
        // Settings view is static, no need to load data
        // Just make sure forms are reset
        document.querySelectorAll('#view-settings form').forEach((form) => form.reset());
        const userIdInput = document.getElementById('settings-change-password-user-id');
        if (userIdInput) userIdInput.value = window.CURRENT_USER_ID || 0;
        this.switchSettingsSection('account');
        this.setSettingsCardSaveState('didar-write', 'idle');
        this.setSettingsCardSaveState('external-api', 'idle');
        this.setSettingsCardSaveState('service-governance', 'idle');
        this.setSettingsCardSaveState('auto-migration', 'idle');
        this.setSettingsCardSaveState('external-api', 'idle');
        this.setSettingsCardSaveState('feature-flags', 'idle');
        if (this.canCurrentUserCapabilityClient('settings.admin.didar_write.manage')) {
            this.loadDidarWriteSettings();
        }
        if (this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            this.loadExternalApiSettings();
        }
        if (this.canCurrentUserCapabilityClient('service_governance.admin.manage')) {
            this.loadServiceGovernanceSettings();
        }
        if (this.canCurrentUserCapabilityClient('settings.admin.auto_migration.manage')) {
            this.loadAutoMigrationSettings();
        }
        if (this.isAdmin()) {
            this.loadFeatureFlagSettings();
        }
    },

    switchSettingsSection(sectionKey = 'account') {
        const target = String(sectionKey || 'account');
        const sections = ['account', 'integration', 'system'];
        sections.forEach((key) => {
            const el = document.getElementById(`settings-section-${key}`);
            if (!el) return;
            el.classList.toggle('hidden', key !== target);
        });
        document.querySelectorAll('[data-settings-section-btn]').forEach((btn) => {
            const key = String(btn.getAttribute('data-settings-section-btn') || '');
            const active = key === target;
            btn.classList.toggle('bg-yellow-500/20', active);
            btn.classList.toggle('border-yellow-500/60', active);
            btn.classList.toggle('text-yellow-300', active);
            btn.classList.toggle('bg-gray-800/60', !active);
            btn.classList.toggle('border-gray-700', !active);
            btn.classList.toggle('text-gray-200', !active);
        });
    },

    setSettingsCardSaveState(cardKey, state = 'idle', message = '') {
        const badge = document.getElementById(`settings-badge-${cardKey}`);
        if (!badge) return;
        const labels = {
            idle: 'بدون تغییر',
            pending: 'در حال ذخیره',
            saved: 'ذخیره شد',
            failed: 'خطا در ذخیره'
        };
        const classes = {
            idle: ['border-gray-700', 'text-gray-300', 'bg-gray-800/70'],
            pending: ['border-yellow-600/60', 'text-yellow-300', 'bg-yellow-900/20'],
            saved: ['border-green-600/60', 'text-green-300', 'bg-green-900/20'],
            failed: ['border-red-600/60', 'text-red-300', 'bg-red-900/20']
        };
        const all = [
            'border-gray-700', 'text-gray-300', 'bg-gray-800/70',
            'border-yellow-600/60', 'text-yellow-300', 'bg-yellow-900/20',
            'border-green-600/60', 'text-green-300', 'bg-green-900/20',
            'border-red-600/60', 'text-red-300', 'bg-red-900/20'
        ];
        all.forEach((cn) => badge.classList.remove(cn));
        (classes[state] || classes.idle).forEach((cn) => badge.classList.add(cn));
        badge.textContent = message || labels[state] || labels.idle;
    },

    loadFeatureFlagSettings() {
        if (!this.isAdmin()) return;
        const historyCheckbox = document.getElementById('settings-feature-history-enabled');
        const freshnessCheckbox = document.getElementById('settings-feature-freshness-enabled');
        const status = document.getElementById('settings-feature-flags-status');
        if (historyCheckbox) historyCheckbox.checked = this.featureFlags.historyV1 === true;
        if (freshnessCheckbox) freshnessCheckbox.checked = this.featureFlags.modalFreshnessV1 !== false;
        if (status) {
            status.textContent = `مسیریاب تاریخچه: ${this.featureFlags.historyV1 ? 'فعال' : 'غیرفعال'} | محافظ تازگی مودال: ${this.featureFlags.modalFreshnessV1 ? 'فعال' : 'غیرفعال'}`;
        }
    },

    async saveFeatureFlagSettings() {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('feature-flags', 'pending');
        try {
            const historyCheckbox = document.getElementById('settings-feature-history-enabled');
            const freshnessCheckbox = document.getElementById('settings-feature-freshness-enabled');
            const nextHistory = !!historyCheckbox?.checked;
            const nextFreshness = !!freshnessCheckbox?.checked;
            const prevHistory = this.featureFlags.historyV1 === true;
            const prevFreshness = this.featureFlags.modalFreshnessV1 !== false;

            this.featureFlags.historyV1 = nextHistory;
            this.featureFlags.modalFreshnessV1 = nextFreshness;
            window.FEATURE_SPA_HISTORY_V1 = this.featureFlags.historyV1;
            window.FEATURE_MODAL_FRESHNESS_V1 = this.featureFlags.modalFreshnessV1;
            const persistResult = this.persistFeatureFlags();
            if (!persistResult.ok) {
                this.featureFlags.historyV1 = prevHistory;
                this.featureFlags.modalFreshnessV1 = prevFreshness;
                window.FEATURE_SPA_HISTORY_V1 = prevHistory;
                window.FEATURE_MODAL_FRESHNESS_V1 = prevFreshness;
                this.setSettingsCardSaveState('feature-flags', 'failed');
                Swal.fire('خطا', 'ذخیره در مرورگر ممکن نشد. احتمالاً دسترسی به localStorage مسدود است.', 'error');
                return;
            }

            if (!prevHistory && nextHistory) {
                this.bindHistoryRouter();
                this.bindHistoryUiSync();
                this.syncHistoryState(this.buildHistoryState({
                    view: this.normalizeView(this.getCurrentView()),
                    modal: null,
                    ui: this.collectHistoryUiState(this.normalizeView(this.getCurrentView()))
                }), { replace: true, source: 'settings_enable_history' });
            }

            this.logTelemetry('flags_updated_from_settings', {
                history_v1_prev: prevHistory,
                history_v1_next: nextHistory,
                modal_freshness_prev: prevFreshness,
                modal_freshness_next: nextFreshness
            });

            this.loadFeatureFlagSettings();
            this.setSettingsCardSaveState('feature-flags', 'saved');
            Swal.fire({
                toast: true,
                position: 'bottom-end',
                icon: 'success',
                title: 'فلگ‌های سیستمی ذخیره شد',
                showConfirmButton: false,
                timer: 2200
            });
        } catch (error) {
            this.setSettingsCardSaveState('feature-flags', 'failed');
            Swal.fire('خطا', 'خطا در ذخیره فلگ‌های سیستمی', 'error');
        }
    },

    async loadDidarWriteSettings() {
        try {
            const res = await this.req('get_didar_write_settings');
            if (res.status !== 'success') return;
            const enabled = String(res.didar_write_enabled || '1') === '1';
            const checkbox = document.getElementById('settings-didar-write-enabled');
            const status = document.getElementById('settings-didar-write-status');
            if (checkbox) checkbox.checked = enabled;
            if (status) {
                status.textContent = enabled
                    ? 'ارسال به CRM خارجی فعال است.'
                    : 'ارسال به CRM خارجی غیرفعال است.';
            }
        } catch (e) {
            console.warn('Failed to load Didar write settings', e);
        }
    },

    async saveDidarWriteSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.didar_write.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('didar-write', 'pending');
        try {
            const checkbox = document.getElementById('settings-didar-write-enabled');
            const enabled = checkbox ? (checkbox.checked ? '1' : '0') : '1';
            const res = await this.req('save_didar_write_settings', { didar_write_enabled: enabled });
            if (res.status === 'success') {
                const status = document.getElementById('settings-didar-write-status');
                if (status) {
                    status.textContent = enabled === '1'
                        ? 'ارسال به CRM خارجی فعال است.'
                        : 'ارسال به CRM خارجی غیرفعال است.';
                }
                this.setSettingsCardSaveState('didar-write', 'saved');
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'تنظیمات ذخیره شد', showConfirmButton:false, timer:2000});
            } else {
                this.setSettingsCardSaveState('didar-write', 'failed');
                Swal.fire('خطا', res.message || 'ذخیره تنظیمات با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('didar-write', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ذخیره تنظیمات', 'error');
        }
    },

    async loadExternalApiSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) return;
        try {
            const res = await this.req('get_external_api_settings');
            if (res.status !== 'success') return;
            const enabled = String(res.external_api_enabled || '0') === '1';
            const enabledEl = document.getElementById('settings-external-api-enabled');
            const endpointEl = document.getElementById('settings-external-api-endpoint');
            const maskedEl = document.getElementById('settings-external-api-token-masked');
            const statusEl = document.getElementById('settings-external-api-status');
            const newTokenBox = document.getElementById('settings-external-api-new-token-box');
            const passwordEl = document.getElementById('settings-external-api-admin-password');
            if (enabledEl) enabledEl.checked = enabled;
            if (endpointEl) endpointEl.value = res.endpoint || '/?api=get_lead_assignment';
            if (maskedEl) maskedEl.value = res.token_masked || '';
            if (newTokenBox) newTokenBox.classList.add('hidden');
            if (passwordEl) passwordEl.value = '';
            if (statusEl) {
                statusEl.textContent = res.token_configured
                    ? (enabled ? 'API فعال است و کلید تنظیم شده است.' : 'کلید تنظیم شده ولی API غیرفعال است.')
                    : 'هنوز کلیدی برای API ساخته نشده است.';
            }
        } catch (error) {
            console.warn('Failed to load external API settings', error);
        }
    },

    async saveExternalApiSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const enabledEl = document.getElementById('settings-external-api-enabled');
            const enabled = enabledEl && enabledEl.checked ? '1' : '0';
            const res = await this.req('save_external_api_settings', { external_api_enabled: enabled });
            if (res.status === 'success') {
                this.setSettingsCardSaveState('external-api', 'saved');
                this.loadExternalApiSettings();
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'تنظیمات API ذخیره شد', showConfirmButton:false, timer:2000});
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'ذخیره تنظیمات API با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ذخیره تنظیمات API', 'error');
        }
    },

    async generateExternalApiToken() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        const passwordEl = document.getElementById('settings-external-api-admin-password');
        const password = passwordEl ? String(passwordEl.value || '') : '';
        if (!password) {
            Swal.fire('خطا', 'رمز عبور ادمین الزامی است', 'error');
            return;
        }
        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const res = await this.req('generate_external_api_token', { current_admin_password: password });
            if (res.status === 'success') {
                const tokenEl = document.getElementById('settings-external-api-new-token');
                const boxEl = document.getElementById('settings-external-api-new-token-box');
                if (tokenEl) tokenEl.value = res.token || '';
                if (boxEl) boxEl.classList.remove('hidden');
                this.setSettingsCardSaveState('external-api', 'saved');
                this.loadExternalApiSettings();
                if (boxEl) boxEl.classList.remove('hidden');
                if (tokenEl) tokenEl.value = res.token || '';
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'کلید API ساخته شد', showConfirmButton:false, timer:2500});
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'ساخت کلید با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ساخت کلید', 'error');
        }
    },

    async revokeExternalApiToken() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        const passwordEl = document.getElementById('settings-external-api-admin-password');
        const password = passwordEl ? String(passwordEl.value || '') : '';
        if (!password) {
            Swal.fire('خطا', 'رمز عبور ادمین الزامی است', 'error');
            return;
        }
        const confirm = await Swal.fire({
            title: 'حذف کلید API؟',
            text: 'بعد از حذف، اتصال اپ‌های خارجی تا ساخت کلید جدید قطع می‌شود.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف کلید',
            cancelButtonText: 'انصراف'
        });
        if (!confirm.isConfirmed) return;

        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const res = await this.req('revoke_external_api_token', { current_admin_password: password });
            if (res.status === 'success') {
                this.setSettingsCardSaveState('external-api', 'saved');
                this.loadExternalApiSettings();
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'کلید API حذف شد', showConfirmButton:false, timer:2500});
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'حذف کلید با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام حذف کلید', 'error');
        }
    },

    async copyExternalApiToken() {
        const tokenEl = document.getElementById('settings-external-api-new-token');
        const token = tokenEl ? String(tokenEl.value || '') : '';
        if (!token) return;
        try {
            await navigator.clipboard.writeText(token);
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'کلید کپی شد', showConfirmButton:false, timer:1600});
        } catch (error) {
            if (tokenEl) {
                tokenEl.focus();
                tokenEl.select();
            }
            Swal.fire('خطا', 'کپی خودکار ممکن نشد؛ متن کلید انتخاب شد.', 'warning');
        }
    },

    async loadServiceGovernanceSettings() {
        if (!this.canCurrentUserCapabilityClient('service_governance.admin.manage')) return;
        try {
            const res = await this.req('get_service_governance_settings');
            if (res.status !== 'success') return;
            const settings = res.settings || {};
            const ownerEl = document.getElementById('settings-app-default-owner');
            const sourceEl = document.getElementById('settings-app-source-code');
            const strategyEl = document.getElementById('settings-vip-effective-deal-strategy');
            const statusEl = document.getElementById('settings-service-governance-status');
            if (ownerEl) {
                ownerEl.innerHTML = '<option value="">-- انتخاب کاربر --</option>';
                (res.users || []).forEach((user) => {
                    if (!user || !user.didar_user_id) return;
                    const opt = document.createElement('option');
                    opt.value = user.didar_user_id;
                    opt.textContent = `${user.display_name || user.email || user.didar_user_id}${user.is_active ? '' : ' (غیرفعال)'}`;
                    ownerEl.appendChild(opt);
                });
                ownerEl.value = settings.app_default_owner_didar_id || '';
            }
            if (sourceEl) sourceEl.value = settings.app_source_code || 'mobile_app';
            if (strategyEl) strategyEl.value = settings.vip_effective_deal_strategy || 'latest_vip_deal';
            if (statusEl) {
                statusEl.textContent = settings.app_default_owner_didar_id
                    ? 'مسئول پیش‌فرض اپ تنظیم شده است.'
                    : 'برای ساخت لیدهای جدید از اپ، مسئول پیش‌فرض باید انتخاب شود.';
            }
        } catch (error) {
            console.warn('Failed to load service governance settings', error);
        }
    },

    async saveServiceGovernanceSettings() {
        if (!this.canCurrentUserCapabilityClient('service_governance.admin.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('service-governance', 'pending');
        try {
            const ownerEl = document.getElementById('settings-app-default-owner');
            const sourceEl = document.getElementById('settings-app-source-code');
            const strategyEl = document.getElementById('settings-vip-effective-deal-strategy');
            const res = await this.req('save_service_governance_settings', {
                app_default_owner_didar_id: ownerEl ? ownerEl.value : '',
                app_source_code: sourceEl ? sourceEl.value : 'mobile_app',
                vip_effective_deal_strategy: strategyEl ? strategyEl.value : 'latest_vip_deal'
            });
            if (res.status === 'success') {
                this.setSettingsCardSaveState('service-governance', 'saved');
                this.loadServiceGovernanceSettings();
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'تنظیمات اپ ذخیره شد', showConfirmButton:false, timer:2000});
            } else {
                this.setSettingsCardSaveState('service-governance', 'failed');
                Swal.fire('خطا', res.message || 'ذخیره تنظیمات اپ با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('service-governance', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ذخیره تنظیمات اپ', 'error');
        }
    },

    async loadAutoMigrationSettings() {
        try {
            const res = await this.req('get_auto_migration_settings');
            if (res.status !== 'success') return;
            const enabled = String(res.auto_migration_enabled || '0') === '1';
            const checkbox = document.getElementById('settings-auto-migration-enabled');
            const status = document.getElementById('settings-auto-migration-status');
            if (checkbox) checkbox.checked = enabled;
            if (status) {
                status.textContent = enabled
                    ? 'مایگریشن خودکار فعال است (ممکن است روی سرعت بارگذاری اثر بگذارد).'
                    : 'مایگریشن خودکار غیرفعال است (پیشنهادی برای کار عادی).';
            }
        } catch (e) {
            console.warn('Failed to load auto migration settings', e);
        }
    },

    async saveAutoMigrationSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.auto_migration.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('auto-migration', 'pending');
        try {
            const checkbox = document.getElementById('settings-auto-migration-enabled');
            const enabled = checkbox ? (checkbox.checked ? '1' : '0') : '0';
            const res = await this.req('save_auto_migration_settings', { auto_migration_enabled: enabled });
            if (res.status === 'success') {
                const status = document.getElementById('settings-auto-migration-status');
                if (status) {
                    status.textContent = enabled === '1'
                        ? 'مایگریشن خودکار فعال است (ممکن است روی سرعت بارگذاری اثر بگذارد).'
                        : 'مایگریشن خودکار غیرفعال است (پیشنهادی برای کار عادی).';
                }
                this.setSettingsCardSaveState('auto-migration', 'saved');
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'تنظیمات ذخیره شد', showConfirmButton:false, timer:2000});
            } else {
                this.setSettingsCardSaveState('auto-migration', 'failed');
                Swal.fire('خطا', res.message || 'ذخیره تنظیمات با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('auto-migration', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ذخیره تنظیمات', 'error');
        }
    },

    async loadExternalApiSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) return;
        try {
            const res = await this.req('get_external_api_settings');
            if (res.status !== 'success') return;
            const enabled = String(res.external_api_enabled || '0') === '1';
            const checkbox = document.getElementById('settings-external-api-enabled');
            const tokenInput = document.getElementById('settings-external-api-token');
            const endpointInput = document.getElementById('settings-external-api-endpoint');
            const status = document.getElementById('settings-external-api-status');
            if (checkbox) checkbox.checked = enabled;
            if (tokenInput) tokenInput.value = res.token_masked || '';
            if (endpointInput && res.endpoint) endpointInput.value = res.endpoint;
            if (status) {
                status.textContent = res.token_configured
                    ? (enabled ? 'API خارجی فعال است و API key تنظیم شده است.' : 'API key موجود است ولی API خارجی غیرفعال است.')
                    : 'هنوز API key ساخته نشده است.';
            }
        } catch (e) {
            console.warn('Failed to load external API settings', e);
        }
    },

    async saveExternalApiSettings() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const checkbox = document.getElementById('settings-external-api-enabled');
            const enabled = checkbox ? (checkbox.checked ? '1' : '0') : '0';
            const res = await this.req('save_external_api_settings', { external_api_enabled: enabled });
            if (res.status === 'success') {
                const tokenInput = document.getElementById('settings-external-api-token');
                const status = document.getElementById('settings-external-api-status');
                if (tokenInput) tokenInput.value = res.token_masked || '';
                if (status) {
                    status.textContent = res.token_configured
                        ? (enabled === '1' ? 'API خارجی فعال است و API key تنظیم شده است.' : 'API key موجود است ولی API خارجی غیرفعال است.')
                        : (enabled === '1' ? 'API خارجی فعال شد اما هنوز API key ساخته نشده است.' : 'API خارجی غیرفعال است و API key هم تنظیم نشده است.');
                }
                this.setSettingsCardSaveState('external-api', 'saved');
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'تنظیمات API خارجی ذخیره شد', showConfirmButton:false, timer:2200});
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'ذخیره تنظیمات API خارجی با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ذخیره تنظیمات API خارجی', 'error');
        }
    },

    async generateExternalApiToken() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        const passwordInput = document.getElementById('settings-external-api-admin-password');
        const current_admin_password = passwordInput ? String(passwordInput.value || '').trim() : '';
        if (!current_admin_password) {
            Swal.fire('خطا', 'رمز عبور مدیر را وارد کنید', 'error');
            return;
        }

        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const res = await this.req('generate_external_api_token', { current_admin_password });
            if (res.status === 'success') {
                const checkbox = document.getElementById('settings-external-api-enabled');
                const tokenInput = document.getElementById('settings-external-api-token');
                const endpointInput = document.getElementById('settings-external-api-endpoint');
                const status = document.getElementById('settings-external-api-status');
                if (checkbox) checkbox.checked = true;
                if (tokenInput) tokenInput.value = res.token || res.token_masked || '';
                if (endpointInput && res.endpoint) endpointInput.value = res.endpoint;
                if (status) {
                    status.textContent = 'API key جدید ساخته شد. همین حالا آن را در اپ مقصد ثبت کنید؛ بعد از رفرش فقط نسخه masked نمایش داده می‌شود.';
                }
                if (passwordInput) passwordInput.value = '';
                this.setSettingsCardSaveState('external-api', 'saved');
                Swal.fire({
                    icon: 'success',
                    title: 'API key جدید ساخته شد',
                    html: `<div class="text-left"><p class="mb-2 text-sm">این مقدار را همین حالا کپی کنید:</p><textarea class="swal2-textarea" style="width:100%;height:110px;font-family:monospace;">${res.token || ''}</textarea></div>`,
                    confirmButtonText: 'بستن'
                });
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'ایجاد API key با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام ایجاد API key', 'error');
        }
    },

    async revokeExternalApiToken() {
        if (!this.canCurrentUserCapabilityClient('settings.admin.external_api.manage')) {
            Swal.fire('خطا', 'دسترسی محدود به مدیر سیستم', 'error');
            return;
        }
        const passwordInput = document.getElementById('settings-external-api-admin-password');
        const current_admin_password = passwordInput ? String(passwordInput.value || '').trim() : '';
        if (!current_admin_password) {
            Swal.fire('خطا', 'رمز عبور مدیر را وارد کنید', 'error');
            return;
        }

        const confirmation = await Swal.fire({
            title: 'حذف API key؟',
            text: 'بعد از این کار، دسترسی اپ‌های دیگر به API خارجی قطع می‌شود.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'انصراف'
        });
        if (!confirmation.isConfirmed) return;

        this.setSettingsCardSaveState('external-api', 'pending');
        try {
            const res = await this.req('revoke_external_api_token', { current_admin_password });
            if (res.status === 'success') {
                const checkbox = document.getElementById('settings-external-api-enabled');
                const tokenInput = document.getElementById('settings-external-api-token');
                const status = document.getElementById('settings-external-api-status');
                if (checkbox) checkbox.checked = false;
                if (tokenInput) tokenInput.value = '';
                if (status) status.textContent = 'API key حذف شد و API خارجی غیرفعال است.';
                if (passwordInput) passwordInput.value = '';
                this.setSettingsCardSaveState('external-api', 'saved');
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'API key حذف شد', showConfirmButton:false, timer:2200});
            } else {
                this.setSettingsCardSaveState('external-api', 'failed');
                Swal.fire('خطا', res.message || 'حذف API key با خطا مواجه شد', 'error');
            }
        } catch (error) {
            this.setSettingsCardSaveState('external-api', 'failed');
            Swal.fire('خطا', 'خطا در ارتباط با سرور هنگام حذف API key', 'error');
        }
    },

    // REFERRALS
    async loadReferrals() {
        try {
            const res = await this.req('get_referrals');
            if (res.status === 'success') {
                this.state.referrals = res.referrals || [];
                this.renderReferrals();
            }
        } catch (e) {
            console.warn('Failed to load referrals', e);
            this.state.referrals = [];
            this.renderReferrals();
        }
    },

    renderReferrals() {
        const tbody = document.getElementById('referrals-list');
        if (!tbody) return;

        if (this.state.referrals.length === 0) {
            tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-gray-500">هیچ ارجاعی یافت نشد</td></tr>';
            return;
        }

        tbody.innerHTML = this.state.referrals.map(r => `
            <tr class="hover:bg-gray-800/30">
                <td class="p-3">
                    <div class="text-sm text-gray-200">${r.first_name || ''} ${r.last_name || ''}</div>
                    <div class="text-xs text-gray-500 mt-1">${r.mobile_phone || ''}</div>
                </td>
                <td class="p-3 text-xs text-gray-300">${r.phones ? JSON.parse(r.phones).join(' ، ') : ''}</td>
                <td class="p-3 text-xs text-gray-300">${r.referrer_name || '---'}</td>
                <td class="p-3 text-xs text-gray-400">${r.created_at ? new Date(r.created_at).toLocaleString('fa-IR') : ''}</td>
                <td class="p-3 text-xs text-gray-300 whitespace-pre-wrap">${r.note || ''}</td>
            </tr>
        `).join('');
    },
    
    // DASHBOARD
    async loadDashboard() {
        const ownerFilterWrap = document.getElementById('dashboard-owner-filter-wrap');
        if (ownerFilterWrap) {
            ownerFilterWrap.classList.toggle('hidden', !this.hasCrossUserScopeClient('dashboard'));
        }
        const ownerFilter = document.getElementById('dashboard-owner-filter')?.value || '';
        const statusFilter = document.getElementById('dashboard-status-filter')?.value || 'all';

        const res = await this.req('get_dashboard_stats', {
            owner_didar_id: ownerFilter,
            status: statusFilter
        });
        if(res.status === 'success') {
            const stats = res.stats;
            const content = document.getElementById('dashboard-content');
            const inactiveStageDeals = Array.isArray(stats.inactive_stage_deals) ? stats.inactive_stage_deals : [];
            const parkedStageDeals = Array.isArray(stats.parked_stage_deals)
                ? stats.parked_stage_deals
                : Object.entries(stats.parked_deals_by_stage || {}).map(([stageValue, count]) => ({
                    value: stageValue,
                    title: stageValue,
                    count: Number(count || 0)
                })).filter(item => item.value && item.count > 0);
            
            content.innerHTML = `
                <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-6">
                    <div class="glass p-4 border-r-4 border-blue-500 cursor-pointer hover:bg-gray-800/50 transition" onclick="app.goToLeadsWithFilter('all')">
                        <div class="text-sm text-gray-400 mb-1">کل لید</div>
                        <div class="text-2xl font-bold text-white">${stats.total_leads || stats.total_persons || 0}</div>
                        </div>
                    <div class="glass p-4 border-r-4 border-green-500 cursor-pointer hover:bg-gray-800/50 transition" onclick="app.goToLeadsWithFilter('success')">
                        <div class="text-sm text-gray-400 mb-1">لید های موفق</div>
                        <div class="text-2xl font-bold text-white">${stats.successful_leads || 0}</div>
                    </div>
                    <div class="glass p-4 border-r-4 border-red-500 cursor-pointer hover:bg-gray-800/50 transition" onclick="app.goToLeadsWithFilter('failed')">
                        <div class="text-sm text-gray-400 mb-1">لید های ناموفق</div>
                        <div class="text-2xl font-bold text-white">${stats.failed_leads || 0}</div>
                    </div>
                </div>
                
                <div class="glass p-6">
                    <h3 class="text-lg font-bold mb-4 text-yellow-500">کاریز معاملات</h3>
                    <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-7 gap-3">
                        ${this.state.pipelineStages.map(stage => `
                            <div class="text-center p-3 bg-gray-800 rounded cursor-pointer hover:bg-gray-700 transition relative" onclick="app.goToDealsWithFilter('${stage}')">
                                ${this.isAdmin() ? `
                                <button type="button"
                                    class="absolute top-2 left-2 w-7 h-7 rounded-lg border border-amber-500/30 bg-amber-500/10 text-amber-300 hover:bg-amber-500/20 transition flex items-center justify-center"
                                    title="انتقال همه معاملات این مرحله به پارک‌شده"
                                    onclick="event.stopPropagation(); app.moveStageDealsToParked('${this.escapeHtml(stage)}', ${(stats.deals_by_stage && stats.deals_by_stage[stage]) ? stats.deals_by_stage[stage] : 0})">
                                    <i class="fa-solid fa-box-archive text-[10px]"></i>
                                </button>
                                ` : ''}
                                <div class="text-xs text-gray-400 mb-1 ${this.isAdmin() ? 'pl-8' : ''}">${stage}</div>
                                <div class="text-xl font-bold text-white">${(stats.deals_by_stage && stats.deals_by_stage[stage]) ? stats.deals_by_stage[stage] : 0}</div>
                            </div>
                        `).join('')}
                    </div>
                    <div class="mt-4 text-xs text-gray-400">کل جاری (Pending): ${stats.ongoing_deals || 0}</div>
                </div>
                ${inactiveStageDeals.length ? `
                <div class="glass p-6 mt-6">
                    <div class="flex items-center justify-between gap-3 mb-4">
                        <h3 class="text-lg font-bold text-yellow-500">مراحل غیرفعال کاریز</h3>
                        <div class="text-xs text-gray-400">مجموع معاملات: ${this.formatNumber(stats.inactive_stage_deals_total || 0)}</div>
                    </div>
                    <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-7 gap-3">
                        ${inactiveStageDeals.map(item => `
                            <div class="text-center p-3 bg-gray-800 rounded cursor-pointer hover:bg-gray-700 transition border border-transparent hover:border-yellow-500/30" onclick="app.goToDealsWithFilter('${this.escapeHtml(item.value)}', '${this.escapeHtml(item.title || item.value)}')">
                                <div class="text-xs text-gray-400 mb-1">${this.escapeHtml(item.title || item.value)}</div>
                                <div class="text-xl font-bold text-white">${this.formatNumber(item.count || 0)}</div>
                            </div>
                        `).join('')}
                    </div>
                </div>
                ` : ''}
                ${parkedStageDeals.length ? `
                <div class="glass p-6 mt-6">
                    <div class="flex items-center justify-between gap-3 mb-4">
                        <h3 class="text-lg font-bold text-amber-400">معاملات پارک‌شده</h3>
                        <div class="flex items-center gap-3">
                            <div class="text-xs text-gray-400">مجموع: ${this.formatNumber(stats.parked_stage_deals_total || 0)}</div>
                            ${this.isAdmin() ? `
                            <button type="button"
                                onclick="app.unparkAllDeals('${ownerFilter}', '', ${stats.parked_stage_deals_total || 0})"
                                class="flex items-center gap-1.5 px-3 py-1 text-xs rounded-lg border border-green-500/40 bg-green-500/10 text-green-300 hover:bg-green-500/20 transition">
                                <i class="fa-solid fa-box-open"></i>بازگردانی همه
                            </button>
                            ` : ''}
                        </div>
                    </div>
                    <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-7 gap-3">
                        ${parkedStageDeals.map(item => `
                            <div class="text-center p-3 bg-gray-800 rounded cursor-pointer hover:bg-gray-700 transition border border-transparent hover:border-amber-500/30 relative" onclick="app.goToDealsWithFilter('${this.escapeHtml(item.value)}', '${this.escapeHtml(item.title || item.value)}', { pipelineBucket: 'parked' })">
                                ${this.isAdmin() ? `
                                <button type="button"
                                    class="absolute top-2 left-2 w-7 h-7 rounded-lg border border-green-500/30 bg-green-500/10 text-green-300 hover:bg-green-500/20 transition flex items-center justify-center"
                                    title="بازگردانی همه پارک‌شده‌های این مرحله"
                                    onclick="event.stopPropagation(); app.moveStageDealsOutOfParked('${this.escapeHtml(item.value)}', ${item.count || 0})">
                                    <i class="fa-solid fa-box-open text-[10px]"></i>
                                </button>
                                ` : ''}
                                <div class="text-xs text-gray-400 mb-1 ${this.isAdmin() ? 'pl-8' : ''}">${this.escapeHtml(item.title || item.value)}</div>
                                <div class="text-xl font-bold text-white">${this.formatNumber(item.count || 0)}</div>
                            </div>
                        `).join('')}
                    </div>
                    ${this.isAdmin() && Array.isArray(stats.parked_by_user) && stats.parked_by_user.length > 1 && !ownerFilter ? `
                    <div class="mt-4 pt-4 border-t border-gray-700/60">
                        <div class="text-xs text-gray-500 mb-2">بازگردانی به تفکیک کارشناس:</div>
                        <div class="flex flex-wrap gap-2">
                            ${stats.parked_by_user.map(u => `
                                <button type="button"
                                    onclick="app.unparkAllDeals('${this.escapeHtml(u.didar_id)}', '${this.escapeHtml(u.owner_name || u.didar_id)}', ${u.count || 0})"
                                    class="flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-lg border border-amber-500/30 bg-amber-500/10 text-amber-200 hover:bg-amber-500/20 transition">
                                    <i class="fa-solid fa-user text-[9px]"></i>
                                    <span>${this.escapeHtml(u.owner_name || u.didar_id)}</span>
                                    <span class="bg-amber-500/20 text-amber-300 rounded px-1">${this.formatNumber(u.count)}</span>
                                </button>
                            `).join('')}
                        </div>
                    </div>
                    ` : ''}
                </div>
                ` : ''}
            `;
        }
    },

    async moveStageDealsToParked(stage, currentCount = 0) {
        if (!this.canCurrentUserCapabilityClient('deals.admin.parking')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند انتقال گروهی انجام دهد', 'error');
            return;
        }

        const ownerFilter = document.getElementById('dashboard-owner-filter')?.value || '';
        const stageLabel = String(stage || '').trim();
        if (!stageLabel) {
            Swal.fire('خطا', 'مرحله کاریز مشخص نشده است', 'error');
            return;
        }

        const { value: reason } = await Swal.fire({
            title: 'انتقال گروهی به پارک‌شده',
            html: `
                <div class="text-right text-sm text-gray-300 leading-7">
                    <div>مرحله انتخاب‌شده: <span class="text-white font-bold">${this.escapeHtml(stageLabel)}</span></div>
                    <div>تعداد فعلی روی داشبورد: <span class="text-amber-300 font-bold">${this.formatNumber(currentCount || 0)}</span></div>
                    ${ownerFilter ? '<div class="text-xs text-gray-400 mt-2">این انتقال فقط روی فیلتر مالک فعلی اعمال می‌شود.</div>' : ''}
                </div>
            `,
            input: 'textarea',
            inputLabel: 'توضیح (اختیاری)',
            inputPlaceholder: 'در صورت نیاز دلیل انتقال گروهی را بنویسید...',
            showCancelButton: true,
            confirmButtonText: 'انتقال همه',
            cancelButtonText: 'لغو'
        });

        if (reason === undefined) {
            return;
        }

        const res = await this.req('deal/move_stage_to_parked', {
            pipeline_stage: stageLabel,
            owner_didar_id: ownerFilter,
            parked_reason: reason || ''
        });

        if (res.status === 'success') {
            Swal.fire({
                icon: 'success',
                title: 'انجام شد',
                text: `${this.formatNumber(res.moved_count || 0)} معامله از مرحله «${stageLabel}» به پارک‌شده منتقل شد`,
                confirmButtonText: 'باشه'
            });
            await this.loadDashboard();
            if (document.getElementById('deals-container')) {
                this.loadDeals();
            }
        } else {
            Swal.fire('خطا', res.message || 'خطا در انتقال گروهی معاملات', 'error');
        }
    },
    
    async moveStageDealsOutOfParked(stage, currentCount = 0) {
        if (!this.canCurrentUserCapabilityClient('deals.admin.parking')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند بازگردانی گروهی انجام دهد', 'error');
            return;
        }
        const ownerFilter = document.getElementById('dashboard-owner-filter')?.value || '';
        const stageLabel = String(stage || '').trim();
        if (!stageLabel) {
            Swal.fire('خطا', 'مرحله پارک‌شده مشخص نشده است', 'error');
            return;
        }
        const confirm = await Swal.fire({
            title: 'بازگردانی گروهی از پارک',
            html: `
                <div class="text-right text-sm text-gray-300 leading-7">
                    <div>مرحله: <span class="text-white font-bold">${this.escapeHtml(stageLabel)}</span></div>
                    <div>معاملات پارک‌شده این مرحله: <span class="text-amber-300 font-bold">${this.formatNumber(currentCount || 0)}</span></div>
                    ${ownerFilter ? '<div class="text-xs text-gray-400 mt-2">فقط روی فیلتر مالک فعلی اعمال می‌شود.</div>' : ''}
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'بازگردانی همه',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#22c55e',
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('deal/move_stage_out_of_parked', {
            pipeline_stage: stageLabel,
            owner_didar_id: ownerFilter,
        });
        if (res.status === 'success') {
            Swal.fire({
                icon: 'success',
                title: 'انجام شد',
                text: `${this.formatNumber(res.moved_count || 0)} معامله از پارک به مرحله «${stageLabel}» بازگردانده شد`,
                confirmButtonText: 'باشه'
            });
            await this.loadDashboard();
            if (document.getElementById('deals-container')) this.loadDeals();
        } else {
            Swal.fire('خطا', res.message || 'خطا در بازگردانی گروهی معاملات', 'error');
        }
    },

    async unparkAllDeals(ownerId = '', ownerName = '', totalCount = 0) {
        if (!this.canCurrentUserCapabilityClient('deals.admin.parking')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند این عملیات را انجام دهد', 'error');
            return;
        }
        const ownerLabel = ownerName || (ownerId ? ownerId : 'همه کارشناسان');
        const confirm = await Swal.fire({
            title: 'بازگردانی همه معاملات پارک‌شده',
            html: `
                <div class="text-right text-sm text-gray-300 leading-7">
                    <div>کارشناس: <span class="text-white font-bold">${this.escapeHtml(String(ownerLabel))}</span></div>
                    <div>تعداد کل: <span class="text-amber-300 font-bold">${this.formatNumber(totalCount || 0)}</span></div>
                    <div class="text-xs text-yellow-400 mt-2">تمام معاملات پارک‌شده به مرحله اصلی‌شان بازمی‌گردند.</div>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'بازگردانی همه',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#22c55e',
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('deal/unpark_all', {
            owner_didar_id: ownerId || '',
        });
        if (res.status === 'success') {
            Swal.fire({
                icon: 'success',
                title: 'انجام شد',
                text: `${this.formatNumber(res.moved_count || 0)} معامله از پارک به کاریز بازگردانده شد`,
                confirmButtonText: 'باشه'
            });
            await this.loadDashboard();
            if (document.getElementById('deals-container')) this.loadDeals();
        } else {
            Swal.fire('خطا', res.message || 'خطا در بازگردانی معاملات', 'error');
        }
    },

    // ACTIVITY TYPES
    async loadActivityTypes() {
        const res = await this.req('get_activity_types');
        if(res.status === 'success') {
            this.state.activityTypes = res.types || [];
        }
    },

    async loadProducts() {
        try {
            const res = await this.req('get_products');
            if (res.status === 'success') {
                this.state.products = res.products || [];
                this.renderProductOptions();
                this.renderLeadProductFilter();
                this.renderDealTitleOptions();
                if (this.state.lookupSelectedGroup === '__products_manager__') this.renderLookupProductsManagerTable();
            }
        } catch (e) {
            console.error('Failed to load products', e);
        }
    },
    
	    async loadDidarUsers(resourceKey = '') {
	        try {
	            console.log('[DEBUG] Starting loadDidarUsers...');
	            const resolvedResourceKey = this.getPreferredDidarUsersResourceKey(resourceKey);
	            const res = await this.req('get_didar_users', { resource_key: resolvedResourceKey });
	            console.log('[DEBUG] get_didar_users response:', res);
	            
	            if(res.status === 'success') {
	                this.state.didarUsers = res.users || [];
	                console.log('[DEBUG] External CRM users loaded successfully:', this.state.didarUsers.length, 'users');
	                console.log('[DEBUG] Users data:', JSON.stringify(this.state.didarUsers, null, 2));
	                
	                this.populateLeadOwnerSelect();
	                this.populateOwnerFilterSelects();
	                
	                // اگر لیست خالی است، به کاربر اطلاع بده
	                if (this.state.didarUsers.length === 0) {
	                    console.warn('[DEBUG] No External CRM users found. This might indicate an issue with API or all users are inactive.');
	                }
	            } else {
	                console.warn('[DEBUG] External CRM users list unavailable:', {
	                    resource_key: resolvedResourceKey,
	                    message: res.message || 'Unknown error',
	                    status: res.status || ''
	                });
	                this.setDidarUsersFallbackToCurrentUser();
	                this.populateLeadOwnerSelect();
	                this.populateOwnerFilterSelects();
	            }
	        } catch (error) {
	            console.error('[DEBUG] Error loading External CRM users:', error);
	            this.setDidarUsersFallbackToCurrentUser();
	            this.populateLeadOwnerSelect();
	            this.populateOwnerFilterSelects();
	        }
	    },

    async loadReminders() {
        const container = document.getElementById('reminders-list-container');
        if (!container) return;

        container.innerHTML = '<div class="text-center py-20 col-span-full"><div class="loading-spinner mb-4 mx-auto"></div><p class="text-gray-400">در حال بارگذاری معاملات گذشته...</p></div>';

        try {
            const res = await this.req('get_reminders_list');
            if (res.status === 'success') {
                const reminders = res.reminders || [];
                if (reminders.length === 0) {
                    container.innerHTML = '<div class="text-center py-20 col-span-full"><i class="fa-solid fa-clock-rotate-left text-4xl text-gray-700 mb-4 block"></i><p class="text-gray-500">هیچ یادآوری فعالی یافت نشد.</p></div>';
                    return;
                }

                // Priority levels configuration
                const priorityLevels = {
                    'high': {
                        color: 'border-red-600 bg-red-500/10',
                        icon: 'fa-exclamation-triangle',
                        badge: 'bg-red-500/20 text-red-400',
                        text: 'اولویت بالا'
                    },
                    'medium': {
                        color: 'border-yellow-600 bg-yellow-500/10',
                        icon: 'fa-clock',
                        badge: 'bg-yellow-500/20 text-yellow-400',
                        text: 'اولویت متوسط'
                    },
                    'low': {
                        color: 'border-blue-600 bg-blue-500/10',
                        icon: 'fa-info-circle',
                        badge: 'bg-blue-500/20 text-blue-400',
                        text: 'اولویت پایین'
                    }
                };

                container.innerHTML = reminders.map(rem => {
                    const contactId = rem.contact_didar_id || rem.contact_id || (rem.contact ? rem.contact.didar_contact_id : '') || rem.local_person_id;
                    const isOverdue = new Date(rem.reminder_time) < new Date();
                    const statusColor = rem.reminder_sent ? 'border-gray-500' : (isOverdue ? 'border-red-500' : 'border-blue-500');
                    const statusText = rem.reminder_sent ? 'ارسال شده' : (isOverdue ? 'زمان گذشته' : 'در انتظار');
                    const badgeClass = rem.reminder_sent ? 'bg-gray-500/20 text-gray-400' : (isOverdue ? 'bg-red-500/20 text-red-400' : 'bg-blue-500/20 text-blue-400');
                    // Shared accent stripe by reminder status (mobile-only CSS; inert on desktop).
                    const remClass = rem.reminder_sent ? 's--neutral' : (isOverdue ? 's--danger' : 's--info');

                    const timeToReminder = new Date(rem.reminder_time) - new Date();
                    const hoursToReminder = timeToReminder / (1000 * 60 * 60);
                    let priority = 'low';
                    if (isOverdue || hoursToReminder < 1) priority = 'high';
                    else if (hoursToReminder < 24) priority = 'medium';

                    const priorityData = priorityLevels[priority];
                    
                    const timeRemaining = isOverdue ? 
                        `${Math.abs(Math.floor(hoursToReminder))} ساعت پیش` : 
                        `${Math.floor(hoursToReminder)} ساعت دیگر`;

                    const customerName = (rem.first_name || '') + ' ' + (rem.last_name || '').trim();
                    const avatarInitials = customerName.length > 0 ? 
                        customerName.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase() : 
                        '؟؟';

                    return `
                        <div class="glass p-5 rounded-2xl border border-gray-700/50 hover:border-blue-500/50 transition-all duration-500 group flex flex-col h-full relative overflow-hidden accent-card ${remClass}">
                            <!-- Top Status Bar -->
                            <div class="flex justify-between items-center mb-4">
                                <span class="text-[9px] px-2.5 py-1 rounded-full font-bold tracking-tight ${priorityData.badge} flex items-center gap-1.5">
                                    <i class="fa-solid ${priorityData.icon} text-[8px]"></i>
                                    ${priorityData.text}
                                </span>
                                <span class="text-[9px] px-2.5 py-1 rounded-full font-bold ${badgeClass}">
                                    ${statusText}
                                </span>
                            </div>

                            <!-- Main Content -->
                            <div class="flex items-center gap-4 mb-5">
                                <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-600/20 to-purple-600/20 border border-blue-500/20 flex items-center justify-center text-blue-400 font-black text-sm shadow-inner group-hover:scale-110 transition-transform">
                                    ${avatarInitials}
                                </div>
                                <div class="flex-1 min-w-0">
                                    <h4 class="text-white font-bold text-sm truncate mb-0.5">${this.escapeHtml(customerName || 'مشتری نامشخص')}</h4>
                                    <p class="text-[10px] text-gray-400 font-medium truncate opacity-80">${this.escapeHtml(rem.activity_title || 'فعالیت بدون عنوان')}</p>
                                </div>
                            </div>

                            <!-- Timing Info -->
                            <div class="grid grid-cols-2 gap-2 mb-5">
                                <div class="bg-gray-800/40 p-2 rounded-lg border border-gray-700/30">
                                    <span class="block text-[8px] text-gray-500 mb-0.5">زمان یادآوری</span>
                                    <span class="text-[10px] text-gray-300 font-bold font-mono">${toPersianDateTime(rem.reminder_time)}</span>
                                </div>
                                <div class="bg-gray-800/40 p-2 rounded-lg border border-gray-700/30">
                                    <span class="block text-[8px] text-gray-500 mb-0.5">وضعیت زمانی</span>
                                    <span class="text-[10px] ${isOverdue ? 'text-red-400' : 'text-green-400'} font-bold">${timeRemaining}</span>
                                </div>
                            </div>

                            <!-- Progress Section -->
                            <div class="mb-6">
                                <div class="flex justify-between text-[9px] mb-1.5 px-0.5">
                                    <span class="text-gray-500">پیشرفت فعالیت</span>
                                    <span class="text-blue-400 font-bold">${rem.reminder_sent ? '100%' : 'در جریان'}</span>
                                </div>
                                <div class="w-full bg-gray-900/50 rounded-full h-1.5 overflow-hidden border border-gray-800">
                                    <div class="bg-gradient-to-r from-blue-600 to-indigo-500 h-full rounded-full transition-all duration-700 ease-out"
                                         style="width: ${rem.reminder_sent ? '100' : Math.max(15, Math.min(85, (new Date() - new Date(rem.due_date)) / (new Date(rem.reminder_time) - new Date(rem.due_date)) * 100))}%">
                                    </div>
                                </div>
                            </div>

                            <!-- Actions Group -->
                            <div class="mt-auto space-y-3">
                                <div class="grid grid-cols-2 gap-2">
                                    <button onclick="app.openPersonProfile('${contactId}')"
                                        class="text-[10px] bg-blue-600/10 hover:bg-blue-600 text-blue-400 hover:text-white py-2 rounded-xl transition-all border border-blue-500/20 flex items-center justify-center gap-2">
                                        <i class="fa-solid fa-user-tag"></i> مشاهده لید
                                    </button>
                                    <button onclick="app.markReminderDone('${rem.activity_didar_id}')"
                                        class="text-[10px] bg-emerald-600/10 hover:bg-emerald-600 text-emerald-400 hover:text-white py-2 rounded-xl transition-all border border-emerald-500/20 flex items-center justify-center gap-2">
                                        <i class="fa-solid fa-check-circle"></i> انجام شد
                                    </button>
                                </div>
                                
                                <div class="flex justify-between items-center gap-1.5 pt-3 border-t border-gray-800">
                                    <button onclick="app.snoozeReminder('${rem.activity_didar_id}', 60)" class="flex-1 text-[9px] bg-gray-800 hover:bg-yellow-600/20 text-gray-400 hover:text-yellow-500 py-1.5 rounded-lg border border-gray-700/50 transition-colors">+۱س</button>
                                    <button onclick="app.snoozeReminder('${rem.activity_didar_id}', 240)" class="flex-1 text-[9px] bg-gray-800 hover:bg-yellow-600/20 text-gray-400 hover:text-yellow-500 py-1.5 rounded-lg border border-gray-700/50 transition-colors">+۴س</button>
                                    <button onclick="app.snoozeReminder('${rem.activity_didar_id}', 1440)" class="flex-1 text-[9px] bg-gray-800 hover:bg-yellow-600/20 text-gray-400 hover:text-yellow-500 py-1.5 rounded-lg border border-gray-700/50 transition-colors">+۱روز</button>
                                    <div class="w-px h-4 bg-gray-800 mx-1"></div>
                                    <button onclick="app.resendReminder('${rem.activity_didar_id}')" class="p-2 text-gray-500 hover:text-blue-400 transition-colors" title="ارسال مجدد">
                                        <i class="fa-solid fa-bell"></i>
                                    </button>
                                </div>
                            </div>
                        </div>
                    `;
                }).join('');
            } else {
                container.innerHTML = `<div class="text-center py-20 col-span-full text-red-400">${res.message || 'خطا در دریافت یادآوری‌ها'}</div>`;
            }
        } catch (error) {
            console.error('Reminders load failed:', error);
            container.innerHTML = '<div class="text-center py-20 col-span-full text-red-400">خطا در ارتباط با سرور</div>';
        }
    },

    // Reminder Actions
    async editReminder(activityId) {
        // TODO: Implement edit reminder functionality
        Swal.fire({
            title: 'ویرایش یادآوری',
            text: 'این قابلیت به زودی اضافه خواهد شد',
            icon: 'info',
            confirmButtonText: 'باشه'
        });
    },

    async resendReminder(activityId) {
        try {
            const result = await Swal.fire({
                title: 'ارسال مجدد اعلان',
                text: 'آیا مطمئن هستید که می‌خواهید اعلان را مجدداً ارسال کنید؟',
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: 'بله، ارسال کن',
                cancelButtonText: 'لغو'
            });

            if (result.isConfirmed) {
                const res = await this.req('resend_reminder', { activity_id: activityId });
                if (res.status === 'success') {
                    Swal.fire({
                        title: 'موفق!',
                        text: 'اعلان مجدداً ارسال شد',
                        icon: 'success',
                        timer: 2000
                    });
                    this.loadReminders();
                } else {
                    throw new Error(res.message);
                }
            }
        } catch (error) {
            console.error('Resend reminder failed:', error);
            Swal.fire({
                title: 'خطا!',
                text: error.message || 'خطا در ارسال مجدد اعلان',
                icon: 'error'
            });
        }
    },

    async markReminderDone(activityId) {
        try {
            const result = await Swal.fire({
                title: 'علامت‌گذاری به عنوان انجام شده',
                text: 'این یادآوری به عنوان انجام شده علامت‌گذاری خواهد شد',
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: 'بله، انجام شد',
                cancelButtonText: 'لغو'
            });

            if (result.isConfirmed) {
                const res = await this.req('mark_reminder_done', { activity_id: activityId });
                if (res.status === 'success') {
                    Swal.fire({
                        title: 'موفق!',
                        text: 'یادآوری به عنوان انجام شده علامت‌گذاری شد',
                        icon: 'success',
                        timer: 2000
                    });
                    this.loadReminders();
                } else {
                    throw new Error(res.message);
                }
            }
        } catch (error) {
            console.error('Mark reminder done failed:', error);
            Swal.fire({
                title: 'خطا!',
                text: error.message || 'خطا در علامت‌گذاری یادآوری',
                icon: 'error'
            });
        }
    },

    async snoozeReminder(activityId, minutes) {
        try {
            const timeLabels = {
                60: '۱ ساعت',
                240: '۴ ساعت',
                1440: '۱ روز'
            };

            const result = await Swal.fire({
                title: 'تعویق یادآوری',
                text: `یادآوری به مدت ${timeLabels[minutes]} تعویق خواهد شد`,
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: 'بله، تعویق کن',
                cancelButtonText: 'لغو'
            });

            if (result.isConfirmed) {
                const res = await this.req('snooze_reminder', {
                    activity_id: activityId,
                    minutes: minutes
                });
                if (res.status === 'success') {
                    Swal.fire({
                        title: 'موفق!',
                        text: `یادآوری به مدت ${timeLabels[minutes]} تعویق شد`,
                        icon: 'success',
                        timer: 2000
                    });
                    this.loadReminders();
                } else {
                    throw new Error(res.message);
                }
            }
        } catch (error) {
            console.error('Snooze reminder failed:', error);
            Swal.fire({
                title: 'خطا!',
                text: error.message || 'خطا در تعویق یادآوری',
                icon: 'error'
            });
        }
    },

	    populateOwnerFilterSelects() {
	        const leadsOwnerSelect = document.getElementById('leads-owner-filter');
	        const dealsOwnerSelect = document.getElementById('deals-owner-filter');
	        const legacyDealsOwnerSelect = document.getElementById('legacy-deals-owner-filter');
	        const dashboardOwnerSelect = document.getElementById('dashboard-owner-filter');
	        
	        console.log('[DEBUG] populateOwnerFilterSelects called');
	        console.log('[DEBUG] leadsOwnerSelect exists:', !!leadsOwnerSelect);
	        console.log('[DEBUG] dealsOwnerSelect exists:', !!dealsOwnerSelect);
	        console.log('[DEBUG] legacyDealsOwnerSelect exists:', !!legacyDealsOwnerSelect);
	        console.log('[DEBUG] dashboardOwnerSelect exists:', !!dashboardOwnerSelect);
	        console.log('[DEBUG] didarUsers count:', this.state.didarUsers?.length || 0);

	        const render = (select, isDashboard = false) => {
	            if (!select) {
	                console.log('[DEBUG] Select element is null, skipping render');
	                return;
	            }
	            
	            const current = select.value || '';
	            console.log('[DEBUG] Rendering select, current value:', current, 'isDashboard:', isDashboard);
	            
	            select.innerHTML = isDashboard ? '<option value="">همه مسئول‌ها (کل سیستم)</option>' : '<option value="">همه مسئول‌ها</option>';
	            
	            console.log('[DEBUG] Starting to add user options. Users array:', this.state.didarUsers);
	            
	            (this.state.didarUsers || []).forEach((user, index) => {
	                // Use owner_id (didar_id) if available, otherwise fallback to internal id
	                const value = user.owner_id || user.id;
	                const label = user.display_name || user.email || ('کاربر ' + user.id);
	                
	                console.log(`[DEBUG] User ${index}:`, { value, label, user });
	                
	                if (!value) {
	                    console.log(`[DEBUG] User ${index} skipped - no value`);
	                    return;
	                }
	                
	                const opt = document.createElement('option');
	                opt.value = value;
	                opt.textContent = label;
	                select.appendChild(opt);
	                console.log(`[DEBUG] User ${index} added to select with value: ${value}, label: ${label}`);
	            });
	            
	            console.log('[DEBUG] Total options in select:', select.options.length);
	            // Restore previous value if it still exists in the new list
	            select.value = current;
	        };

	        render(leadsOwnerSelect);
	        render(dealsOwnerSelect);
	        render(legacyDealsOwnerSelect);
	        render(dashboardOwnerSelect, true);
	        
	        console.log('[DEBUG] populateOwnerFilterSelects complete');
	    },
    
    populateLeadOwnerSelect() {
        const select = document.getElementById('lead-owner-select');
        const prevSelect = document.getElementById('lead-previous-owner-select');
        if(!select && !prevSelect) {
            console.warn('lead owner selects not found');
            return;
        }
        if (select) {
            select.innerHTML = '<option value="">-- انتخاب مسئول از CRM خارجی --</option>';
        }
        if (prevSelect) {
            prevSelect.innerHTML = '<option value="">-- انتخاب مسئول قبلی --</option>';
        }
        
        if (!this.state.didarUsers || this.state.didarUsers.length === 0) {
            console.warn('No External CRM users available to populate select');
            if (select) {
                const opt = document.createElement('option');
                opt.value = '';
                opt.textContent = '-- هیچ کاربری یافت نشد --';
                opt.disabled = true;
                select.appendChild(opt);
            }
            if (prevSelect) {
                const opt2 = document.createElement('option');
                opt2.value = '';
                opt2.textContent = '-- هیچ کاربری یافت نشد --';
                opt2.disabled = true;
                prevSelect.appendChild(opt2);
            }
            return;
        }
        
        let addedCount = 0;
        this.state.didarUsers.forEach(user => {
            if(!user.id) {
                console.warn('User without id found:', user);
                return;
            }
            const value = user.owner_id || user.id;
            const label = user.display_name || user.email || user.id;
            if (select) {
                const opt = document.createElement('option');
                opt.value = value;
                opt.textContent = label;
                select.appendChild(opt);
            }
            if (prevSelect) {
                const optPrev = document.createElement('option');
                optPrev.value = value;
                optPrev.textContent = label;
                prevSelect.appendChild(optPrev);
            }
            addedCount++;
        });
        
        console.log(`Populated lead owner select with ${addedCount} users`);
    },
    
        populateSelectOptions(selectId, options, placeholderOverride = null) {
        const select = document.getElementById(selectId);
        if(!select) return;
        const current = select.value;
        let placeholder = placeholderOverride;
        if (!placeholder) {
            const existingPlaceholder = select.querySelector('option[value=""]')?.textContent;
            if (existingPlaceholder) {
                placeholder = existingPlaceholder;
            }
        }
        if (!placeholder) {
            // Get default placeholder based on select ID
            placeholder = '-- انتخاب --';
            if (selectId === 'lead-source') {
                placeholder = 'شیوه آشنایی *';
            } else if (selectId === 'lead-content-topic') {
                placeholder = 'موضوع محتوا *';
            }
        }
        select.innerHTML = `<option value="">${placeholder}</option>`;
        options.forEach(opt => {
            const option = document.createElement('option');
            option.value = opt.value;
            option.textContent = opt.label ?? opt.title ?? opt.value;
            select.appendChild(option);
        });
        if (current) {
            select.value = current;
        }
    },

    async loadLookups() {
        try {
            const res = await this.req('get_lookups', {});
            if (res.status === 'success' && res.lookups) {
                this.state.lookups = res.lookups || {};
                this.state.lookupsLoaded = true;
                try {
                    const servicesRes = await this.req('get_lookups', { group: 'allowed_services', include_inactive: '1' });
                    if (servicesRes.status === 'success' && Array.isArray(servicesRes.items)) {
                        this.state.allowedServiceLookupItemsAll = servicesRes.items;
                    }
                } catch (serviceLookupError) {
                    console.warn('Failed to load allowed services archive:', serviceLookupError);
                    this.state.allowedServiceLookupItemsAll = [];
                }
                try {
                    // Load destination banks INCLUDING hidden/inactive ones so that older
                    // deals whose card was later disabled in the lookup manager can still
                    // resolve the bank title / card / account / sheba for display.
                    const banksRes = await this.req('get_lookups', { group: 'payment_destination_banks', include_inactive: '1' });
                    if (banksRes.status === 'success' && Array.isArray(banksRes.items)) {
                        this.state.paymentDestinationBanksAll = banksRes.items;
                    }
                } catch (bankLookupError) {
                    console.warn('Failed to load destination banks archive:', bankLookupError);
                    this.state.paymentDestinationBanksAll = [];
                }
                const pipeline = this.getLookupValues('pipeline_stages');
                if (pipeline.length) {
                    this.state.pipelineStages = pipeline;
                }
                if (this.isAdmin() && this.state.lookupSelectedGroup) {
                    await this.loadLookupItems(this.state.lookupSelectedGroup);
                }
            }
        } catch (error) {
            console.warn('Failed to load lookups:', error);
        }
    },

    getLookupItems(groupCode) {
        const group = this.state.lookups?.[groupCode];
        if (group && Array.isArray(group.items) && group.items.length) {
            return group.items;
        }
        if (groupCode === 'lead_sources') return this.leadSources || [];
        if (groupCode === 'content_topics') return this.contentTopics || [];
        return [];
    },

    getLookupOptions(groupCode) {
        const items = this.getLookupItems(groupCode);
        return items.map(item => ({
            value: item.value ?? item.title ?? item.label ?? '',
            label: item.title ?? item.label ?? item.value ?? ''
        })).filter(opt => opt.value !== '' || opt.label !== '');
    },

    getLookupValues(groupCode) {
        return this.getLookupOptions(groupCode).map(opt => opt.value || opt.label);
    },
    getActivePipelineStageOptions() {
        const options = this.getLookupOptions('pipeline_stages');
        if (options.length) {
            return options.filter(opt => String(opt.value || '').trim() !== '');
        }

        return (this.state.pipelineStages || [])
            .map(stage => ({ value: stage, label: stage }))
            .filter(opt => String(opt.value || '').trim() !== '');
    },
    ensureSelectOption(selectElement, value, label = null, isTemporary = false) {
        if (!selectElement) return null;
        const targetValue = String(value || '').trim();
        if (!targetValue) return null;

        const existing = Array.from(selectElement.options).find(option => option.value === targetValue);
        if (existing) return existing;

        if (isTemporary) {
            Array.from(selectElement.querySelectorAll('option[data-temp-option="1"]')).forEach(option => option.remove());
        }

        const option = document.createElement('option');
        option.value = targetValue;
        option.textContent = label || targetValue;
        if (isTemporary) option.dataset.tempOption = '1';
        selectElement.appendChild(option);
        return option;
    },
    syncDealPipelineStageSelectOptions(currentStage = '') {
        const selectElement = document.getElementById('deal-pipeline-stage');
        if (!selectElement) return;

        Array.from(selectElement.querySelectorAll('option[data-temp-option="1"]')).forEach(option => option.remove());
        const normalizedStage = String(currentStage || '').trim();
        if (!normalizedStage) return;

        this.ensureSelectOption(selectElement, normalizedStage, `${normalizedStage} (غیرفعال/آرشیوی)`, true);
    },
    buildReactivationStageSelectHtml() {
        const options = this.getActivePipelineStageOptions();
        return [
            '<option value="">-- انتخاب مرحله کاریز --</option>',
            ...options.map(opt => `<option value="${this.escapeHtml(opt.value)}">${this.escapeHtml(opt.label || opt.value)}</option>`)
        ].join('');
    },

    getLookupLabel(groupCode, value) {
        if (!value) return '-';
        const rawValue = String(value || '').trim();
        const legacyLabels = {
            lead_sources: {
                yes: 'بله',
                eitaa: 'ایتا',
                incoming_call: 'تماس ورودی',
                instagram: 'اینستاگرام',
                referral: 'معرف',
                old_customer: 'مشتری قدیمی',
                other: 'سایر'
            },
            lead_source_details: {
                general: 'عمومی',
                generic: 'عمومی',
                referral_customer: 'معرف - مشتری قدیم',
                referral_internal: 'معرف - همکاران داخلی',
                referral_external: 'معرف - همکاران خارج از سازمان',
                referral_teacher: 'معرف - استاد مطلبی'
            },
            content_topics: {
                vip_channel: 'کانال VIP',
                detailing_training: 'آموزش دیتیلینگ',
                car_intelligence_test: 'تست هوش خودرو',
                inspection_test: 'تست کارشناسی',
                other: 'سایر'
            }
        };
        const legacyLabel = legacyLabels[groupCode]?.[rawValue.toLowerCase()] || '';
        const items = this.getLookupItems(groupCode);
        for (const item of items) {
            const itemValue = item.value ?? item.title ?? item.label;
            if (itemValue === value || String(itemValue || '').trim() === rawValue) {
                const display = item.title ?? item.label ?? itemValue;
                return legacyLabel && String(display || '').trim().toLowerCase() === rawValue.toLowerCase()
                    ? legacyLabel
                    : display;
            }
        }
        const fallbackItems = (() => {
            if (groupCode === 'lead_sources') return this.leadSources || [];
            if (groupCode === 'content_topics') return this.contentTopics || [];
            if (groupCode === 'lead_source_details') {
                return Object.values(this.leadSourceDetails || {}).flat();
            }
            return [];
        })();
        for (const item of fallbackItems) {
            const itemValue = item.value ?? item.title ?? item.label;
            if (itemValue === value || String(itemValue || '').trim() === rawValue) {
                const display = item.title ?? item.label ?? itemValue;
                return legacyLabel && String(display || '').trim().toLowerCase() === rawValue.toLowerCase()
                    ? legacyLabel
                    : display;
            }
        }
        return legacyLabel || value;
    },

    getLookupChildOptions(groupCode, parentValue) {
        const group = this.state.lookups?.[groupCode];
        if (group && Array.isArray(group.items) && group.items.length) {
            return group.items
                .filter(item => (item.parent_value || '') === parentValue)
                .map(item => ({
                    value: item.value ?? item.title ?? '',
                    label: item.title ?? item.value ?? ''
                }));
        }
        if (groupCode === 'lead_source_details') {
            const fallback = this.leadSourceDetails?.[parentValue] || this.leadSourceDetails?.default || [];
            return fallback.map(item => ({ value: item.value, label: item.label }));
        }
        return [];
    },
    normalizeLookupComparable(value) {
        return String(value ?? '').trim().replace(/\s+/g, ' ');
    },
    getLookupItemMatches(item, value) {
        const target = this.normalizeLookupComparable(value);
        if (!target || !item) return false;
        return [item.value, item.title, item.code, item.label]
            .some(candidate => this.normalizeLookupComparable(candidate) === target);
    },
    findLookupItem(groupCode, value, includeInactiveFallback = false) {
        const primaryItems = this.getLookupItems(groupCode) || [];
        let archivedItems = [];
        if (includeInactiveFallback) {
            if (groupCode === 'allowed_services') {
                archivedItems = this.state.allowedServiceLookupItemsAll || [];
            } else if (groupCode === 'payment_destination_banks') {
                archivedItems = this.state.paymentDestinationBanksAll || [];
            }
        }
        const items = [...primaryItems, ...archivedItems];
        const seen = new Set();
        const uniqueItems = items.filter(item => {
            const key = String(item?.id ?? `${item?.value ?? ''}:${item?.title ?? ''}`);
            if (seen.has(key)) return false;
            seen.add(key);
            return true;
        });
        return uniqueItems.find(item => this.getLookupItemMatches(item, value)) || null;
    },
    getLookupMeta(groupCode, value) {
        const item = this.findLookupItem(groupCode, value, true);
        if (!item || !item.meta_json) return {};
        try {
            const parsed = JSON.parse(item.meta_json);
            return parsed && typeof parsed === 'object' ? parsed : {};
        } catch (_) {
            return {};
        }
    },
    getAllowedServiceFixedPrice(serviceValue) {
        if (!serviceValue) return null;
        const meta = this.getLookupMeta('allowed_services', serviceValue);
        if (!meta || typeof meta !== 'object') return null;
        const rawPrice = meta.price ?? null;
        if (rawPrice === null || rawPrice === '') return null;
        const price = typeof rawPrice === 'number' ? rawPrice : parseMoneyValue(String(rawPrice));
        return Number.isFinite(price) && price > 0 ? price : null;
    },
    getAllowedServicePriceListCode(serviceValue) {
        if (!serviceValue) return '';
        const meta = this.getLookupMeta('allowed_services', serviceValue);
        if (!meta || typeof meta !== 'object') return '';
        return String(meta.price_list_code ?? '').trim();
    },
    getAllowedServiceLookupItemsDetailed(includeInactiveFallback = false) {
        const activeItems = this.getLookupItems('allowed_services') || [];
        const archivedItems = includeInactiveFallback ? (this.state.allowedServiceLookupItemsAll || []) : [];
        const seen = new Set();
        return [...activeItems, ...archivedItems].filter(item => {
            if (!item || !(item.id || item.value || item.title)) return false;
            const key = String(item.id ?? `${item.value ?? ''}:${item.title ?? ''}`);
            if (seen.has(key)) return false;
            seen.add(key);
            return true;
        });
    },
    getAllowedServiceLookupItemByValue(serviceValue) {
        const target = this.normalizeLookupComparable(serviceValue);
        if (!target) return null;
        const items = this.getAllowedServiceLookupItemsDetailed(true);
        return items.find(item => this.getLookupItemMatches(item, target)) || null;
    },
    getAllowedServiceLookupItemIdByValue(serviceValue) {
        const item = this.getAllowedServiceLookupItemByValue(serviceValue);
        const id = parseInt(item?.id ?? 0, 10);
        return Number.isFinite(id) && id > 0 ? id : 0;
    },
    getProductsLinkedToServiceLookupItem(serviceLookupItemId) {
        const targetId = parseInt(serviceLookupItemId || 0, 10);
        if (!targetId) return [];
        const products = Array.isArray(this.state.products) ? this.state.products : [];
        return products.filter(p => parseInt(p?.service_lookup_item_id ?? 0, 10) === targetId);
    },
    getAllowedServiceLinkedProductIds(serviceValue) {
        const serviceId = this.getAllowedServiceLookupItemIdByValue(serviceValue);
        return this.getProductsLinkedToServiceLookupItem(serviceId).map(p => String(p.id));
    },
    buildAllowedServicesOptionsHtml(selectedServiceLookupItemId = '') {
        const selected = String(selectedServiceLookupItemId ?? '');
        const items = this.getAllowedServiceLookupItemsDetailed();
        const options = ['<option value="">-</option>'];
        items.forEach(item => {
            const id = String(item.id ?? '');
            if (!id) return;
            const label = this.escapeHtml(item.title ?? item.value ?? id);
            options.push(`<option value="${id}" ${id === selected ? 'selected' : ''}>${label}</option>`);
        });
        return options.join('');
    },
    buildProductsMultiSelectHtml(selectedIds = []) {
        const selected = new Set((selectedIds || []).map(v => String(v)));
        const products = Array.isArray(this.state.products) ? this.state.products : [];
        if (!products.length) {
            return '<div class="text-[10px] text-gray-500">محصولی ثبت نشده</div>';
        }
        return `
            <select class="input bg-gray-800 text-xs min-w-[170px]" name="service_linked_product_ids" multiple size="${Math.min(5, Math.max(2, products.length))}">
                ${products.map(p => {
                    const id = String(p.id ?? '');
                    if (!id) return '';
                    return `<option value="${id}" ${selected.has(id) ? 'selected' : ''}>${this.escapeHtml(p.name || id)}</option>`;
                }).join('')}
            </select>
        `;
    },
    buildLookupStageDisabledUsersMultiSelectHtml(selectedIds = []) {
        const selected = new Set((selectedIds || []).map(v => String(v)));
        const users = Array.isArray(this.state.lookupStageUserOptions) ? this.state.lookupStageUserOptions : [];
        if (!users.length) {
            return '<div class="text-[10px] text-gray-500">کاربری بارگذاری نشده</div>';
        }
        return `
            <select class="input bg-gray-800 text-xs min-w-[190px]" name="stage_disabled_user_ids" multiple size="${Math.min(6, Math.max(2, users.length))}">
                ${users.map(user => {
                    const id = String(user.didar_user_id ?? '');
                    if (!id) return '';
                    const label = user.display_name || user.email || id;
                    return `<option value="${this.escapeHtml(id)}" ${selected.has(id) ? 'selected' : ''}>${this.escapeHtml(label)}</option>`;
                }).join('')}
            </select>
        `;
    },
    getLookupTableColumnCount() {
        return 21;
    },
    setLookupManagerModeUI(mode) {
        const itemsSection = document.getElementById('lookup-items-section');
        const productsSection = document.getElementById('lookup-products-manager-section');
        const rbacSection = document.getElementById('lookup-rbac-manager-section');
        const quickSettings = document.getElementById('field-quick-settings');
        const addBtn = document.getElementById('lookup-add-item-btn');

        if (itemsSection) itemsSection.classList.toggle('hidden', mode !== 'lookup_or_field');
        if (productsSection) productsSection.classList.toggle('hidden', mode !== 'products_manager');
        if (rbacSection) rbacSection.classList.toggle('hidden', mode !== 'rbac_manager');
        if (quickSettings) quickSettings.classList.toggle('hidden', mode !== 'lookup_or_field');
        if (addBtn) addBtn.disabled = (mode !== 'lookup_or_field');
    },
    toggleLookupServiceSpecificColumns(show) {
        const ids = [
            'lookup-col-header-service-price',
            'lookup-col-header-service-price-list-code',
            'lookup-col-header-service-products'
        ];
        ids.forEach(id => {
            const el = document.getElementById(id);
            if (!el) return;
            el.classList.toggle('hidden', !show);
        });
        document.querySelectorAll('.lookup-col-service-price,.lookup-col-service-price-list-code,.lookup-col-service-products')
            .forEach(el => el.classList.toggle('hidden', !show));
    },
    toggleLookupDestinationBankColumns(show) {
        const ids = [
            'lookup-col-header-destination-bank-name',
            'lookup-col-header-destination-account-type',
            'lookup-col-header-destination-card',
            'lookup-col-header-destination-account',
            'lookup-col-header-destination-sheba',
            'lookup-col-header-destination-owner',
            'lookup-col-header-destination-tafsili'
        ];
        ids.forEach(id => {
            const el = document.getElementById(id);
            if (!el) return;
            el.classList.toggle('hidden', !show);
        });
        document.querySelectorAll('.lookup-col-destination-bank-name,.lookup-col-destination-account-type,.lookup-col-destination-card,.lookup-col-destination-account,.lookup-col-destination-sheba,.lookup-col-destination-owner,.lookup-col-destination-tafsili')
            .forEach(el => el.classList.toggle('hidden', !show));
    },
    toggleLookupPipelineStageUserColumns(show) {
        const el = document.getElementById('lookup-col-header-stage-disabled-users');
        if (el) el.classList.toggle('hidden', !show);
        document.querySelectorAll('.lookup-col-stage-disabled-users')
            .forEach(node => node.classList.toggle('hidden', !show));
    },
    async ensureLookupStageUserOptionsLoaded(force = false) {
        if (!force && Array.isArray(this.state.lookupStageUserOptions) && this.state.lookupStageUserOptions.length) {
            return this.state.lookupStageUserOptions;
        }
        const res = await this.req('get_users_list');
        if (res.status !== 'success') {
            this.state.lookupStageUserOptions = [];
            return [];
        }
        const users = Array.isArray(res.users) ? res.users : [];
        this.state.lookupStageUserOptions = users
            .filter(user => Number(user.is_active ?? 1) === 1 && String(user.didar_user_id || '').trim() !== '')
            .map(user => ({
                didar_user_id: String(user.didar_user_id || '').trim(),
                display_name: user.display_name || `${user.first_name || ''} ${user.last_name || ''}`.trim() || user.email || String(user.didar_user_id || '').trim(),
                email: user.email || ''
            }));
        return this.state.lookupStageUserOptions;
    },
    renderLookupProductsManagerTable() {
        const tbody = document.getElementById('lookup-products-table');
        if (!tbody) return;
        const rows = Array.isArray(this.state.products) ? this.state.products : [];
        if (!rows.length) {
            tbody.innerHTML = '<tr><td colspan="3" class="p-6 text-center text-gray-500">محصولی ثبت نشده است.</td></tr>';
            return;
        }
        tbody.innerHTML = rows.map(p => {
            const id = p.id || '';
            const name = this.escapeHtml(p.name || '');
            const selectedServiceId = String(p.service_lookup_item_id ?? '');
            return `
                <tr data-product-id="${id}">
                    <td class="p-3"><input class="input bg-gray-800 text-xs" name="name" value="${name}" placeholder="نام محصول"></td>
                    <td class="p-3">
                        <select class="input bg-gray-800 text-xs" name="service_lookup_item_id">
                            ${this.buildAllowedServicesOptionsHtml(selectedServiceId)}
                        </select>
                    </td>
                    <td class="p-3">
                        <div class="flex gap-2 justify-center">
                            <button type="button" class="px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-xs" onclick="app.saveLookupProductRow(this)">Save</button>
                            <button type="button" class="px-3 py-1 rounded bg-red-600 hover:bg-red-500 text-xs" onclick="app.deleteLookupProductRow(this)">Delete</button>
                        </div>
                    </td>
                </tr>
            `;
        }).join('');
    },
    addLookupProductRow() {
        const tbody = document.getElementById('lookup-products-table');
        if (!tbody) return;
        if (tbody.querySelector('td[colspan="3"]')) {
            tbody.innerHTML = '';
        }
        const row = document.createElement('tr');
        row.dataset.productId = '';
        row.innerHTML = `
            <td class="p-3"><input class="input bg-gray-800 text-xs" name="name" value="" placeholder="نام محصول"></td>
            <td class="p-3">
                <select class="input bg-gray-800 text-xs" name="service_lookup_item_id">
                    ${this.buildAllowedServicesOptionsHtml('')}
                </select>
            </td>
            <td class="p-3">
                <div class="flex gap-2 justify-center">
                    <button type="button" class="px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-xs" onclick="app.saveLookupProductRow(this)">Save</button>
                    <button type="button" class="px-3 py-1 rounded bg-red-600 hover:bg-red-500 text-xs" onclick="app.deleteLookupProductRow(this)">Delete</button>
                </div>
            </td>
        `;
        tbody.prepend(row);
    },
    async saveLookupProductRow(button) {
        const row = button.closest('tr');
        if (!row) return;
        const id = row.dataset.productId || '';
        const name = row.querySelector('[name="name"]')?.value?.trim() || '';
        const serviceLookupItemId = row.querySelector('[name="service_lookup_item_id"]')?.value || '';
        if (!name) {
            Swal.fire('خطا', 'نام محصول الزامی است', 'error');
            return;
        }
        const res = await this.req('save_product', {
            id,
            name,
            service_lookup_item_id: serviceLookupItemId
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره محصول', 'error');
            return;
        }
        await this.loadProducts();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'محصول ذخیره شد', showConfirmButton: false, timer: 1500 });
        if (this.state.lookupSelectedGroup === 'allowed_services') {
            this.renderLookupItemsTable('allowed_services', this.state.lookupItems);
        }
    },
    async deleteLookupProductRow(button) {
        const row = button.closest('tr');
        if (!row) return;
        const id = row.dataset.productId || '';
        if (!id) {
            row.remove();
            return;
        }
        const result = await Swal.fire({
            title: 'حذف محصول؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف',
            cancelButtonText: 'انصراف'
        });
        if (!result.isConfirmed) return;
        const res = await this.req('delete_product', { id });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در حذف محصول', 'error');
            return;
        }
        await this.loadProducts();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'محصول حذف شد', showConfirmButton: false, timer: 1500 });
        if (this.state.lookupSelectedGroup === 'allowed_services') {
            this.renderLookupItemsTable('allowed_services', this.state.lookupItems);
        }
    },
    async loadLookupRbacManagerData(force = false) {
        if (!this.isAdmin()) return;
        if (!force && this.state.rbacCatalog && this.state.rbacCatalog.roles && this.state.rbacCatalog.capabilities) {
            this.renderLookupRbacManager();
            return;
        }
        const [catalogRes, usersRes] = await Promise.all([
            this.req('rbac_catalog'),
            this.req('get_users_list')
        ]);
        if (catalogRes.status !== 'success') {
            Swal.fire('خطا', catalogRes.message || 'خطا در دریافت RBAC', 'error');
            return;
        }
        this.state.rbacCatalog = {
            roles: catalogRes.roles || [],
            capabilities: catalogRes.capabilities || [],
            resources: catalogRes.resources || {},
            scope_types: catalogRes.scope_types || ['none', 'self', 'selected_users', 'all'],
            matrix: catalogRes.matrix || null
        };
        this.state.rbacUsersForScopes = (usersRes.status === 'success' ? (usersRes.users || []) : []);
        this.renderLookupRbacManager();
    },
    renderLookupRbacManager() {
        const roleSelect = document.getElementById('rbac-role-select');
        const cloneTemplate = document.getElementById('rbac-clone-template-role');
        const roles = Array.isArray(this.state.rbacCatalog?.roles) ? this.state.rbacCatalog.roles : [];
        const currentRoleKey = roleSelect?.value || this.state.rbacSelectedRoleKey || '';
        const buildOpts = () => roles.map(r => {
            const key = String(r.role_key || '').trim();
            if (!key) return '';
            const label = r.title || key;
            return `<option value="${this.escapeHtml(key)}">${this.escapeHtml(label)}</option>`;
        }).join('');
        if (roleSelect) {
            roleSelect.innerHTML = '<option value="">-- انتخاب نقش --</option>' + buildOpts();
            if (currentRoleKey) roleSelect.value = currentRoleKey;
        }
        if (cloneTemplate) {
            cloneTemplate.innerHTML = '<option value="">-- تمپلیت نقش --</option>' + buildOpts();
        }
        const userSelect = document.getElementById('rbac-user-select');
        if (userSelect) {
            const users = Array.isArray(this.state.rbacUsersForScopes) ? this.state.rbacUsersForScopes : [];
            const curUser = userSelect.value;
            userSelect.innerHTML = '<option value="">-- انتخاب کاربر --</option>' + users.map(u => {
                const id = String(u.id || '');
                if (!id) return '';
                const name = u.display_name || `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || id;
                const roleTxt = u.role ? ` — ${u.role}` : '';
                return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(name + roleTxt)}</option>`;
            }).join('');
            if (curUser) userSelect.value = curUser;
        }
        if (currentRoleKey) {
            this.onLookupRbacRoleChange();
        }
    },
    async onLookupRbacRoleChange() {
        const roleKey = document.getElementById('rbac-role-select')?.value || '';
        this.state.rbacSelectedRoleKey = roleKey;
        if (!roleKey) {
            this.renderLookupRbacRoleEditor(null, [], []);
            return;
        }
        const res = await this.req('rbac_role_permissions', { role_key: roleKey });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در دریافت دسترسی‌های نقش', 'error');
            return;
        }
        this.state.rbacCurrentRolePermissions = res;
        this.renderLookupRbacRoleEditor(res.role || null, res.capabilities || [], res.scopes || []);
    },
    renderLookupRbacRoleEditor(role, capabilityKeys, scopes) {
        const roleIdEl = document.getElementById('rbac-role-id');
        const roleKeyEl = document.getElementById('rbac-role-key');
        const roleTitleEl = document.getElementById('rbac-role-title');
        const roleActiveEl = document.getElementById('rbac-role-active');
        const roleSysBadge = document.getElementById('rbac-role-system-badge');
        if (roleIdEl) roleIdEl.value = role?.id || '';
        if (roleKeyEl) roleKeyEl.value = role?.role_key || '';
        if (roleTitleEl) roleTitleEl.value = role?.title || '';
        if (roleActiveEl) roleActiveEl.checked = String(role?.is_active ?? '1') === '1';
        if (roleSysBadge) roleSysBadge.classList.toggle('hidden', String(role?.is_system ?? '0') !== '1');

        const isSystemRole = String(role?.is_system ?? '0') === '1';
        const roleKeyVal = String(role?.role_key || '');
        if (roleKeyEl) roleKeyEl.disabled = isSystemRole; // system role_key is immutable
        const deleteBtn = document.getElementById('rbac-delete-role-btn');
        if (deleteBtn) {
            const canDelete = !!role && !isSystemRole && roleKeyVal !== 'admin';
            deleteBtn.classList.toggle('hidden', !canDelete);
        }

        this.state.rbacSelectedCapabilityKeys = Array.isArray(capabilityKeys) ? capabilityKeys.map(v => String(v)) : [];
        // Authoritative working copy: BOTH the matrix and the list view read/write this same set,
        // and saveLookupRbacCapabilities() emits exactly this set, so any capability not surfaced as
        // a column is preserved untouched, and a no-op edit round-trips to an identical grant set.
        this.state.rbacWorkingCaps = new Set(this.state.rbacSelectedCapabilityKeys);
        this.state.rbacSelectedScopes = Array.isArray(scopes) ? scopes : [];
        this.state.rbacCurrentRole = role || null;
        this.renderLookupRbacCapabilities();
        this.renderLookupRbacScopes();
        this.updateRbacRoleSummary();
    },
    // ---- RBAC capability metadata (UI grouping only - never changes backend security) ----
    // Tab -> card -> permission structure. Persian labels are primary; the technical key
    // is shown smaller/gray under each label. Sensitivity drives badges and quick presets.
    rbacTabDefinitions() {
        return [
            { id: 'main', label: 'اصلی', cards: [
                { id: 'activities', label: 'فعالیت‌ها', items: [
                    ['activities.view', 'مشاهده فعالیت‌ها'],
                    ['activities.create', 'ایجاد فعالیت'],
                    ['activities.edit', 'ویرایش فعالیت'],
                    ['activities.delete', 'حذف فعالیت'],
                    ['activities.types.view', 'مشاهده نوع فعالیت‌ها'],
                ]},
                { id: 'leads', label: 'لیدها', items: [
                    ['leads.view', 'مشاهده لیدها'],
                    ['leads.profile.view', 'مشاهده پروفایل لید'],
                    ['leads.lookup_phone', 'مشاهده شماره تماس لید'],
                    ['leads.create', 'ایجاد لید'],
                    ['leads.edit', 'ویرایش لید'],
                    ['leads.delete', 'حذف لید'],
                    ['leads.transfer_owner', 'انتقال مالک لید'],
                    ['leads.backfill_products', 'تکمیل یا اصلاح محصولات لید'],
                    ['lead_owner_history.view', 'مشاهده تاریخچه مالک لید'],
                ]},
                { id: 'deals', label: 'معاملات', items: [
                    ['deals.view', 'مشاهده معاملات'],
                    ['deals.detail.view', 'مشاهده جزئیات معامله'],
                    ['deals.create', 'ایجاد معامله'],
                    ['deals.edit', 'ویرایش معامله'],
                    ['deals.delete', 'حذف معامله'],
                    ['deals.status.edit', 'تغییر وضعیت معامله'],
                    ['deals.pipeline.edit', 'تغییر پایپ‌لاین معامله'],
                    ['deals.course.edit', 'ویرایش وضعیت دوره مرتبط'],
                    ['deals.reactivate', 'فعال‌سازی مجدد معامله'],
                    ['deals.reconcile', 'مغایرت‌گیری معاملات'],
                    ['deals.force_success', 'اجبار معامله به موفق'],
                ]},
                { id: 'lead_pools', label: 'استخر لیدها', items: [
                    ['lead_pools.view', 'مشاهده استخر لیدها'],
                    ['lead_pools.claim', 'برداشتن لید از استخر'],
                    ['lead_pools.assign', 'تخصیص لید به دیگران'],
                    ['lead_pools.manage', 'مدیریت استخر لیدها'],
                ]},
                { id: 'reminders', label: 'یادآورها', items: [
                    ['reminders.view', 'مشاهده یادآورها'],
                    ['reminders.manage', 'مدیریت یادآورها'],
                ]},
                { id: 'support', label: 'پشتیبانی', items: [
                    ['support.view', 'مشاهده پشتیبانی'],
                    ['support.create', 'ایجاد درخواست پشتیبانی'],
                    ['support.edit', 'ویرایش درخواست پشتیبانی'],
                    ['support.delete', 'حذف درخواست پشتیبانی'],
                ]},
                { id: 'customer_access', label: 'درخواست‌های دسترسی مشتری', items: [
                    ['customer_access.view', 'مشاهده درخواست‌های دسترسی مشتری'],
                    ['customer_access.create', 'ایجاد درخواست دسترسی مشتری'],
                    ['customer_access.resolve', 'رسیدگی به درخواست دسترسی مشتری'],
                ]},
                { id: 'satisfaction', label: 'رضایت مشتری', items: [
                    ['satisfaction.view', 'مشاهده رضایت مشتری'],
                    ['satisfaction.create', 'ثبت رضایت مشتری'],
                ]},
                { id: 'products', label: 'محصولات', items: [
                    ['products.view', 'مشاهده محصولات'],
                    ['products.manage', 'مدیریت محصولات'],
                    ['products.service_links.manage', 'مدیریت ارتباط محصول و خدمت'],
                ]},
                { id: 'referrals', label: 'ارجاع‌ها', items: [
                    ['referrals.view', 'مشاهده ارجاع‌ها'],
                ]},
            ]},
            { id: 'reports', label: 'گزارش‌ها', cards: [
                { id: 'dashboard', label: 'داشبورد', items: [
                    ['dashboard.view', 'مشاهده داشبورد اصلی'],
                ]},
                { id: 'kpi', label: 'KPI', items: [
                    ['kpi.view', 'مشاهده داشبورد KPI'],
                    ['kpi.drilldown', 'مشاهده جزئیات KPI'],
                    ['kpi.targets.manage', 'مدیریت اهداف KPI'],
                    ['kpi.deals.details', 'مشاهده جزئیات معاملات در KPI'],
                ]},
                { id: 'performance', label: 'داشبورد عملکرد', items: [
                    ['performance.view', 'مشاهده داشبورد عملکرد'],
                    ['performance.drilldown', 'مشاهده جزئیات عملکرد'],
                    ['performance.ceo_report', 'مشاهده گزارش مدیرعامل'],
                ]},
                { id: 'performance_widgets', label: 'ویجت‌های عملکرد', groups: [
                    { label: 'نمودارها', items: [
                        ['performance.chart.daily_sales', 'نمودار فروش روزانه'],
                        ['performance.chart.daily_activities', 'نمودار فعالیت‌های روزانه'],
                        ['performance.chart.deal_status', 'نمودار وضعیت معاملات'],
                        ['performance.chart.product_share', 'نمودار سهم محصولات'],
                        ['performance.chart.conversion_funnel', 'نمودار قیف تبدیل'],
                    ]},
                    { label: 'کارت‌های عددی', items: [
                        ['performance.card.sales_total', 'کارت مجموع فروش'],
                        ['performance.card.outstanding_amount_sum', 'کارت مبلغ مانده'],
                        ['performance.card.settled_count', 'کارت تعداد تسویه‌شده'],
                        ['performance.card.lost_count', 'کارت تعداد ناموفق'],
                        ['performance.card.won_count', 'کارت تعداد موفق'],
                        ['performance.card.open_pipeline_count', 'کارت معاملات باز'],
                        ['performance.card.without_payment_count', 'کارت معاملات بدون پرداخت'],
                        ['performance.card.total_deposits_amount', 'کارت کل واریزی‌ها'],
                        ['performance.card.outstanding_balance_amount', 'کارت کل مانده حساب‌ها'],
                        ['performance.card.cheque_collected_amount', 'کارت واریزی چک‌ها'],
                        ['performance.card.access_granted_count', 'کارت دسترسی داده‌شده'],
                    ]},
                    { label: 'جدول‌ها', items: [
                        ['performance.table.activity_summary', 'جدول خلاصه فعالیت‌ها'],
                        ['performance.table.activity_type_details', 'جدول جزئیات نوع فعالیت'],
                        ['performance.table.deal_status', 'جدول وضعیت معاملات'],
                        ['performance.table.lead_status', 'جدول وضعیت لیدها'],
                        ['performance.table.conversion_status', 'جدول وضعیت تبدیل'],
                        ['performance.table.payment_mix', 'جدول ترکیب پرداخت'],
                        ['performance.table.payment_status', 'جدول وضعیت پرداخت'],
                        ['performance.table.product_status', 'جدول وضعیت محصول'],
                        ['performance.table.product_sold_by_owner_day', 'جدول فروش محصول بر اساس کارشناس و روز'],
                        ['performance.table.product_requests_by_owner_day', 'جدول درخواست محصول بر اساس کارشناس و روز'],
                        ['performance.table.product_failed_by_owner_day', 'جدول محصول ناموفق بر اساس کارشناس و روز'],
                        ['performance.table.failure_reasons', 'جدول دلایل شکست'],
                        ['performance.table.ceo_cash_composition', 'جدول ترکیب واریزی مدیرعامل'],
                        ['performance.table.ceo_product_sales', 'جدول محصولات فروخته‌شده مدیرعامل'],
                        ['performance.table.ceo_marketing_sources', 'جدول گزارشات مارکتینگ'],
                        ['performance.table.ceo_agent_sales', 'جدول فروش کارشناسان مدیرعامل'],
                        ['performance.table.ceo_registered_deals_sources', 'جدول معاملات و شیوه آشنایی'],
                        ['performance.table.ceo_deal_status', 'جدول وضعیت معاملات مدیرعامل'],
                        ['performance.table.ceo_access_grants', 'جدول دسترسی‌های داده‌شده'],
                        ['performance.table.ceo_cheque_status', 'جدول وضعیت کل چک‌ها'],
                    ]},
                ]},
            ]},
            { id: 'finance', label: 'مالی و خروجی', cards: [
                { id: 'payments', label: 'پرداخت‌ها', items: [
                    ['payments.create', 'ایجاد پرداخت'],
                    ['payments.edit', 'ویرایش پرداخت'],
                    ['payments.delete', 'حذف پرداخت'],
                    ['payments.owner_override', 'تغییر مالک قفل‌شده پرداخت'],
                    ['financial_payments.view', 'مشاهده پرداخت‌های مالی'],
                    ['financial_payments.sepidar', 'ثبت پرداخت در سپیدار'],
                ]},
                { id: 'cheques', label: 'چک‌ها', items: [
                    ['cheques.view', 'مشاهده چک‌ها'],
                    ['cheques.review', 'تعیین وضعیت چک'],
                    ['cheques.collect', 'تعیین وضعیت وصول'],
                ]},
                { id: 'exports', label: 'خروجی‌ها', items: [
                    ['export.catalog', 'مشاهده بخش خروجی‌ها'],
                    ['export.dataset.leads', 'خروجی لیدها'],
                    ['export.dataset.deals', 'خروجی معاملات'],
                    ['export.dataset.payments', 'خروجی پرداخت‌ها'],
                ]},
                { id: 'deal_finance', label: 'عملیات مالی معامله', items: [
                    ['deals.reconcile', 'مغایرت‌گیری معاملات'],
                    ['deals.force_success', 'اجبار معامله به موفق'],
                    ['deals.reactivate', 'فعال‌سازی مجدد معامله'],
                ]},
            ]},
            { id: 'admin', label: 'کاربران و تنظیمات', cards: [
                { id: 'users', label: 'کاربران', items: [
                    ['users.view', 'مشاهده کاربران'],
                    ['users.create', 'ایجاد کاربر'],
                    ['users.edit', 'ویرایش کاربر'],
                    ['users.delete', 'حذف کاربر'],
                    ['users.roles.edit', 'ویرایش نقش کاربران'],
                    ['users.passwords.change', 'تغییر رمز کاربران'],
                    ['users.transfer_data', 'انتقال داده کاربر'],
                    ['users.sync_didar', 'همگام‌سازی کاربران CRM خارجی'],
                    ['users.scope_options.view', 'مشاهده کاربران برای محدوده‌ها'],
                ]},
                { id: 'field_config', label: 'تنظیمات فیلدها', items: [
                    ['field_config.view', 'مشاهده تنظیمات فیلدها'],
                    ['field_config.manage', 'مدیریت تنظیمات فیلدها'],
                ]},
                { id: 'lookups', label: 'Lookups', items: [
                    ['lookups.view', 'مشاهده Lookups'],
                    ['lookups.manage', 'مدیریت Lookups'],
                ]},
                { id: 'settings', label: 'تنظیمات سیستم', items: [
                    ['settings.didar_write.manage', 'مدیریت تنظیمات نوشتن در CRM خارجی'],
                    ['settings.auto_migration.manage', 'مدیریت تنظیمات مهاجرت خودکار'],
                    ['settings.external_api.manage', 'مدیریت تنظیمات API خارجی'],
                    ['settings.master_password.manage', 'مدیریت رمز مادر'],
                ]},
                { id: 'notifications', label: 'اعلان‌ها', items: [
                    ['notifications.view', 'مشاهده اعلان‌ها'],
                    ['notifications.manage', 'مدیریت اعلان‌ها'],
                ]},
            ]},
            { id: 'system', label: 'عملیات سیستمی', cards: [
                { id: 'audit_logs', label: 'لاگ‌ها', items: [
                    ['audit_logs.view', 'مشاهده لاگ‌ها'],
                    ['audit_logs.recover', 'بازیابی رکوردهای لاگ'],
                ]},
                { id: 'activity_repair', label: 'تعمیر اطلاعات', items: [
                    ['activity_repair.preview', 'پیش‌نمایش تعمیر فعالیت‌ها'],
                    ['activity_repair.run', 'اجرای تعمیر فعالیت‌ها'],
                ]},
                { id: 'migration', label: 'مهاجرت داده', items: [
                    ['migration.preview', 'پیش‌نمایش مهاجرت'],
                    ['migration.run', 'اجرای مهاجرت'],
                ]},
                { id: 'maintenance', label: 'نگهداری دیتابیس', items: [
                    ['maintenance.view', 'مشاهده نگهداری دیتابیس'],
                    ['maintenance.backup', 'ساخت بکاپ دیتابیس'],
                    ['maintenance.restore', 'بازگردانی بکاپ دیتابیس'],
                    ['maintenance.backfill.preview', 'پیش‌نمایش backfill دفتر پرداخت / کد تفصیلی'],
                    ['maintenance.backfill.run', 'اجرای backfill دفتر پرداخت / کد تفصیلی'],
                    ['maintenance.backfill.verify', 'اعتبارسنجی backfill دفتر پرداخت / کد تفصیلی'],
                ]},
                { id: 'sync', label: 'همگام‌سازی', items: [
                    ['sync.run', 'اجرای Sync'],
                ]},
                { id: 'operation_manager', label: 'مدیر عملیات', items: [
                    ['operation_manager.view', 'مشاهده مدیر عملیات'],
                    ['operation_manager.preview', 'پیش‌نمایش عملیات'],
                    ['operation_manager.execute', 'اجرای عملیات'],
                    ['operation_manager.rules.view', 'مشاهده قوانین عملیات'],
                    ['operation_manager.rules.manage', 'مدیریت قوانین عملیات'],
                    ['operation_manager.automation.run', 'اجرای اتوماسیون عملیات'],
                ]},
            ]},
            { id: 'menus', label: 'منوها', cards: [
                { id: 'menus_main', label: 'منوهای اصلی', items: [
                    ['menu.dashboard', 'منوی داشبورد'],
                    ['menu.leads', 'منوی لیدها'],
                    ['menu.deals', 'منوی معاملات'],
                    ['menu.referrals', 'منوی ارجاع‌ها'],
                    ['menu.persons', 'منوی اشخاص'],
                    ['menu.reminders', 'منوی یادآورها'],
                    ['menu.settings', 'منوی تنظیمات'],
                    ['menu.lookups', 'منوی Lookups'],
                    ['menu.exports', 'منوی خروجی‌ها'],
                    ['menu.audit_logs', 'منوی لاگ‌ها'],
                    ['menu.users', 'منوی کاربران'],
                    ['menu.maintenance', 'منوی نگهداری دیتابیس'],
                    ['menu.customer_access', 'منوی درخواست‌های دسترسی مشتری'],
                ]},
                { id: 'menus_admin', label: 'منوهای مدیریتی', items: [
                    ['menu.performance_dashboard', 'منوی داشبورد عملکرد'],
                    ['menu.kpi_dashboard', 'منوی داشبورد KPI'],
                    ['menu.operation_manager', 'منوی مدیر عملیات'],
                    ['menu.legacy_deals', 'منوی معاملات قدیمی'],
                    ['menu.blataklif_leads', 'منوی لیدهای بلاتکلیف'],
                    ['menu.cheques', 'منوی مدیریت پرداخت و چک'],
                    ['menu.commissions', 'منوی محاسبه پورسانت'],
                ]},
            ]},
            { id: 'advanced', label: 'پیشرفته', cards: [
                { id: 'legacy_alias', label: 'Legacy Alias', items: [
                    ['action.leads.create', 'Alias: ایجاد لید'],
                    ['action.leads.edit', 'Alias: ویرایش لید'],
                    ['action.leads.delete', 'Alias: حذف لید'],
                    ['action.deals.create', 'Alias: ایجاد معامله'],
                    ['action.deals.edit', 'Alias: ویرایش معامله'],
                    ['action.deals.delete', 'Alias: حذف معامله'],
                    ['action.payments.create', 'Alias: ایجاد پرداخت'],
                    ['action.payments.edit', 'Alias: ویرایش پرداخت'],
                    ['action.payments.delete', 'Alias: حذف پرداخت'],
                ]},
            ]},
        ];
    },
    rbacSensitivitySets() {
        if (!this._rbacSensSets) {
            this._rbacSensSets = {
                critical: new Set([
                    'deals.force_success', 'export.dataset.payments', 'payments.delete', 'payments.owner_override',
                    'users.passwords.change', 'users.transfer_data', 'users.roles.edit',
                    'settings.master_password.manage', 'migration.run', 'activity_repair.run',
                    'operation_manager.execute', 'operation_manager.automation.run', 'audit_logs.recover',
                ]),
                sensitive: new Set([
                    'deals.delete', 'deals.reactivate', 'deals.reconcile',
                    'leads.lookup_phone', 'leads.delete', 'leads.transfer_owner', 'leads.backfill_products',
                    'lead_pools.assign', 'lead_pools.manage',
                    'payments.edit', 'export.dataset.leads', 'export.dataset.deals',
                    'users.delete', 'users.sync_didar',
                    'field_config.manage', 'lookups.manage', 'notifications.manage',
                    'settings.didar_write.manage', 'settings.auto_migration.manage', 'settings.external_api.manage',
                    'sync.run', 'operation_manager.rules.manage',
                    'products.manage', 'products.service_links.manage',
                    'activities.delete', 'support.delete',
                    'kpi.drilldown', 'kpi.targets.manage', 'kpi.deals.details', 'performance.drilldown',
                ]),
            };
        }
        return this._rbacSensSets;
    },
    rbacSensitivityFor(key) {
        const sets = this.rbacSensitivitySets();
        const k = String(key);
        if (sets.critical.has(k)) return 'critical';
        if (sets.sensitive.has(k)) return 'sensitive';
        return 'normal';
    },
    rbacActionFor(key) {
        const k = String(key);
        if (k.startsWith('menu.')) return 'read';
        if (k === 'export.catalog') return 'read';
        if (k.endsWith('.view')) return 'read';
        if (k.endsWith('.create')) return 'create';
        if (k.endsWith('.edit')) return 'edit';
        if (k.endsWith('.delete')) return 'delete';
        if (k.endsWith('.manage')) return 'manage';
        return 'special';
    },
    // Classify a capability as read-only viewing, a write/manage action, or a
    // delete. Suffix-only classification (rbacActionFor) misses read-only detail
    // perms (kpi.drilldown, kpi.deals.details, performance.drilldown,
    // leads.lookup_phone, export.dataset.*, *.preview), which are "special" and
    // therefore land in neither the view nor manage quick-toggle group. This
    // mirrors RbacCatalog::defaultGrants()'s read-only rule: a capability is a
    // write unless it only reads. delete is split out so it is full-only.
    rbacKindFor(key) {
        const k = String(key);
        if (k.endsWith('.delete')) return 'delete';
        // Mutation verbs as dot-delimited segments, plus the compound special
        // write actions that don't end in a generic verb.
        const WRITE = /(^|\.)(create|edit|manage|run|recover|change|reactivate|reconcile|force_success|transfer_owner|transfer_data|backfill_products|sync_didar|assign|claim)(\.|$)/;
        return WRITE.test(k) ? 'write' : 'read';
    },
    // Resolve the static tab/card map against the live catalog. Only catalog-present keys
    // render; any catalog key not placed anywhere lands in the Advanced "other" card so
    // no permission is ever lost.
    buildRbacResolvedView() {
        const caps = Array.isArray(this.state.rbacCatalog?.capabilities) ? this.state.rbacCatalog.capabilities : [];
        const catalogKeys = new Set();
        caps.forEach(c => { const k = String(c.capability_key || ''); if (k) catalogKeys.add(k); });
        const tabs = this.rbacTabDefinitions();
        const knownKeys = new Set();
        tabs.forEach(t => t.cards.forEach(card => {
            (card.items || []).forEach(([k]) => knownKeys.add(k));
            (card.groups || []).forEach(g => (g.items || []).forEach(([k]) => knownKeys.add(k)));
        }));
        const filterItems = (items) => (items || []).filter(([k]) => catalogKeys.has(k));
        const resolved = tabs.map(t => {
            const cards = t.cards.map(card => {
                if (card.groups) {
                    const groups = card.groups
                        .map(g => ({ label: g.label, items: filterItems(g.items) }))
                        .filter(g => g.items.length);
                    const total = groups.reduce((n, g) => n + g.items.length, 0);
                    return total ? { id: card.id, label: card.label, groups } : null;
                }
                const items = filterItems(card.items);
                return items.length ? { id: card.id, label: card.label, items } : null;
            }).filter(Boolean);
            return { id: t.id, label: t.label, cards };
        });
        const otherItems = [];
        caps.forEach(c => {
            const k = String(c.capability_key || '');
            if (!k || knownKeys.has(k)) return;
            otherItems.push([k, String(c.title || k)]);
        });
        if (otherItems.length) {
            otherItems.sort((a, b) => a[0].localeCompare(b[0]));
            let adv = resolved.find(t => t.id === 'advanced');
            if (!adv) { adv = { id: 'advanced', label: 'پیشرفته', cards: [] }; resolved.push(adv); }
            adv.cards.push({ id: 'other', label: 'سایر دسترسی‌ها', items: otherItems });
        }
        return resolved.filter(t => t.cards.length);
    },
    rbacCardAllKeys(card) {
        const keys = [];
        (card.items || []).forEach(([k]) => keys.push(k));
        (card.groups || []).forEach(g => (g.items || []).forEach(([k]) => keys.push(k)));
        return keys;
    },
    parseRbacCapabilityKeys(raw) {
        if (Array.isArray(raw)) return raw.map(v => String(v)).filter(Boolean);
        const text = String(raw || '').trim();
        if (!text) return [];
        try {
            const decoded = JSON.parse(text);
            return Array.isArray(decoded) ? decoded.map(v => String(v)).filter(Boolean) : [];
        } catch (e) {
            return text.split(',').map(v => v.trim()).filter(Boolean);
        }
    },
    getRbacWorkingCapabilitySet() {
        if (!(this.state.rbacWorkingCaps instanceof Set)) {
            this.state.rbacWorkingCaps = new Set((this.state.rbacSelectedCapabilityKeys || []).map(v => String(v)));
        }
        return this.state.rbacWorkingCaps;
    },
    updateRbacSelectedCapabilityKeys(keys, checked) {
        const selected = this.getRbacWorkingCapabilitySet();
        this.parseRbacCapabilityKeys(keys).forEach(key => {
            if (checked) {
                selected.add(key);
            } else {
                selected.delete(key);
            }
        });
    },
    syncRbacCapabilityCheckboxes(keys, checked) {
        const keySet = new Set(this.parseRbacCapabilityKeys(keys));
        if (!keySet.size) return;
        document.querySelectorAll('.rbac-capability-checkbox').forEach(el => {
            if (keySet.has(el.value)) el.checked = checked;
        });
    },
    setRbacActiveTab(tabId) {
        this.state.rbacActiveTab = tabId;
        this.renderLookupRbacCapabilities();
    },
    setRbacCapabilityFilter(mode) {
        this.state.rbacCapabilityFilter = mode;
        this.renderLookupRbacCapabilities();
    },
    highlightRbacFilterChips() {
        const active = this.state.rbacCapabilityFilter || 'all';
        document.querySelectorAll('[data-rbac-filter-btn]').forEach(btn => {
            const on = (btn.dataset.rbacFilterBtn === active);
            btn.classList.toggle('bg-cyan-600', on && active !== 'sensitive');
            btn.classList.toggle('bg-rose-600', on && active === 'sensitive');
            btn.classList.toggle('text-white', on);
            btn.classList.toggle('border-transparent', on);
            btn.classList.toggle('text-gray-300', !on);
        });
    },
    // The keys that belong to each quick-toggle GROUP for a card. The groups are
    // independent and non-overlapping (except none/full, which are all/nothing):
    //   none   = [] (clear everything)
    //   view   = every read-only perm (incl. drilldown/details/lookup specials), non-critical
    //   manage = every write/manage perm (create/edit/manage + write specials), non-critical
    //   full   = all keys
    // delete and critical perms are reachable only via full.
    rbacGroupKeys(allKeys, group) {
        if (group === 'full') return allKeys.slice();
        if (group === 'view') return allKeys.filter(k =>
            this.rbacKindFor(k) === 'read' && this.rbacSensitivityFor(k) !== 'critical');
        if (group === 'manage') return allKeys.filter(k =>
            this.rbacKindFor(k) === 'write' && this.rbacSensitivityFor(k) !== 'critical');
        return []; // none
    },
    // none/full always apply. view/manage apply only when the card actually has
    // perms of that kind (otherwise the button has nothing to toggle).
    rbacGroupApplicable(allKeys, group) {
        if (!allKeys.length) return false;
        if (group === 'none' || group === 'full') return true;
        return this.rbacGroupKeys(allKeys, group).length > 0;
    },
    // A button is "active" when its group is fully selected. Groups are
    // independent, so several buttons can be active at once (e.g. view + manage).
    rbacGroupActive(allKeys, selected, group) {
        const onCount = allKeys.filter(k => selected.has(k)).length;
        if (group === 'none') return onCount === 0;
        if (group === 'full') return allKeys.length > 0 && onCount === allKeys.length;
        const g = this.rbacGroupKeys(allKeys, group);
        return g.length > 0 && g.every(k => selected.has(k));
    },
    // Click a quick-toggle button. none clears all, full selects all, and
    // view/manage independently toggle their own group on/off WITHOUT touching
    // the other group's selections.
    rbacApplyCardPreset(cardUid, group) {
        const keys = (this._rbacCardKeyIndex || {})[cardUid] || [];
        if (!keys.length || !this.rbacGroupApplicable(keys, group)) return;
        if (group === 'none') {
            this.updateRbacSelectedCapabilityKeys(keys, false);
        } else if (group === 'full') {
            this.updateRbacSelectedCapabilityKeys(keys, true);
        } else {
            const selected = this.getRbacWorkingCapabilitySet();
            const g = this.rbacGroupKeys(keys, group);
            const allOn = g.every(k => selected.has(k));
            this.updateRbacSelectedCapabilityKeys(g, !allOn);
        }
        this.renderLookupRbacCapabilities();
        this.updateRbacRoleSummary();
    },
    rbacOnPermToggle(key, checked, cardUid, inputEl = null) {
        this.updateRbacSelectedCapabilityKeys([key], checked);
        const cb = inputEl || Array.from(document.querySelectorAll('.rbac-capability-checkbox')).find(el => el.value === key);
        cb?.closest('.rbac-perm-row')?.classList.toggle('is-selected', !!checked);
        if ((this.state.rbacCapabilityFilter || 'all') !== 'all') {
            this.renderLookupRbacCapabilities();
        } else {
            this.refreshRbacCardMeta(cardUid);
        }
        this.updateRbacRoleSummary();
    },
    // Per-card quick-toggle buttons. Each inactive button shares one muted gray
    // look (see .rbac-preset-btn in main.css); active buttons are colored per id
    // via .preset-<id>. Several can be active simultaneously.
    rbacPresetDefs() {
        return [
            { id: 'none', label: 'هیچ‌کدام' },
            { id: 'view', label: 'مشاهده' },
            { id: 'manage', label: 'مدیریت' },
            { id: 'full', label: 'کامل' },
        ];
    },
    // state: 'active' | 'inactive'. All visuals live in main.css (.rbac-preset-btn)
    // because the purged Tailwind build lacks the needed color utilities. The
    // disabled look is driven by the button's disabled attribute (:disabled).
    rbacPresetBtnClass(state, def) {
        return state === 'active' ? `rbac-preset-btn is-active preset-${def.id}` : 'rbac-preset-btn';
    },
    refreshRbacCardMeta(cardUid) {
        const keys = (this._rbacCardKeyIndex || {})[cardUid] || [];
        const selected = this.getRbacWorkingCapabilitySet();
        const on = keys.filter(k => selected.has(k)).length;
        const el = document.querySelector(`[data-rbac-card-count="${cardUid}"]`);
        if (el) el.textContent = `${on}/${keys.length}`;
        // Recompute each button's active state independently. Disabled buttons
        // have nothing to toggle, so skip them.
        this.rbacPresetDefs().forEach(def => {
            const btn = document.querySelector(`[data-rbac-preset="${cardUid}"][data-preset-id="${def.id}"]`);
            if (!btn || btn.disabled) return;
            const isActive = this.rbacGroupActive(keys, selected, def.id);
            btn.className = this.rbacPresetBtnClass(isActive ? 'active' : 'inactive', def);
            btn.setAttribute('aria-pressed', String(isActive));
        });
    },
    renderLookupRbacCapabilities() {
        const view = this.state.rbacCapsView || 'matrix';
        if (view === 'list') this.renderRbacCapabilityList();
        else this.renderRbacCapabilityMatrix();
    },
    setRbacCapsView(view) {
        view = (view === 'list') ? 'list' : 'matrix';
        this.state.rbacCapsView = view;
        const matrixEl = document.getElementById('rbac-capabilities-matrix');
        const listEl = document.getElementById('rbac-capabilities-container');
        if (matrixEl) matrixEl.classList.toggle('hidden', view !== 'matrix');
        if (listEl) listEl.classList.toggle('hidden', view !== 'list');
        this._syncRbacCapsViewButtons();
        this.renderLookupRbacCapabilities();
    },
    _syncRbacCapsViewButtons() {
        const view = this.state.rbacCapsView || 'matrix';
        const set = (el, active) => {
            if (!el) return;
            el.classList.toggle('bg-cyan-600', active);
            el.classList.toggle('text-white', active);
            el.classList.toggle('bg-gray-800', !active);
            el.classList.toggle('text-gray-300', !active);
            el.setAttribute('aria-selected', active ? 'true' : 'false');
        };
        set(document.getElementById('rbac-caps-view-matrix-btn'), view === 'matrix');
        set(document.getElementById('rbac-caps-view-list-btn'), view === 'list');
    },
    // ---- shared helpers over the authoritative working set (this.state.rbacWorkingCaps) ----
    rbacCellState(keys, sel) {
        if (!keys || !keys.length) return 'empty';
        let on = 0;
        keys.forEach(k => { if (sel.has(k)) on++; });
        if (on === 0) return 'off';
        if (on === keys.length) return 'on';
        return 'mixed';
    },
    _rbacCountGranted(keys, sel) {
        let n = 0;
        (keys || []).forEach(k => { if (sel.has(k)) n++; });
        return n;
    },
    _rbacCapTitleMap() {
        const map = {};
        (this.state.rbacCatalog?.capabilities || []).forEach(c => { map[String(c.capability_key || '')] = String(c.title || ''); });
        return map;
    },
    _rbacModuleMatchesSearch(mod, search, capTitle) {
        if (!search) return true;
        if (String(mod.title || '').toLowerCase().includes(search)) return true;
        if (String(mod.key || '').toLowerCase().includes(search)) return true;
        return (mod.all_keys || []).some(k => k.toLowerCase().includes(search) || String(capTitle[k] || '').toLowerCase().includes(search));
    },
    _rbacDetectPreset(mod, sel, presets) {
        const granted = new Set((mod.all_keys || []).filter(k => sel.has(k)));
        for (const p of (presets || [])) {
            const expect = new Set();
            (p.actions || []).forEach(a => {
                const keys = a === 'advanced' ? (mod.advanced || []).map(x => x.key) : (mod.cells?.[a] || []);
                keys.forEach(k => expect.add(k));
            });
            if (expect.size === granted.size && [...expect].every(k => granted.has(k))) return p.key;
        }
        return '';
    },
    _rbacCellHtml(moduleKey, action, keys, sel, actionTitle, moduleTitle) {
        if (!keys || !keys.length) {
            return `<td class="rbac-cell"><span class="rbac-empty" title="این ماژول عملیات «${this.escapeHtml(actionTitle)}» را ندارد">—</span></td>`;
        }
        const state = this.rbacCellState(keys, sel);
        const cls = state === 'on' ? 'is-on' : (state === 'mixed' ? 'is-mixed' : 'is-off');
        const aria = state === 'on' ? 'true' : (state === 'mixed' ? 'mixed' : 'false');
        const icon = state === 'on' ? 'fa-check' : (state === 'mixed' ? 'fa-minus' : '');
        return `<td class="rbac-cell">
            <button type="button" class="rbac-chk rbac-act-${action} ${cls}" role="checkbox" aria-checked="${aria}"
                title="${this.escapeHtml(actionTitle)} — ${this.escapeHtml(moduleTitle)}"
                onclick="app.toggleRbacMatrixCell('${moduleKey}','${action}')">
                ${icon ? `<i class="fa-solid ${icon}"></i>` : ''}
            </button>
        </td>`;
    },
    renderRbacCapabilityMatrix() {
        const host = document.getElementById('rbac-capabilities-matrix');
        if (!host) {
            // Matrix markup isn't present in this view -> fall back to the flat list,
            // which is the only capability surface rendered in the RBAC manager HTML.
            this.state.rbacCapsView = 'list';
            const listEl = document.getElementById('rbac-capabilities-container');
            if (listEl) listEl.classList.remove('hidden');
            this._syncRbacCapsViewButtons();
            this.renderRbacCapabilityList();
            return;
        }
        const roleKey = this.state.rbacSelectedRoleKey || '';
        if (!roleKey) {
            host.innerHTML = '<div class="text-center text-gray-500 text-xs py-10">ابتدا یک نقش انتخاب کنید</div>';
            return;
        }
        const matrix = this.state.rbacCatalog?.matrix;
        if (!matrix || !Array.isArray(matrix.groups) || !matrix.groups.length) {
            // Backend without matrix support -> degrade gracefully to the flat list.
            this.state.rbacCapsView = 'list';
            const matrixEl = document.getElementById('rbac-capabilities-matrix');
            const listEl = document.getElementById('rbac-capabilities-container');
            if (matrixEl) matrixEl.classList.add('hidden');
            if (listEl) listEl.classList.remove('hidden');
            this._syncRbacCapsViewButtons();
            this.renderRbacCapabilityList();
            return;
        }
        const sel = this.getRbacWorkingCapabilitySet();
        const actions = Array.isArray(matrix.actions) ? matrix.actions : [];
        const presets = Array.isArray(matrix.presets) ? matrix.presets : [];
        const search = (document.getElementById('rbac-capabilities-search')?.value || '').trim().toLowerCase();
        const capTitle = this._rbacCapTitleMap();
        const expanded = this.state.rbacMatrixExpanded instanceof Set ? this.state.rbacMatrixExpanded : (this.state.rbacMatrixExpanded = new Set());
        const actionIcon = { read: 'fa-eye', create: 'fa-plus', update: 'fa-pen', delete: 'fa-trash' };
        const index = {};
        const visible = [];
        const colCount = actions.length + 3;

        const thead = `<tr>
            <th class="rbac-mh rbac-mh-mod">ماژول</th>
            ${actions.map(a => `<th class="rbac-mh rbac-mh-${a.key}">
                <div class="rbac-mh-act" title="${this.escapeHtml(a.desc || '')}"><i class="fa-solid ${actionIcon[a.key] || 'fa-circle'}"></i><span>${this.escapeHtml(a.title)}</span></div>
                <button type="button" class="rbac-colall" title="انتخاب/حذف کل این ستون" onclick="app.toggleRbacMatrixColumn('${a.key}')"><i class="fa-solid fa-arrows-up-down"></i></button>
            </th>`).join('')}
            <th class="rbac-mh rbac-mh-adv">پیشرفته</th>
            <th class="rbac-mh rbac-mh-preset">پیش‌تنظیم</th>
        </tr>`;

        let bodyRows = '';
        matrix.groups.forEach(group => {
            const groupModules = (group.modules || []).filter(mod => this._rbacModuleMatchesSearch(mod, search, capTitle));
            if (!groupModules.length) return;
            bodyRows += `<tr class="rbac-grouprow"><td colspan="${colCount}">${this.escapeHtml(group.title)}</td></tr>`;
            groupModules.forEach(mod => {
                index[mod.key] = mod;
                visible.push(mod.key);
                const advKeys = (mod.advanced || []).map(a => a.key);
                const advState = this.rbacCellState(advKeys, sel);
                const cells = actions.map(a => this._rbacCellHtml(mod.key, a.key, mod.cells?.[a.key] || [], sel, a.title, mod.title)).join('');
                const presetVal = this._rbacDetectPreset(mod, sel, presets);
                const advBtn = advKeys.length
                    ? `<button type="button" class="rbac-adv-btn ${advState === 'on' ? 'is-on' : (advState === 'mixed' ? 'is-mixed' : 'is-off')}" onclick="app.toggleRbacMatrixAdvanced('${mod.key}')" title="نمایش دسترسی‌های پیشرفته">
                           <span>${this._rbacCountGranted(advKeys, sel)}/${advKeys.length}</span><i class="fa-solid ${expanded.has(mod.key) ? 'fa-chevron-up' : 'fa-chevron-down'}"></i>
                       </button>`
                    : '<span class="rbac-empty">—</span>';
                const presetSel = `<select class="rbac-preset-sel" title="اعمال پیش‌تنظیم روی این ماژول" onchange="app.applyRbacMatrixPreset('${mod.key}', this.value)">
                        <option value="">${presetVal ? '— سفارشی' : 'سفارشی'}</option>
                        ${presets.map(p => `<option value="${p.key}" ${presetVal === p.key ? 'selected' : ''}>${this.escapeHtml(p.title)}</option>`).join('')}
                    </select>`;
                bodyRows += `<tr class="rbac-modrow">
                    <td class="rbac-modcell"><div class="rbac-modtitle">${this.escapeHtml(mod.title)}</div><div class="rbac-modkey">${this.escapeHtml(mod.key)}</div></td>
                    ${cells}
                    <td class="rbac-cell">${advBtn}</td>
                    <td class="rbac-cell rbac-presetcell">${presetSel}</td>
                </tr>`;
                if (expanded.has(mod.key) && advKeys.length) {
                    const advItems = (mod.advanced || []).map(a => {
                        const on = sel.has(a.key) ? 'checked' : '';
                        return `<label class="rbac-advitem">
                            <input type="checkbox" class="accent-cyan-500 rbac-capability-checkbox" value="${this.escapeHtml(a.key)}" ${on} onchange="app.onRbacListCapToggle(this)">
                            <span class="rbac-advitem-title">${this.escapeHtml(a.title)}</span><span class="rbac-advitem-key">${this.escapeHtml(a.key)}</span>
                        </label>`;
                    }).join('');
                    bodyRows += `<tr class="rbac-advrow"><td colspan="${colCount}"><div class="rbac-advgrid">${advItems}</div></td></tr>`;
                }
            });
        });

        this.state.rbacMatrixIndex = index;
        this.state.rbacMatrixVisibleModules = visible;
        if (!bodyRows) {
            host.innerHTML = '<div class="text-center text-gray-500 text-xs py-10">چیزی یافت نشد</div>';
            return;
        }
        host.innerHTML = `<div class="rbac-matrix-scroll"><table class="rbac-matrix"><thead>${thead}</thead><tbody>${bodyRows}</tbody></table></div>
            <div class="rbac-matrix-legend">
                <span><i class="fa-solid fa-check rbac-lg-on"></i> دارد</span>
                <span><i class="fa-solid fa-minus rbac-lg-mixed"></i> جزئی</span>
                <span class="rbac-lg-muted">— این عملیات برای ماژول تعریف نشده</span>
            </div>`;
    },
    renderRbacCapabilityList() {
        const container = document.getElementById('rbac-capabilities-container');
        if (!container) return;
        const roleKey = this.state.rbacSelectedRoleKey || '';
        if (!roleKey) {
            container.innerHTML = '<div class="text-center text-gray-500 text-xs py-6">ابتدا یک نقش انتخاب کنید</div>';
            this.highlightRbacFilterChips();
            return;
        }
        const search = (document.getElementById('rbac-capabilities-search')?.value || '').trim().toLowerCase();

        const filter = this.state.rbacCapabilityFilter || 'all';
        const selected = this.getRbacWorkingCapabilitySet();
        const view = this.buildRbacResolvedView();
        if (!view.length) {
            container.innerHTML = '<div class="text-center text-gray-500 text-xs py-6">دسترسی‌ای برای نمایش نیست</div>';
            this.highlightRbacFilterChips();
            return;
        }
        let activeTab = this.state.rbacActiveTab;
        if (!view.some(t => t.id === activeTab)) activeTab = view[0].id;
        this.state.rbacActiveTab = activeTab;

        // Per-item visibility under search + filter (presets/counts still use ALL card keys).
        const itemVisible = (key, label) => {
            if (filter === 'active' && !selected.has(key)) return false;
            if (filter === 'sensitive' && this.rbacSensitivityFor(key) === 'normal') return false;
            if (search) {
                const lbl = String(label || '').toLowerCase();
                if (!key.toLowerCase().includes(search) && !lbl.includes(search)) return false;
            }
            return true;
        };

        // Tab bar with per-tab selected counts.
        const tabBar = view.map(t => {
            const tabKeys = [];
            t.cards.forEach(card => this.rbacCardAllKeys(card).forEach(k => tabKeys.push(k)));
            const on = tabKeys.filter(k => selected.has(k)).length;
            const isActive = t.id === activeTab;
            return `<button type="button" onclick="app.setRbacActiveTab('${t.id}')" aria-pressed="${isActive}"
                class="rbac-tab${isActive ? ' is-active' : ''}">
                ${this.escapeHtml(t.label)}${on ? ` <span class="rbac-tab-count">(${on})</span>` : ''}
            </button>`;
        }).join('');

        const tab = view.find(t => t.id === activeTab);
        this._rbacCardKeyIndex = {};

        const badgeHtml = (key) => {
            const s = this.rbacSensitivityFor(key);
            if (s === 'critical') return '<span class="rbac-badge px-1.5 py-0.5 rounded text-[9px] font-bold bg-rose-500/20 text-rose-300 border border-rose-500/40">خیلی حساس</span>';
            if (s === 'sensitive') return '<span class="rbac-badge px-1.5 py-0.5 rounded text-[9px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/40">حساس</span>';
            return '';
        };
        const permRow = (cardUid, key, label) => {
            const checked = selected.has(key) ? 'checked' : '';
            const esKey = this.escapeHtml(key);
            return `<label class="rbac-perm-row ${checked ? 'is-selected' : ''}">
                <span class="rbac-cell">
                    <span class="rbac-labelwrap">
                        <span class="rbac-label" title="${this.escapeHtml(label)}">${this.escapeHtml(label)}</span>
                        ${badgeHtml(key)}
                    </span>
                    <span class="rbac-key" title="${esKey}">${esKey}</span>
                </span>
                <input type="checkbox" class="rbac-cb accent-cyan-500 rbac-capability-checkbox"
                    value="${esKey}"
                    onchange="app.rbacOnPermToggle(this.value, this.checked, '${cardUid}', this)"
                    ${checked}>
            </label>`;
        };

        const renderCard = (card) => {
            const cardUid = `${activeTab}::${card.id}`;
            const allKeys = this.rbacCardAllKeys(card);
            this._rbacCardKeyIndex[cardUid] = allKeys;
            const onCount = allKeys.filter(k => selected.has(k)).length;
            const presetBtns = this.rbacPresetDefs().map(def => {
                const applicable = this.rbacGroupApplicable(allKeys, def.id);
                const isActive = applicable && this.rbacGroupActive(allKeys, selected, def.id);
                const attrs = applicable
                    ? `onclick="app.rbacApplyCardPreset('${cardUid}','${def.id}')"`
                    : `disabled title="برای این بخش موردی برای اعمال وجود ندارد"`;
                return `<button type="button" data-rbac-preset="${cardUid}" data-preset-id="${def.id}"
                    aria-pressed="${isActive}" ${attrs}
                    class="${this.rbacPresetBtnClass(isActive ? 'active' : 'inactive', def)}">${def.label}</button>`;
            }).join('');

            // Build body honoring search/filter; track whether anything is visible.
            let visibleCount = 0;
            let body = '';
            if (card.groups) {
                body = card.groups.map(g => {
                    const rows = g.items.filter(([k, l]) => itemVisible(k, l));
                    if (!rows.length) return '';
                    visibleCount += rows.length;
                    return `<details class="rounded-lg border border-gray-800 bg-gray-950/20" open>
                        <summary class="cursor-pointer px-2.5 py-1.5 text-[11px] font-bold text-cyan-300">${this.escapeHtml(g.label)} <span class="text-gray-500">(${rows.length})</span></summary>
                        <div class="grid grid-cols-1 md:grid-cols-2 gap-2 p-2 border-t border-gray-800">
                            ${rows.map(([k, l]) => permRow(cardUid, k, l)).join('')}
                        </div>
                    </details>`;
                }).join('');
                body = `<div class="space-y-2">${body}</div>`;
            } else {
                const rows = card.items.filter(([k, l]) => itemVisible(k, l));
                visibleCount = rows.length;
                body = `<div class="grid grid-cols-1 md:grid-cols-2 gap-2">${rows.map(([k, l]) => permRow(cardUid, k, l)).join('')}</div>`;
            }
            if (!visibleCount) return '';

            return `<section class="rounded-xl border border-gray-800 bg-gray-900/40 p-3">
                <div class="mb-2 flex flex-wrap items-center justify-between gap-2">
                    <h6 class="text-sm font-bold text-white">${this.escapeHtml(card.label)}
                        <span class="text-[10px] font-normal text-gray-500" data-rbac-card-count="${cardUid}">${onCount}/${allKeys.length}</span>
                    </h6>
                    <div class="flex items-center gap-1">
                        ${presetBtns}
                    </div>
                </div>
                ${body}
            </section>`;
        };

        const cardsHtml = tab.cards.map(renderCard).join('');
        container.innerHTML = `
            <div class="flex flex-wrap gap-1.5 mb-3 sticky top-0 z-10 bg-gray-900/80 backdrop-blur py-1 -mx-1 px-1 rounded">${tabBar}</div>
            ${cardsHtml
                ? `<div class="grid grid-cols-1 gap-3">${cardsHtml}</div>`
                : '<div class="text-center text-gray-500 text-xs py-6">موردی با این جستجو/فیلتر یافت نشد</div>'}
        `;
        this.highlightRbacFilterChips();
    },
    toggleRbacMatrixCell(moduleKey, action) {
        const mod = this.state.rbacMatrixIndex?.[moduleKey];
        if (!mod) return;
        const keys = action === 'advanced' ? (mod.advanced || []).map(a => a.key) : (mod.cells?.[action] || []);
        if (!keys.length) return;
        const sel = this.getRbacWorkingCapabilitySet();
        if (this.rbacCellState(keys, sel) === 'on') keys.forEach(k => sel.delete(k));
        else keys.forEach(k => sel.add(k));
        this.renderRbacCapabilityMatrix();
        this.updateRbacRoleSummary();
    },
    toggleRbacMatrixColumn(action) {
        const idx = this.state.rbacMatrixIndex || {};
        const visible = this.state.rbacMatrixVisibleModules || Object.keys(idx);
        const sel = this.getRbacWorkingCapabilitySet();
        const allKeys = [];
        visible.forEach(mk => { const mod = idx[mk]; if (mod) (mod.cells?.[action] || []).forEach(k => allKeys.push(k)); });
        if (!allKeys.length) return;
        const everyOn = allKeys.every(k => sel.has(k));
        if (everyOn) allKeys.forEach(k => sel.delete(k));
        else allKeys.forEach(k => sel.add(k));
        this.renderRbacCapabilityMatrix();
        this.updateRbacRoleSummary();
    },
    applyRbacMatrixPreset(moduleKey, presetKey) {
        if (!presetKey) return;
        const mod = this.state.rbacMatrixIndex?.[moduleKey];
        const preset = (this.state.rbacCatalog?.matrix?.presets || []).find(p => p.key === presetKey);
        if (!mod || !preset) return;
        const sel = this.getRbacWorkingCapabilitySet();
        (mod.all_keys || []).forEach(k => sel.delete(k));
        (preset.actions || []).forEach(a => {
            const keys = a === 'advanced' ? (mod.advanced || []).map(x => x.key) : (mod.cells?.[a] || []);
            keys.forEach(k => sel.add(k));
        });
        this.renderRbacCapabilityMatrix();
        this.updateRbacRoleSummary();
    },
    toggleRbacMatrixAdvanced(moduleKey) {
        if (!(this.state.rbacMatrixExpanded instanceof Set)) this.state.rbacMatrixExpanded = new Set();
        const ex = this.state.rbacMatrixExpanded;
        if (ex.has(moduleKey)) ex.delete(moduleKey); else ex.add(moduleKey);
        this.renderRbacCapabilityMatrix();
    },
    onRbacListCapToggle(el) {
        const key = el.value;
        const sel = this.getRbacWorkingCapabilitySet();
        if (el.checked) sel.add(key); else sel.delete(key);
        this.updateRbacRoleSummary();
        if ((this.state.rbacCapsView || 'matrix') === 'matrix') this.renderRbacCapabilityMatrix();
    },
    updateRbacRoleSummary() {
        const el = document.getElementById('rbac-role-summary');
        if (!el) return;
        const role = this.state.rbacCurrentRole;
        if (!role) {
            el.innerHTML = '<i class="fa-regular fa-hand-pointer text-gray-500"></i> یک نقش را برای مدیریت دسترسی‌ها انتخاب کنید';
            return;
        }
        const count = this.getRbacWorkingCapabilitySet().size || 0;
        const sys = String(role.is_system ?? '0') === '1';
        const dirty = this._rbacCapsDirty();
        el.innerHTML = `
            <span class="text-gray-100 font-bold">${this.escapeHtml(role.title || role.role_key || '')}</span>
            <span class="text-gray-600">·</span>
            <span class="text-cyan-300">${count} دسترسی فعال</span>
            ${sys ? '<span class="text-[10px] px-2 py-0.5 rounded bg-gray-700 text-gray-300 mr-1">سیستمی</span>' : ''}
            ${dirty ? '<span class="text-[10px] px-2 py-0.5 rounded bg-amber-600/30 text-amber-300 mr-1"><i class="fa-solid fa-circle-exclamation ml-1"></i>تغییرات ذخیره‌نشده</span>' : ''}
        `;
    },
    _rbacCapsDirty() {
        const orig = new Set((this.state.rbacSelectedCapabilityKeys || []).map(v => String(v)));
        const cur = this.getRbacWorkingCapabilitySet();
        if (orig.size !== cur.size) return true;
        for (const k of cur) if (!orig.has(k)) return true;
        return false;
    },
    renderLookupRbacScopes() {
        const container = document.getElementById('rbac-scopes-container');
        if (!container) return;
        const roleKey = this.state.rbacSelectedRoleKey || '';
        if (!roleKey) {
            container.innerHTML = '<div class="text-center text-gray-500 text-xs py-6 border border-gray-800 rounded-lg bg-gray-900/30">ابتدا یک نقش انتخاب کنید</div>';
            return;
        }
        const scopeMap = {};
        (this.state.rbacSelectedScopes || []).forEach(s => { scopeMap[String(s.resource_key || '')] = s; });
        const users = Array.isArray(this.state.rbacUsersForScopes) ? this.state.rbacUsersForScopes : [];
        const userOptionsHtml = users.map(u => {
            const id = String(u.id || '');
            if (!id) return '';
            const name = u.display_name || `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || id;
            return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(name)}</option>`;
        }).join('');
        const resourceCatalog = this.state.rbacCatalog?.resources || {};
        const resources = Object.keys(resourceCatalog).length ? Object.keys(resourceCatalog) : ['dashboard', 'leads', 'deals', 'payments', 'kpi', 'performance'];
        const scopeTypes = Array.isArray(this.state.rbacCatalog?.scope_types) ? this.state.rbacCatalog.scope_types : ['none', 'self', 'selected_users', 'all'];
        const typeLabels = {
            none: 'بدون دسترسی داده',
            self: 'فقط خودم',
            all: 'همه کاربران',
            selected_users: 'کاربران منتخب'
        };
        container.innerHTML = resources.map(resourceKey => {
            const meta = resourceCatalog[resourceKey] || {};
            const s = scopeMap[resourceKey] || { resource_key: resourceKey, scope_type: (roleKey === 'admin' ? 'all' : 'self'), selected_user_ids: [] };
            const scopeType = String(s.scope_type || 'self');
            const selectedUsers = new Set((s.selected_user_ids || []).map(v => String(v)));
            return `
                <div class="border border-gray-800 rounded-lg p-3 bg-gray-900/30" data-rbac-scope-resource="${resourceKey}">
                    <div class="text-xs text-gray-200 mb-2 font-bold">${this.escapeHtml(meta.title || resourceKey)}</div>
                    <select class="input bg-gray-800 text-xs mb-2" name="scope_type" onchange="app.toggleRbacScopeUsers(this)">
                        ${scopeTypes.map(type => `<option value="${this.escapeHtml(type)}" ${scopeType === type ? 'selected' : ''}>${this.escapeHtml(typeLabels[type] || type)}</option>`).join('')}
                    </select>
                    <div class="rbac-scope-users-wrap ${scopeType === 'selected_users' ? '' : 'hidden'}">
                        <select class="input bg-gray-800 text-xs w-full" name="selected_user_ids" multiple size="6">
                            ${userOptionsHtml.replace(/<option value=\"([^\"]+)\">/g, (m, id) => `<option value="${id}" ${selectedUsers.has(String(id)) ? 'selected' : ''}>`)}
                        </select>
                    </div>
                </div>
            `;
        }).join('');
    },
    toggleRbacScopeUsers(selectEl) {
        const card = selectEl?.closest('[data-rbac-scope-resource]');
        if (!card) return;
        const wrap = card.querySelector('.rbac-scope-users-wrap');
        if (!wrap) return;
        wrap.classList.toggle('hidden', (selectEl.value || '') !== 'selected_users');
    },
    async saveLookupRbacRole() {
        const roleKey = (document.getElementById('rbac-role-key')?.value || '').trim();
        const title = (document.getElementById('rbac-role-title')?.value || '').trim();
        const id = document.getElementById('rbac-role-id')?.value || '';
        const isActive = document.getElementById('rbac-role-active')?.checked ? '1' : '0';
        if (!roleKey || !title) {
            Swal.fire('خطا', 'role_key و عنوان نقش الزامی است', 'error');
            return;
        }
        const res = await this.req('rbac_save_role', { id, role_key: roleKey, title, is_active: isActive });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره نقش', 'error');
            return;
        }
        this.state.rbacSelectedRoleKey = roleKey;
        await this.loadLookupRbacManagerData(true);
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'نقش ذخیره شد', showConfirmButton: false, timer: 1500 });
    },
    async cloneLookupRbacRole() {
        const newRoleKey = (document.getElementById('rbac-clone-role-key')?.value || '').trim();
        const newTitle = (document.getElementById('rbac-clone-role-title')?.value || '').trim();
        const templateRoleKey = (document.getElementById('rbac-clone-template-role')?.value || '').trim();
        if (!newRoleKey || !newTitle || !templateRoleKey) {
            Swal.fire('خطا', 'فیلدهای ساخت از تمپلیت کامل نیست', 'error');
            return;
        }
        const res = await this.req('rbac_clone_role', {
            new_role_key: newRoleKey,
            new_title: newTitle,
            template_role_key: templateRoleKey
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ساخت نقش از تمپلیت', 'error');
            return;
        }
        this.state.rbacSelectedRoleKey = newRoleKey;
        await this.loadLookupRbacManagerData(true);
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        const sel = document.getElementById('rbac-role-select');
        if (sel) sel.value = newRoleKey;
        await this.onLookupRbacRoleChange();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'نقش جدید ساخته شد', showConfirmButton: false, timer: 1500 });
    },
    async saveLookupRbacCapabilities() {
        const roleKey = this.state.rbacSelectedRoleKey || '';
        if (!roleKey) {
            Swal.fire('خطا', 'ابتدا نقش را انتخاب کنید', 'error');
            return;
        }

        Array.from(document.querySelectorAll('.rbac-capability-checkbox')).forEach(el => {
            this.updateRbacSelectedCapabilityKeys([el.value], el.checked);
        });
        const capabilityKeys = Array.from(this.getRbacWorkingCapabilitySet()).map(v => String(v)).filter(Boolean).sort();
        const res = await this.req('rbac_save_role_capabilities', {
            role_key: roleKey,
            capability_keys: JSON.stringify(capabilityKeys)
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره دسترسی‌ها', 'error');
            return;
        }
        await this.onLookupRbacRoleChange();
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'دسترسی‌ها ذخیره شد', showConfirmButton: false, timer: 1500 });
    },
    async saveLookupRbacScopes() {
        const roleKey = this.state.rbacSelectedRoleKey || '';
        if (!roleKey) {
            Swal.fire('خطا', 'ابتدا نقش را انتخاب کنید', 'error');
            return;
        }
        const cards = Array.from(document.querySelectorAll('[data-rbac-scope-resource]'));
        const scopes = cards.map(card => {
            const resourceKey = card.dataset.rbacScopeResource || '';
            const scopeType = card.querySelector('[name="scope_type"]')?.value || 'self';
            const selectedUserIds = Array.from(card.querySelector('[name="selected_user_ids"]')?.selectedOptions || []).map(o => parseInt(o.value, 10)).filter(v => Number.isFinite(v) && v > 0);
            return { resource_key: resourceKey, scope_type: scopeType, selected_user_ids: selectedUserIds };
        });
        const res = await this.req('rbac_save_role_scopes', {
            role_key: roleKey,
            scopes: JSON.stringify(scopes)
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره Scope', 'error');
            return;
        }
        await this.onLookupRbacRoleChange();
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'Scope ذخیره شد', showConfirmButton: false, timer: 1500 });
    },
    async deleteLookupRbacRole() {
        const roleKey = this.state.rbacSelectedRoleKey || (document.getElementById('rbac-role-key')?.value || '').trim();
        if (!roleKey) {
            Swal.fire('خطا', 'ابتدا یک نقش انتخاب کنید', 'error');
            return;
        }
        const role = (this.state.rbacCatalog?.roles || []).find(r => String(r.role_key) === roleKey);
        if (role && String(role.is_system || '0') === '1') {
            Swal.fire('خطا', 'نقش سیستمی قابل حذف نیست', 'error');
            return;
        }
        const result = await Swal.fire({
            title: 'حذف نقش؟',
            text: `نقش «${roleKey}» و تمام دسترسی‌ها و محدوده‌های آن حذف می‌شود.`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف',
            cancelButtonText: 'انصراف',
            confirmButtonColor: '#b91c1c'
        });
        if (!result.isConfirmed) return;
        const res = await this.req('rbac_delete_role', { role_key: roleKey });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در حذف نقش', 'error');
            return;
        }
        this.state.rbacSelectedRoleKey = '';
        const sel = document.getElementById('rbac-role-select');
        if (sel) sel.value = '';
        await this.loadLookupRbacManagerData(true);
        this.renderLookupRbacRoleEditor(null, [], []);
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'نقش حذف شد', showConfirmButton: false, timer: 1500 });
    },
    async onLookupRbacUserChange() {
        const userId = document.getElementById('rbac-user-select')?.value || '';
        const body = document.getElementById('rbac-user-override-body');
        if (!userId) {
            if (body) body.classList.add('hidden');
            this.state.rbacUserOverrides = null;
            return;
        }
        const res = await this.req('rbac_user_permissions', { user_id: userId });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در دریافت دسترسی کاربر', 'error');
            return;
        }
        this.state.rbacUserOverrides = {
            user_id: userId,
            capability_overrides: res.capability_overrides || {},
            scopes: Array.isArray(res.scopes) ? res.scopes : []
        };
        if (body) body.classList.remove('hidden');
        this.renderLookupRbacUserCapabilities();
        this.renderLookupRbacUserScopes();
    },
    renderLookupRbacUserCapabilities() {
        const container = document.getElementById('rbac-user-capabilities-container');
        if (!container) return;
        const overrides = this.state.rbacUserOverrides?.capability_overrides || {};
        const search = (document.getElementById('rbac-user-capabilities-search')?.value || '').trim().toLowerCase();
        const caps = Array.isArray(this.state.rbacCatalog?.capabilities) ? this.state.rbacCatalog.capabilities : [];
        const filtered = caps.filter(c => {
            const key = String(c.capability_key || '');
            const title = String(c.title || '');
            return !search || key.toLowerCase().includes(search) || title.toLowerCase().includes(search);
        });
        if (!filtered.length) {
            container.innerHTML = '<div class="text-center text-gray-500 text-xs py-6">چیزی یافت نشد</div>';
            return;
        }
        const groups = {};
        filtered.forEach(c => { const g = String(c.category || 'other'); (groups[g] = groups[g] || []).push(c); });
        container.innerHTML = Object.keys(groups).sort().map(cat => `
            <div>
                <div class="text-xs text-amber-300 font-bold mb-2">${this.escapeHtml(cat)}</div>
                <div class="space-y-1">
                    ${groups[cat].map(c => {
                        const key = String(c.capability_key || '');
                        const cur = overrides[key] === 'allow' ? 'allow' : (overrides[key] === 'deny' ? 'deny' : '');
                        return `<div class="flex items-center justify-between gap-2 text-xs">
                            <span class="text-gray-300 truncate" title="${this.escapeHtml(key)}">${this.escapeHtml(c.title || key)}</span>
                            <select class="input bg-gray-800 text-[11px] rbac-user-cap-select" data-cap="${this.escapeHtml(key)}" style="width:104px">
                                <option value="" ${cur === '' ? 'selected' : ''}>ارث‌بری</option>
                                <option value="allow" ${cur === 'allow' ? 'selected' : ''}>مجاز</option>
                                <option value="deny" ${cur === 'deny' ? 'selected' : ''}>ممنوع</option>
                            </select>
                        </div>`;
                    }).join('')}
                </div>
            </div>
        `).join('');
    },
    renderLookupRbacUserScopes() {
        const container = document.getElementById('rbac-user-scopes-container');
        if (!container) return;
        const scopeMap = {};
        (this.state.rbacUserOverrides?.scopes || []).forEach(s => { scopeMap[String(s.resource_key || '')] = s; });
        const users = Array.isArray(this.state.rbacUsersForScopes) ? this.state.rbacUsersForScopes : [];
        const userOptionsHtml = users.map(u => {
            const id = String(u.id || '');
            if (!id) return '';
            const name = u.display_name || `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || id;
            return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(name)}</option>`;
        }).join('');
        const resourceCatalog = this.state.rbacCatalog?.resources || {};
        const resources = Object.keys(resourceCatalog).length ? Object.keys(resourceCatalog) : ['dashboard', 'leads', 'deals', 'payments', 'kpi', 'performance'];
        const scopeTypes = Array.isArray(this.state.rbacCatalog?.scope_types) ? this.state.rbacCatalog.scope_types : ['none', 'self', 'selected_users', 'all'];
        const typeLabels = { none: 'بدون دسترسی داده', self: 'فقط خودش', all: 'همه کاربران', selected_users: 'کاربران منتخب' };
        container.innerHTML = resources.map(resourceKey => {
            const meta = resourceCatalog[resourceKey] || {};
            const s = scopeMap[resourceKey];
            const scopeType = s ? String(s.scope_type || 'self') : '';
            const selectedUsers = new Set(((s?.selected_user_ids) || []).map(v => String(v)));
            return `
                <div class="border border-gray-800 rounded-lg p-3 bg-gray-900/30" data-rbac-user-scope-resource="${resourceKey}">
                    <div class="text-xs text-gray-200 mb-2 font-bold">${this.escapeHtml(meta.title || resourceKey)}</div>
                    <select class="input bg-gray-800 text-xs mb-2" name="scope_type" onchange="app.toggleRbacUserScopeUsers(this)">
                        <option value="" ${scopeType === '' ? 'selected' : ''}>ارث‌بری از نقش</option>
                        ${scopeTypes.map(type => `<option value="${this.escapeHtml(type)}" ${scopeType === type ? 'selected' : ''}>${this.escapeHtml(typeLabels[type] || type)}</option>`).join('')}
                    </select>
                    <div class="rbac-user-scope-users-wrap ${scopeType === 'selected_users' ? '' : 'hidden'}">
                        <select class="input bg-gray-800 text-xs w-full" name="selected_user_ids" multiple size="5">
                            ${userOptionsHtml.replace(/<option value=\"([^\"]+)\">/g, (m, id) => `<option value="${id}" ${selectedUsers.has(String(id)) ? 'selected' : ''}>`)}
                        </select>
                    </div>
                </div>
            `;
        }).join('');
    },
    toggleRbacUserScopeUsers(selectEl) {
        const card = selectEl?.closest('[data-rbac-user-scope-resource]');
        if (!card) return;
        const wrap = card.querySelector('.rbac-user-scope-users-wrap');
        if (!wrap) return;
        wrap.classList.toggle('hidden', (selectEl.value || '') !== 'selected_users');
    },
    async saveLookupRbacUserCapabilities() {
        const userId = this.state.rbacUserOverrides?.user_id || document.getElementById('rbac-user-select')?.value || '';
        if (!userId) {
            Swal.fire('خطا', 'ابتدا یک کاربر انتخاب کنید', 'error');
            return;
        }
        const allow = [];
        const deny = [];
        document.querySelectorAll('.rbac-user-cap-select').forEach(el => {
            const v = el.value;
            const k = el.dataset.cap;
            if (!k) return;
            if (v === 'allow') allow.push(k);
            else if (v === 'deny') deny.push(k);
        });
        const res = await this.req('rbac_save_user_capabilities', {
            user_id: userId,
            allow_keys: JSON.stringify(allow),
            deny_keys: JSON.stringify(deny)
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره دسترسی کاربر', 'error');
            return;
        }
        await this.onLookupRbacUserChange();
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'دسترسی کاربر ذخیره شد', showConfirmButton: false, timer: 1500 });
    },
    async saveLookupRbacUserScopes() {
        const userId = this.state.rbacUserOverrides?.user_id || document.getElementById('rbac-user-select')?.value || '';
        if (!userId) {
            Swal.fire('خطا', 'ابتدا یک کاربر انتخاب کنید', 'error');
            return;
        }
        const cards = Array.from(document.querySelectorAll('[data-rbac-user-scope-resource]'));
        const scopes = [];
        cards.forEach(card => {
            const scopeType = card.querySelector('[name="scope_type"]')?.value || '';
            if (!scopeType) return; // "inherit" -> omit so the override is removed (replace semantics)
            const resourceKey = card.dataset.rbacUserScopeResource || '';
            const selectedUserIds = Array.from(card.querySelector('[name="selected_user_ids"]')?.selectedOptions || []).map(o => parseInt(o.value, 10)).filter(v => Number.isFinite(v) && v > 0);
            scopes.push({ resource_key: resourceKey, scope_type: scopeType, selected_user_ids: selectedUserIds });
        });
        const res = await this.req('rbac_save_user_scopes', {
            user_id: userId,
            scopes: JSON.stringify(scopes)
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره Scope کاربر', 'error');
            return;
        }
        await this.onLookupRbacUserChange();
        this.state.rbacCurrentPermissionsLoaded = false;
        await this.loadCurrentRbacPermissions(true);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'Scope کاربر ذخیره شد', showConfirmButton: false, timer: 1500 });
    },
    resetDealServiceLookupAutofillState() {
        const fieldIds = ['deal-service-cost', 'deal-payable-amount', 'deal-price-list-code'];
        fieldIds.forEach((id) => {
            const el = document.getElementById(id);
            if (!el) return;
            delete el.dataset.autofillSource;
            delete el.dataset.autofillService;
            delete el.dataset.autofillValue;
            delete el.dataset.autofillDirty;
        });
        const titleField = document.getElementById('deal-title');
        if (titleField) {
            delete titleField.dataset.lastSelectedService;
        }
    },
    markDealFieldLookupAutofill(el, serviceValue, value) {
        if (!el) return;
        el.dataset.autofillSource = 'lookup';
        el.dataset.autofillService = serviceValue || '';
        el.dataset.autofillValue = String(value ?? '');
        el.dataset.autofillDirty = '0';
    },
    clearDealFieldLookupAutofill(el) {
        if (!el) return;
        delete el.dataset.autofillSource;
        delete el.dataset.autofillService;
        delete el.dataset.autofillValue;
        delete el.dataset.autofillDirty;
    },
    isDealFieldLookupAutofillReusable(el) {
        if (!el) return false;
        return el.dataset.autofillSource === 'lookup' && String(el.dataset.autofillDirty || '0') !== '1';
    },
    initDealServiceLookupAutofillTracking() {
        if (this.state._dealLookupAutofillTrackingInitialized) return;
        this.state._dealLookupAutofillTrackingInitialized = true;

        const markDirtyIfNeeded = (el, normalizer) => {
            if (!el) return;
            if (el.dataset.autofillSource !== 'lookup') return;
            const current = normalizer(el.value || '');
            const autoVal = String(el.dataset.autofillValue || '');
            el.dataset.autofillDirty = current === autoVal ? '0' : '1';
        };

        document.addEventListener('input', (e) => {
            const el = e.target;
            if (!el || !(el instanceof HTMLElement)) return;
            if (el.id === 'deal-payable-amount') {
                markDirtyIfNeeded(el, (v) => formatMoneyString(v));
                return;
            }
            if (el.id === 'deal-price-list-code') {
                markDirtyIfNeeded(el, (v) => String(v ?? '').trim());
            }
        });
    },
    setDealServiceCostLookupLock(locked) {
        const serviceCostField = document.getElementById('deal-service-cost');
        const payableAmountField = document.getElementById('deal-payable-amount');
        const priceListCodeField = document.getElementById('deal-price-list-code');
        [serviceCostField, payableAmountField, priceListCodeField].forEach((field) => {
            if (!field) return;
            field.readOnly = !!locked;
            field.dataset.lookupPriceLocked = locked ? '1' : '';
            field.classList.toggle('bg-gray-900', !!locked);
            field.classList.toggle('cursor-not-allowed', !!locked);
            field.classList.toggle('text-gray-300', !!locked);
        });
    },
    applyDealServiceLookupPricing() {
        const dealId = document.getElementById('deal-id')?.value?.trim() || '';

        const titleField = document.getElementById('deal-title');
        const serviceCostField = document.getElementById('deal-service-cost');
        const payableAmountField = document.getElementById('deal-payable-amount');
        const priceListCodeField = document.getElementById('deal-price-list-code');
        const hasDiscountField = document.getElementById('deal-has-discount');
        const discountTypeField = document.getElementById('deal-discount-type');
        const discountAmountField = document.getElementById('deal-discount-amount');
        if (!titleField || !serviceCostField || !payableAmountField || !priceListCodeField) return;

        const selectedService = (titleField.value || '').trim();
        const previousSelectedService = String(titleField.dataset.lastSelectedService || '').trim();
        const serviceChanged = previousSelectedService !== '' && previousSelectedService !== selectedService;
        const pricingLocked = String(payableAmountField.dataset.lookupPriceLocked || '') === '1'
            || payableAmountField.readOnly === true;
        const fixedPrice = this.getAllowedServiceFixedPrice(selectedService);
        const fixedPriceListCode = this.getAllowedServicePriceListCode(selectedService);
        const hasDiscount = String(hasDiscountField?.value || '0') === '1';
        const discountType = String(discountTypeField?.value || '').trim();
        const discountAmount = parseMoneyValue(discountAmountField?.value || 0);
        const effectiveDiscount = hasDiscount && discountType !== 'refund' ? discountAmount : 0;
        const computedPayable = fixedPrice ? Math.max(0, fixedPrice - effectiveDiscount) : 0;

        const serviceCostCurrent = parseMoneyValue(serviceCostField.value || '');
        const payableCurrent = parseMoneyValue(payableAmountField.value || '');
        const priceListCurrent = String(priceListCodeField.value || '').trim();

        const canFillServiceCost =
            serviceChanged ||
            !dealId ||
            serviceCostCurrent <= 0 ||
            this.isDealFieldLookupAutofillReusable(serviceCostField);
        const canFillPayable =
            serviceChanged ||
            !dealId ||
            payableCurrent <= 0 ||
            this.isDealFieldLookupAutofillReusable(payableAmountField);
        const canFillPriceListCode =
            serviceChanged ||
            !dealId ||
            priceListCurrent === '' ||
            this.isDealFieldLookupAutofillReusable(priceListCodeField);

        if (!fixedPrice) {
            if (this.isDealFieldLookupAutofillReusable(serviceCostField)) {
                serviceCostField.value = '';
                this.clearDealFieldLookupAutofill(serviceCostField);
            }
            if (this.isDealFieldLookupAutofillReusable(payableAmountField)) {
                payableAmountField.value = '';
                this.clearDealFieldLookupAutofill(payableAmountField);
            }
            this.setDealServiceCostLookupLock(false);
        }
        if (fixedPrice && canFillServiceCost) {
            const formatted = formatMoneyString(fixedPrice);
            serviceCostField.value = formatted;
            this.markDealFieldLookupAutofill(serviceCostField, selectedService, formatted);
            this.setDealServiceCostLookupLock(true);
        }
        if (fixedPrice && canFillPayable) {
            const formatted = formatMoneyString(computedPayable);
            payableAmountField.value = formatted;
            this.markDealFieldLookupAutofill(payableAmountField, selectedService, formatted);
        }
        if (!fixedPriceListCode && this.isDealFieldLookupAutofillReusable(priceListCodeField)) {
            priceListCodeField.value = '';
            this.clearDealFieldLookupAutofill(priceListCodeField);
        }
        if (fixedPriceListCode && canFillPriceListCode) {
            priceListCodeField.value = fixedPriceListCode;
            this.markDealFieldLookupAutofill(priceListCodeField, selectedService, fixedPriceListCode);
        }
        if (fixedPrice) {
            this.setDealServiceCostLookupLock(true);
        }
        titleField.dataset.lastSelectedService = selectedService;
    },
    getFallbackActivityTypes() {
        const items = this.getLookupItems('activity_types_fallback');
        if (!items.length) return [];
        return items.map(item => {
            let meta = {};
            if (item.meta_json) {
                try { meta = JSON.parse(item.meta_json); } catch (_) { meta = {}; }
            }
            return {
                Id: item.value ?? item.title,
                Title: item.title ?? item.value,
                Direction: meta.direction || 'outgoing',
                Category: meta.category || 'other',
                IsDisabled: false
            };
        });
    },

    applyLookupsToStaticSelects() {
        this.populateSelectOptions('lead-source', this.getLookupOptions('lead_sources'));
        this.populateSelectOptions('lead-content-topic', this.getLookupOptions('content_topics'));
        this.populateSelectOptions('deal-source', this.getLookupOptions('lead_sources'));

        this.renderDealTitleOptions();
        this.populateSelectOptions('deal-status', this.getLookupOptions('deal_statuses'));
        this.populateSelectOptions('deal-pipeline-stage', this.getLookupOptions('pipeline_stages'));
        this.populateSelectOptions('deals-pipeline-filter', [
            { value: 'all', label: 'همه مراحل' },
            ...this.getLookupOptions('pipeline_stages')
        ]);
        this.populateSelectOptions('deals-status-filter', [
            { value: 'all', label: 'همه وضعیت‌ها' },
            ...this.getLookupOptions('deal_statuses')
        ]);
        this.populateSelectOptions('activity-virtual-stage', this.getLookupOptions('activity_stages'));

        this.populateSelectOptions('lead-customer-level', this.getLookupOptions('customer_levels'));
        this.populateSelectOptions('leads-customer-level-filter', this.getLookupOptions('customer_levels'));
        this.populateSelectOptions('lead-activity-financial-level', this.getLookupOptions('financial_levels'));
        this.populateSelectOptions('lead-activity-asset', this.getLookupOptions('asset_ranges'));
        this.populateSelectOptions('lead-activity-income', this.getLookupOptions('income_ranges'));

        this.populateSelectOptions('deal-discount-type', this.getLookupOptions('discount_types'));
        this.populateSelectOptions('deal-discount-occasion', this.getLookupOptions('discount_occasions'));

        this.populateSelectOptions('activity-failure-reason', this.getLookupOptions('failure_reasons'));
        this.populateSelectOptions('deal-failure-reason', this.getLookupOptions('failure_reasons'));
        this.populateSelectOptions('failure-reason', this.getLookupOptions('failure_reasons'));

        this.populateSelectOptions('activity-reminder-hours', this.getLookupOptions('reminder_hours'));
        this.populateSelectOptions('edit-activity-reminder-hours', this.getLookupOptions('reminder_hours'));
        this.populateSelectOptions('activity-reminder-minutes', this.getLookupOptions('reminder_minutes'));
        this.populateSelectOptions('edit-activity-reminder-minutes', this.getLookupOptions('reminder_minutes'));

        this.populateSelectOptions('edit-role-select', this.getUserRoleOptions());

        const roleOptions = this.getUserRoleOptions();
        document.querySelectorAll('select[name="role"]').forEach(sel => {
            const current = sel.value;
            sel.innerHTML = '<option value="">Select role</option>';
            roleOptions.forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.value;
                option.textContent = opt.label || opt.value;
                sel.appendChild(option);
            });
            if (current) sel.value = current;
        });

        this.handleSourceChange();
        this.handleDealSourceChange();
    },

    getUserRoleOptions() {
        const merged = new Map();
        (this.getLookupOptions('user_roles') || []).forEach(opt => {
            const value = (opt?.value || '').toString().trim();
            if (!value) return;
            merged.set(value, { value, label: opt.label || value });
        });

        const rbacRoles = (this.state?.rbacCatalog?.roles || []);
        rbacRoles.forEach(role => {
            const value = ((role && role.role_key) || '').toString().trim();
            if (!value) return;
            const title = ((role && role.title) || value).toString().trim();
            if (!merged.has(value)) {
                merged.set(value, { value, label: title });
            }
        });

        return Array.from(merged.values()).sort((a, b) => (a.label || a.value).localeCompare((b.label || b.value), 'fa'));
    },

    getUserRoleLabel(roleKey) {
        const key = (roleKey || '').toString().trim();
        if (!key) return '-';
        const staticMap = {
            admin: 'مدیر عامل',
            sales_manager: 'مدیر فروش',
            management: 'مدیریت',
            agent: 'کارشناس فروش',
            crm_specialist: 'کارشناس فروش',
            accountant: 'مالی',
            support: 'پشتیبانی'
        };
        if (staticMap[key]) return staticMap[key];
        const match = this.getUserRoleOptions().find(opt => opt.value === key);
        return match?.label || key;
    },

    handleSourceChange() {
        const source = document.getElementById('lead-source')?.value || '';
        const detailSelect = document.getElementById('lead-source-detail');
        if(!detailSelect) return;
        const previous = detailSelect.value || '';
        let options = this.getLookupChildOptions('lead_source_details', source);
        if (!options.length) {
            options = [{ value: 'general', label: 'عمومی' }];
        }
        detailSelect.innerHTML = '<option value="">جزئیات شیوه آشنایی</option>';
        options.forEach(opt => {
            const el = document.createElement('option');
            el.value = opt.value;
            el.textContent = opt.label;
            detailSelect.appendChild(el);
        });
        detailSelect.disabled = false;
        detailSelect.required = false;
        // Default to the placeholder (جزئیات شیوه آشنایی) instead of auto-selecting the
        // first option; keep a prior selection only when it still exists in the new list.
        detailSelect.value = previous && options.some(opt => String(opt.value) === String(previous)) ? previous : '';
    },

    handleDealSourceChange() {
        const source = document.getElementById('deal-source')?.value || '';
        const detailSelect = document.getElementById('deal-source-detail');
        if (!detailSelect) return;
        const previous = detailSelect.value || '';
        let options = this.getLookupChildOptions('lead_source_details', source);
        if (!options.length) {
            options = this.getLookupOptions('lead_source_details');
        }
        detailSelect.innerHTML = '<option value="">جزئیات شیوه آشنایی</option>';
        options.forEach(opt => {
            const el = document.createElement('option');
            el.value = opt.value;
            el.textContent = opt.label;
            detailSelect.appendChild(el);
        });
        detailSelect.disabled = false;
        detailSelect.required = false;
        detailSelect.value = previous && options.some(opt => String(opt.value) === String(previous)) ? previous : '';
    },

    handlePurchaseOtherNumberChange() {
        const configs = [
            {
                select: document.getElementById('activity-has-purchase'),
                field: document.getElementById('previous-purchase-number-field'),
                input: document.querySelector('input[name="previous_purchase_number"]')
            },
            {
                select: document.getElementById('deal-activity-has-purchase'),
                field: document.getElementById('deal-previous-purchase-number-field'),
                input: document.getElementById('deal-activity-previous-number')
            },
            {
                select: document.getElementById('lead-activity-has-purchase'),
                field: document.getElementById('lead-previous-purchase-number-field'),
                input: document.getElementById('lead-activity-previous-number')
            }
        ];
        configs.forEach(cfg => {
            if (!cfg.select || !cfg.field) return;
            const hasPurchase = cfg.select.value;
            if (hasPurchase === '1') {
                cfg.field.classList.remove('hidden');
                if (cfg.input) cfg.input.required = false;
            } else {
                cfg.field.classList.add('hidden');
                if (cfg.input) {
                    cfg.input.required = false;
                    cfg.input.value = '';
                }
            }

            // پس از اولین انتخاب، فیلد قفل شود
            if (hasPurchase !== '' && cfg.select.dataset.locked !== 'true') {
                cfg.select.dataset.locked = 'true';
                cfg.select.disabled = true;
                cfg.select.classList.add('bg-gray-900', 'cursor-not-allowed');
            }
        });
    },
    
    checkIfPurchaseQuestionAnswered(contactId) {
        // بررسی اینکه آیا لید قبلاً به این سوال پاسخ داده (و پاسخ "نامشخص" نباشد)
        if (!contactId || !this.state.currentLeadActivities) return false;
        
        for (let act of this.state.currentLeadActivities) {
            const note = act.result_note || '';
            if ((note.includes('🟢 این لید قبلاً خرید دیگری') || 
                 note.includes('⚪ این لید خرید قبلی با شماره دیگر نداشته است')) &&
                !note.includes('نامشخص')) {
                return true;
            }
        }
        return false;
    },
    
    handleActivityFormVisibility() {
        try {
            const virtualStage = document.getElementById('activity-virtual-stage')?.value;
            const contactId = document.getElementById('activity-contact-id')?.value;
            // The purchase question container has been removed from Activity Modal as per user request.
            // const purchaseField = document.getElementById('activity-has-purchase');
            // const purchaseFieldContainer = document.getElementById('purchase-question-container');

            // No longer checking for these elements in the activity modal context.
            // if (!purchaseField || !purchaseFieldContainer) {
            //     console.warn('Purchase field or container not found');
            //     return;
            // }

            // The logic to handle purchase question visibility is now primarily in openLeadModal and saveLead.

        } catch(error) {
            console.error('Error in handleActivityFormVisibility:', error);
        }
    },

    handleActivityDealSelection() {
        const dealSelect = document.getElementById('activity-deal-select');
        const pipelineContainer = document.getElementById('activity-pipeline-stage-container');
        const pipelineSelect = document.getElementById('activity-virtual-stage');
        const dealId = dealSelect?.value;

        if (!pipelineContainer || !pipelineSelect) return;

        if (dealId) {
            // Deal selected - show and require pipeline stage
            pipelineContainer.style.display = 'block';
            pipelineSelect.required = true;
            // Update label to show it's required
            const label = pipelineContainer.querySelector('label');
            if (label) {
                label.innerHTML = 'مرحله کاریز معامله پس از فعالیت <span class="text-red-500">*</span>';
            }
        } else {
            // No deal selected - hide pipeline stage
            pipelineContainer.style.display = 'none';
            pipelineSelect.required = false;
            pipelineSelect.value = '';
        }
    },
    
    handleActivityStageChange() {
        const stage = document.getElementById('activity-virtual-stage')?.value;
        const extra = document.getElementById('referral-extra');
        const noteInput = document.querySelector('textarea[name="referral_note"]');
        const failureExtra = document.getElementById('failure-reason-extra');
        const failureReason = document.getElementById('activity-failure-reason');
        const failureOtherDesc = document.getElementById('activity-failure-other-description');
        
        const isCrmReferral = stage === 'ارجاع به تیم فروش';
        const isFailed = stage === 'deal_failed' || stage === 'معامله ناموفق' || stage === 'ناموفق';
        const stageMeta = this.getLookupMeta('activity_stages', stage);
        const showFailureFromMeta = stageMeta.show_failure_reason === true || stageMeta.showFailureReason === true;

        // مدیریت بخش ارجاع به تیم فروش
        if(extra) {
            if(isCrmReferral) {
                extra.classList.remove('hidden');
                if (noteInput) noteInput.required = true;
            } else {
                extra.classList.add('hidden');
                if (noteInput) noteInput.required = false;
            }
        }
        
        // مدیریت بخش دلیل ناموفق
        if (failureExtra) {
            const shouldShowFailure = isCrmReferral || isFailed || showFailureFromMeta;
            if (shouldShowFailure) {
                failureExtra.classList.remove('hidden');
                if (failureReason) failureReason.required = true;
                if (failureOtherDesc) {
                    failureOtherDesc.required = true;
                    failureOtherDesc.placeholder = 'لطفاً توضیحات دلیل عدم موفقیت را وارد کنید';
                }
                // نمایش فیلد توضیحات
                const otherDiv = document.getElementById('activity-failure-other-reason');
                if (otherDiv) otherDiv.classList.remove('hidden');
            } else {
                failureExtra.classList.add('hidden');
                if (failureReason) {
                    failureReason.required = false;
                    failureReason.value = '';
                }
                if (failureOtherDesc) {
                    failureOtherDesc.required = false;
                    failureOtherDesc.value = '';
                }
            }
        }
        
        // فراخوانی handleActivityFormVisibility برای به‌روزرسانی وضعیت required فیلد خرید قبلی
        this.handleActivityFormVisibility();
    },
    
    toggleActivityFailureOtherReason() {
        const reason = document.getElementById('activity-failure-reason')?.value;
        const otherDiv = document.getElementById('activity-failure-other-reason');
        const otherDesc = document.getElementById('activity-failure-other-description');
        
        if (!otherDiv || !otherDesc) return;
        
        const virtualStage = document.getElementById('activity-virtual-stage')?.value;
        const isFailed = virtualStage === 'deal_failed' || virtualStage === 'معامله ناموفق';
        const show = isFailed; // در حالت ناموفق همیشه نمایش بده
        otherDiv.classList.toggle('hidden', !show);
        otherDesc.required = show;
        if (!show) {
            otherDesc.value = '';
        } else {
            otherDesc.placeholder = 'لطفاً توضیحات دلیل عدم موفقیت را وارد کنید';
        }
    },
    
	    async openLeadModal(lead = null) {
	        const isEdit = !!lead;
        if (!isEdit && !this.canActionClient('leads.create')) {
            Swal.fire('خطا', 'دسترسی به ایجاد لید ندارید', 'error');
            return;
        }
        if (isEdit && !this.canActionClient('leads.edit')) {
            Swal.fire('خطا', 'دسترسی به ویرایش لید ندارید', 'error');
            return;
        }
        if (isEdit && !this.isAdminOrCrmSpecialist() && !this.isLeadOwner(lead)) {
            Swal.fire('خطا', 'دسترسی به ویرایش این لید را ندارید', 'error');
            return;
        }
        const form = document.getElementById('lead-form');
        if(!form) return;
        const titleEl = document.getElementById('lead-modal-title');
        if (titleEl) {
            titleEl.textContent = isEdit ? 'ویرایش اطلاعات لید' : 'افزودن لید جدید';
        }
	        form.reset();
	        form.dataset.mode = isEdit ? 'edit' : 'create';
	        form.dataset.contactId = lead?.didar_contact_id || '';
	        const deleteBtn = document.getElementById('lead-delete-btn');
	        if (deleteBtn) {
	            const show = isEdit && this.canActionClient('leads.delete');
	            deleteBtn.classList.toggle('hidden', !show);
	            deleteBtn.disabled = !show;
	        }
        this.populateSelectOptions('lead-source', this.getLookupOptions('lead_sources'));
        this.populateSelectOptions('lead-content-topic', this.getLookupOptions('content_topics'));
        this.handleSourceChange();
        const leadActivitySection = document.getElementById('lead-activity-section');
        if (leadActivitySection) {
            leadActivitySection.classList.toggle('hidden', !isEdit);
        }
        
        // Ensure External CRM users are loaded before populating the select
        if ((this.isAdminOrCrmSpecialist() || this.hasCrossUserScopeClient('leads')) && (!this.state.didarUsers || this.state.didarUsers.length === 0)) {
            await this.loadDidarUsers('leads');
        } else {
            this.populateLeadOwnerSelect();
        }
        
        if (isEdit && lead) {
            const fullName = `${lead.first_name || ''} ${lead.last_name || ''}`.trim();
            document.querySelector('#lead-form [name="full_name"]').value = fullName;
            const customerCodeField = document.getElementById('lead-customer-code');
            if (customerCodeField) customerCodeField.value = lead.customer_code || '';
            document.querySelector('#lead-form [name="national_id"]').value = lead.national_id || '';
            document.querySelector('#lead-form [name="mobile_phone"]').value = lead.mobile_phone || '';
            document.querySelector('#lead-form [name="secondary_mobile_phone"]').value = lead.secondary_mobile_phone || '';
            const phone3 = document.querySelector('#lead-form [name="mobile_phone_3"]');
            const phone4 = document.querySelector('#lead-form [name="mobile_phone_4"]');
            if (phone3) phone3.value = lead.mobile_phone_3 || '';
            if (phone4) phone4.value = lead.mobile_phone_4 || '';
            document.querySelector('#lead-form [name="city"]').value = lead.city || '';
            document.querySelector('#lead-form [name="job_title"]').value = lead.job_title || '';
            document.querySelector('#lead-form [name="background_info"]').value = lead.background_info || '';
            document.querySelector('#lead-form [name="owner_didar_id"]').value = lead.owner_didar_id || '';
            const prevOwnerSelect = document.getElementById('lead-previous-owner-select');
            if (prevOwnerSelect) prevOwnerSelect.value = lead.previous_owner_id || '';
            document.getElementById('lead-source').value = lead.acquaintance_source || '';
            this.handleSourceChange();
            document.getElementById('lead-source-detail').value = lead.acquaintance_detail || '';
            document.getElementById('lead-content-topic').value = lead.content_topic || '';

            // Activity-style extra fields
            const setVal = (id, val) => { const el = document.getElementById(id); if (el) el.value = val || ''; };
            // setVal('lead-activity-city', lead.city || ''); // Removed (Issue 4)
            // setVal('lead-activity-job', lead.job_title || ''); // Removed (Issue 4)
            setVal('lead-activity-job-desc', lead.job_description || '');
            setVal('lead-activity-extra-info', lead.extra_info || '');
            setVal('lead-activity-financial-level', lead.financial_level || '');
            setVal('lead-activity-asset', lead.asset_estimation || '');
            setVal('lead-activity-income', lead.income_estimation || '');
            const acqVal = lead.acquaintance_duration || '';
            const acqYears = document.getElementById('lead-activity-acq-years');
            const acqMonths = document.getElementById('lead-activity-acq-months');
            const acqLess = document.getElementById('lead-activity-acq-less');
            if (acqLess && acqYears && acqMonths) {
                if (acqVal && acqVal.includes('کمتر')) {
                    acqLess.checked = true;
                    acqYears.value = '0';
                    acqMonths.value = '0';
                } else {
                    const match = acqVal.match(/(\d+)\s*سال.*?(\d+)\s*ماه/);
                    acqYears.value = match ? match[1] : '0';
                    acqMonths.value = match ? match[2] : '0';
                    acqLess.checked = false;
                }
            }
            const purchaseSelect = document.getElementById('lead-activity-has-purchase');
            if (purchaseSelect) {
                if (lead.has_previous_purchase === 0 || lead.has_previous_purchase === 1 || lead.has_previous_purchase === '0' || lead.has_previous_purchase === '1') {
                    purchaseSelect.value = String(lead.has_previous_purchase);
                    // Issue 2: Hide or Lock?
                    // "The question ... should appear only once during first edit."
                    // If it has a value, we can lock it or hide it. 
                    // Let's lock it for now to be consistent with previous logic, 
                    // but the user requirement implies it might not need to be shown if answered.
                    // However, for profile completeness visibility, keeping it locked is better than hiding.
                    purchaseSelect.disabled = true;
                    purchaseSelect.dataset.locked = 'true';
                    purchaseSelect.classList.add('bg-gray-900', 'cursor-not-allowed');
                } else {
                    purchaseSelect.value = '';
                    purchaseSelect.disabled = false;
                    purchaseSelect.dataset.locked = '';
                    purchaseSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
                }
            }
        } else {
            const customerCodeField = document.getElementById('lead-customer-code');
            if (customerCodeField) customerCodeField.value = 'پس از ثبت خودکار ایجاد می‌شود';
            const prevOwnerSelect = document.getElementById('lead-previous-owner-select');
            if (prevOwnerSelect) prevOwnerSelect.value = '';
            ['lead-activity-job-desc','lead-activity-extra-info'].forEach(id => {
                const el = document.getElementById(id);
                if (el) el.value = '';
            });
            ['lead-activity-financial-level','lead-activity-asset','lead-activity-income','lead-activity-has-purchase'].forEach(id => {
                const el = document.getElementById(id);
                if (el) { el.value=''; el.disabled=false; el.dataset.locked=''; el.classList.remove('bg-gray-900','cursor-not-allowed'); }
            });
            const acqYears = document.getElementById('lead-activity-acq-years');
            const acqMonths = document.getElementById('lead-activity-acq-months');
            const acqLess = document.getElementById('lead-activity-acq-less');
            if (acqYears) acqYears.value = '0';
            if (acqMonths) acqMonths.value = '0';
            if (acqLess) acqLess.checked = false;
            const phone3 = document.querySelector('#lead-form [name="mobile_phone_3"]');
            const phone4 = document.querySelector('#lead-form [name="mobile_phone_4"]');
            if (phone3) phone3.value = '';
            if (phone4) phone4.value = '';
        }
        const customerCodeField = document.getElementById('lead-customer-code');
        if (customerCodeField) {
            const allowEditCustomerCode = this.isAdmin() && isEdit;
            customerCodeField.readOnly = !allowEditCustomerCode;
            customerCodeField.classList.toggle('cursor-not-allowed', !allowEditCustomerCode);
            customerCodeField.classList.toggle('bg-gray-900', !allowEditCustomerCode);
            customerCodeField.classList.toggle('text-gray-300', !allowEditCustomerCode);
            customerCodeField.classList.toggle('bg-gray-800', allowEditCustomerCode);
            customerCodeField.classList.toggle('text-white', allowEditCustomerCode);
        }
        const regDisplay = document.getElementById('lead-register-date-display');
        if (regDisplay) {
            if (isEdit && lead) {
                const regDate = lead.register_time_jalali || (lead.register_time ? toPersianDate(lead.register_time) : '');
                regDisplay.value = regDate || '---';
            } else {
                let today = '';
                if (typeof persianDate !== 'undefined') {
                    try {
                        today = new persianDate().format('YYYY/MM/DD');
                    } catch (e) {}
                }
                regDisplay.value = today || (toPersianDate(new Date().toISOString()) || '');
            }
        }
        
        this.applyLeadFormPermissions(lead, isEdit);
        this.applyRbacActionVisibility();
        
        this.openModal('modalLead');
    },

    /**
     * ذخیره فیلدهای سبک فعالیت از مودال لید در دیتابیس داخلی
     */
    async saveLeadActivityExtras(contactId, form) {
        if (!contactId || !form) return;
        const payload = { contact_id: contactId };
        const getVal = name => form.querySelector(`[name="${name}"]`)?.value?.trim() || '';

        // const city = getVal('activity_city'); // Removed (Issue 4)
        // const job = getVal('activity_job_title'); // Removed (Issue 4)
        const jobDesc = getVal('activity_job_desc');
        const extraInfo = getVal('activity_extra_info');
        const finLevel = getVal('activity_financial_level');
        const asset = getVal('activity_asset');
        const income = getVal('activity_income');
        const acqYears = form.querySelector('[name="activity_acq_years"]')?.value || '0';
        const acqMonths = form.querySelector('[name="activity_acq_months"]')?.value || '0';
        const acqLess = form.querySelector('[name="activity_acq_less"]')?.checked;
        const hasPurchaseEl = form.querySelector('[name="activity_has_purchase"]');
        const prevPurchase = getVal('activity_previous_number');

        // if (city) payload.city = city;
        // if (job) payload.job_title = job;
        if (jobDesc) payload.job_description = jobDesc;
        if (extraInfo) payload.extra_info = extraInfo;
        if (finLevel) payload.financial_level = finLevel;
        if (asset) payload.asset_estimation = asset;
        if (income) payload.income_estimation = income;

        if (acqLess) {
            payload.acquaintance_duration = 'کمتر از 1 ماه';
        } else if (acqYears || acqMonths) {
            payload.acquaintance_duration = `${acqYears || '0'} سال ${acqMonths || '0'} ماه`;
        }

        if (hasPurchaseEl && hasPurchaseEl.dataset.locked !== 'true' && hasPurchaseEl.value !== '') {
            payload.has_previous_purchase = hasPurchaseEl.value;
        } else if (hasPurchaseEl && hasPurchaseEl.dataset.locked === 'true') {
            payload.has_previous_purchase = hasPurchaseEl.value;
        }
        if (prevPurchase) {
            payload.previous_purchase_number = prevPurchase.replace(/\D/g, '').slice(-10);
        }

        if (Object.keys(payload).length <= 1) return;

        try {
            await this.req('save_deal_info', payload);
        } catch (e) {
            console.warn('saveLeadActivityExtras failed', e);
        }
    },
    
    applyLeadFormPermissions(lead, isEdit) {
        const form = document.getElementById('lead-form');
        if(!form) return;
        const isAdmin = this.isAdmin();
        const isCrmSpecialist = this.state.user?.role === 'crm_specialist';
        const isAdminOrCrm = this.isAdminOrCrmSpecialist();
        const isOwner = isEdit && this.isLeadOwner(lead);
        
        const primary = form.querySelector('[name="mobile_phone"]');
        const secondary = form.querySelector('[name="secondary_mobile_phone"]');
        const phone3 = form.querySelector('[name="mobile_phone_3"]');
        const phone4 = form.querySelector('[name="mobile_phone_4"]');
        const ownerSelect = form.querySelector('[name="owner_didar_id"]');
        const prevOwnerSelect = form.querySelector('[name="previous_owner_id"]');
        const sourceSelect = document.getElementById('lead-source');
        const detailSelect = document.getElementById('lead-source-detail');
        const topicSelect = document.getElementById('lead-content-topic');
        const adminFieldsDiv = document.getElementById('lead-admin-fields');
        const phoneFieldsDiv = primary?.closest('.grid');
        const phoneExtraDiv = phone3?.closest('.grid');
        
        const disable = (el, state) => { if(el) el.disabled = state; };
        const setRequired = (el, state) => { if(el) el.required = state; };
        const hide = (el, state) => { if(el) el.style.display = state ? 'none' : ''; };
        const lockIfFilled = (el, value) => {
            if (!el) return;
            const hasVal = value && String(value).trim() !== '';
            if (!isAdmin && hasVal) {
                if ('readOnly' in el) el.readOnly = true;
                if ('disabled' in el) el.disabled = true;
                el.classList.add('bg-gray-900','cursor-not-allowed');
            } else if (!isAdmin) {
                if ('readOnly' in el) el.readOnly = false;
                if ('disabled' in el) el.disabled = false;
                el.classList.remove('bg-gray-900','cursor-not-allowed');
            } else {
                // ادمین آزاد است؛ اما اگر مقدار دارد، قفل نرم
                if ('readOnly' in el) el.readOnly = false;
                if ('disabled' in el) el.disabled = false;
                el.classList.remove('bg-gray-900','cursor-not-allowed');
            }
        };
        
        // Set all admin fields as required
        setRequired(ownerSelect, true);
        setRequired(primary, true);
        // فیلدهای آشنایی: اختیاری هستند
        setRequired(sourceSelect, false);
        setRequired(detailSelect, false);
        setRequired(topicSelect, false);
        if (prevOwnerSelect) prevOwnerSelect.required = false;

        // Admin یا کارشناس فروش: می‌تواند همه را ویرایش کند
        if (isAdminOrCrm) {
            hide(phoneFieldsDiv, false);
            hide(phoneExtraDiv, false);
            if (adminFieldsDiv) adminFieldsDiv.style.display = '';
            return;
        }

        // Agent (کاربر عادی): فیلدهای admin پنهان می‌شوند
        if (adminFieldsDiv) adminFieldsDiv.style.display = 'none';
        hide(phoneFieldsDiv, false);
        hide(phoneExtraDiv, false);
        
        // فیلدهای آشنایی در حالت edit غیرالزامی هستند
        if (isEdit) {
            setRequired(sourceSelect, false);
            setRequired(detailSelect, false);
            setRequired(topicSelect, false);
        }
        
        lockIfFilled(primary, lead?.mobile_phone);
        lockIfFilled(secondary, lead?.secondary_mobile_phone);
        lockIfFilled(phone3, lead?.mobile_phone_3);
        lockIfFilled(phone4, lead?.mobile_phone_4);
        lockIfFilled(form.querySelector('[name="national_id"]'), lead?.national_id);
        lockIfFilled(form.querySelector('[name="city"]'), lead?.city);
        lockIfFilled(form.querySelector('[name="job_title"]'), lead?.job_title);
        lockIfFilled(form.querySelector('[name="background_info"]'), lead?.background_info);

        // Prevent changing ownership/previous owner
        disable(ownerSelect, true);
        disable(prevOwnerSelect, true);
    },
    
    editLeadInfo() {
        if(!this.state.currentLead) return;
        if (!this.canActionClient('leads.edit')) {
            Swal.fire('خطا', 'دسترسی به ویرایش لید ندارید', 'error');
            return;
        }
        
        // کارشناس فروش is view-only
        const isAdmin = window.APP_USER?.role === 'admin' || window.APP_USER?.role === 'system';
        const isCrm = window.APP_USER?.role === 'crm_specialist';
        
        if (isCrm) {
            Swal.fire('خطا', 'کارشناس فروش فقط مجاز به مشاهده است', 'error');
            return;
        }
        
        if (!isAdmin && !this.isLeadOwner(this.state.currentLead)) {
            Swal.fire('خطا', 'دسترسی به ویرایش این لید را ندارید', 'error');
            return;
        }
        
        this.openLeadModal(this.state.currentLead);
    },

    async saveLead(e) {
        e.preventDefault();
        const form = e.target;
        const mode = form.dataset.mode || 'create';
        if (mode === 'create' && !this.canActionClient('leads.create')) {
            Swal.fire('خطا', 'دسترسی به ایجاد لید ندارید', 'error');
            return;
        }
        if (mode === 'edit' && !this.canActionClient('leads.edit')) {
            Swal.fire('خطا', 'دسترسی به ویرایش لید ندارید', 'error');
            return;
        }
        const data = Object.fromEntries(new FormData(form).entries());
        const isAdminUser = this.isAdmin();

        // شیوه آشنایی و موضوع محتوا هنگام افزودن لید جدید اجباری هستند. این فیلدها فقط
        // برای ادمین/کارشناس فروش نمایش داده می‌شوند (برای بقیه پنهان است)، پس فقط وقتی
        // بخش نمایش داده می‌شود اعتبارسنجی می‌کنیم. در حالت ویرایش نیازی به پر کردن مجدد
        // نیست؛ مقدار قبلی از قبل پیش‌فرض بارگذاری شده است.
        if (mode === 'create') {
            const adminFields = document.getElementById('lead-admin-fields');
            const adminFieldsVisible = !!adminFields && adminFields.style.display !== 'none';
            if (adminFieldsVisible) {
                if (!String(data.acquaintance_source || '').trim()) {
                    Swal.fire('خطا', 'لطفاً شیوه آشنایی را انتخاب کنید', 'error');
                    document.getElementById('lead-source')?.focus();
                    return;
                }
                if (!String(data.content_topic || '').trim()) {
                    Swal.fire('خطا', 'لطفاً موضوع محتوا را انتخاب کنید', 'error');
                    document.getElementById('lead-content-topic')?.focus();
                    return;
                }
            }
        }

        // اعتبارسنجی فیلدها
        const fullName = data.full_name || '';
        const nationalId = data.national_id || '';
        const mobilePhone = data.mobile_phone || '';
        const secondaryMobilePhone = data.secondary_mobile_phone || '';
        const mobilePhone3 = data.mobile_phone_3 || '';
        const mobilePhone4 = data.mobile_phone_4 || '';
        const city = data.city || '';
        const jobTitle = data.job_title || '';
        
        // اعتبارسنجی نام و نام خانوادگی
        if (fullName.trim()) {
            const nameValidation = validatePersianText(fullName, 'نام و نام خانوادگی');
            if (!nameValidation.valid) {
                Swal.fire('خطا', nameValidation.message, 'error');
                return;
            }
        }
        
        // اعتبارسنجی کد ملی
        if (nationalId.trim()) {
            const nationalIdValidation = validateNationalId(nationalId);
            if (!nationalIdValidation.valid) {
                Swal.fire('خطا', nationalIdValidation.message, 'error');
                return;
            }
            // فقط اعداد را ذخیره کن
            data.national_id = nationalId.trim().replace(/\D/g, '');
        }
        
        // اعتبارسنجی شماره تماس اصلی
        if (mobilePhone.trim()) {
            const mobileValidation = validatePhone(mobilePhone, 'شماره تماس اصلی');
            if (!mobileValidation.valid) {
                Swal.fire('خطا', mobileValidation.message, 'error');
                return;
            }
            // فقط اعداد را ذخیره کن
            data.mobile_phone = mobilePhone.trim().replace(/\D/g, '');
        }
        
        // اعتبارسنجی شماره تماس دوم
        if (secondaryMobilePhone.trim()) {
            const secondaryValidation = validatePhone(secondaryMobilePhone, 'شماره تماس دوم');
            if (!secondaryValidation.valid) {
                Swal.fire('خطا', secondaryValidation.message, 'error');
                return;
            }
            // فقط اعداد را ذخیره کن
            data.secondary_mobile_phone = secondaryMobilePhone.trim().replace(/\D/g, '');
        }
        if (mobilePhone3.trim()) {
            const mobile3Validation = validatePhone(mobilePhone3, 'شماره تماس سوم');
            if (!mobile3Validation.valid) {
                Swal.fire('خطا', mobile3Validation.message, 'error');
                return;
            }
            data.mobile_phone_3 = mobilePhone3.trim().replace(/\D/g, '');
        }
        if (mobilePhone4.trim()) {
            const mobile4Validation = validatePhone(mobilePhone4, 'شماره تماس چهارم');
            if (!mobile4Validation.valid) {
                Swal.fire('خطا', mobile4Validation.message, 'error');
                return;
            }
            data.mobile_phone_4 = mobilePhone4.trim().replace(/\D/g, '');
        }

        if (mode === 'edit' && isAdminUser && Object.prototype.hasOwnProperty.call(data, 'customer_code')) {
            const rawCustomerCode = String(data.customer_code || '').trim();
            if (rawCustomerCode !== '') {
                if (!/^\d+$/.test(rawCustomerCode)) {
                    Swal.fire('خطا', 'کد مشتری باید فقط عدد باشد.', 'error');
                    return;
                }
                data.customer_code = rawCustomerCode;
            } else {
                delete data.customer_code;
            }
        }

        // بررسی تکراری بودن شماره‌های تلفن
        const phoneNumbers = [];
        const phoneLabels = [];

        if (data.mobile_phone) {
            phoneNumbers.push(data.mobile_phone);
            phoneLabels.push('شماره تماس اصلی');
        }
        if (data.secondary_mobile_phone) {
            phoneNumbers.push(data.secondary_mobile_phone);
            phoneLabels.push('شماره تماس دوم');
        }
        if (data.mobile_phone_3) {
            phoneNumbers.push(data.mobile_phone_3);
            phoneLabels.push('شماره تماس سوم');
        }
        if (data.mobile_phone_4) {
            phoneNumbers.push(data.mobile_phone_4);
            phoneLabels.push('شماره تماس چهارم');
        }

        // بررسی تکراری بودن شماره‌ها
        for (let i = 0; i < phoneNumbers.length; i++) {
            for (let j = i + 1; j < phoneNumbers.length; j++) {
                if (phoneNumbers[i] === phoneNumbers[j]) {
                    Swal.fire('خطا', `شماره ${phoneLabels[i]} با ${phoneLabels[j]} تکراری است! لطفاً شماره‌های منحصر به فرد وارد کنید.`, 'error');
                    return;
                }
            }
        }

        // اعتبارسنجی شهر
        if (city.trim()) {
            const cityValidation = validatePersianText(city, 'شهر');
            if (!cityValidation.valid) {
                Swal.fire('خطا', cityValidation.message, 'error');
                return;
            }
        }
        
        // اعتبارسنجی شغل
        if (jobTitle.trim()) {
            const jobValidation = validatePersianText(jobTitle, 'شغل');
            if (!jobValidation.valid) {
                Swal.fire('خطا', jobValidation.message, 'error');
                return;
            }
        }
        
        if (mode === 'edit') {
            data.contact_id = form.dataset.contactId;
            if (!data.contact_id) {
                Swal.fire('خطا', 'شناسه لید نامعتبر است', 'error');
                return;
            }
            if (!this.isAdminOrCrmSpecialist() && (!this.state.currentLead || !this.isLeadOwner(this.state.currentLead))) {
                Swal.fire('خطا', 'دسترسی به ویرایش این لید را ندارید', 'error');
                return;
            }

            // Issue 5: Mandatory fields on First Edit
            // Check if name is generic.
            const currentName = ((this.state.currentLead?.first_name || '') + ' ' + (this.state.currentLead?.last_name || '')).trim();
            const isFirstEdit = currentName === 'لید جدید';
            
            if (isFirstEdit) {
                if (!fullName || fullName === 'لید جدید') {
                    Swal.fire('خطا', 'لطفاً نام و نام خانوادگی لید را وارد کنید.', 'error');
                    return;
                }
                
                const hasPurchaseSelect = form.querySelector('[name="activity_has_purchase"]');
                if (hasPurchaseSelect && !hasPurchaseSelect.value) {
                     Swal.fire('خطا', 'لطفاً وضعیت خرید قبلی با شماره دیگر را مشخص کنید.', 'error');
                     return;
                }
            }
        } else if (!this.isAdminOrCrmSpecialist()) {
            Swal.fire('خطا', 'فقط مدیر و کارشناس فروش می‌توانند لید جدید اضافه کنند', 'error');
            return;
        }

        // Collect Additional Fields (Issue 3)
        // job_description
        const jobDesc = form.querySelector('[name="activity_job_desc"]')?.value;
        if (jobDesc) data.job_description = jobDesc;
        
        // extra_info
        const extraInfo = form.querySelector('[name="activity_extra_info"]')?.value;
        if (extraInfo) data.extra_info = extraInfo;
        
        // financial_level
        const finLevel = form.querySelector('[name="activity_financial_level"]')?.value;
        if (finLevel) data.financial_level = finLevel;
        
        // asset_estimation
        const assetEst = form.querySelector('[name="activity_asset"]')?.value;
        if (assetEst) data.asset_estimation = assetEst;
        
        // income_estimation
        const incomeEst = form.querySelector('[name="activity_income"]')?.value;
        if (incomeEst) data.income_estimation = incomeEst;
        
        // customer_level و customer_level_notes
        const customerLevelEl = form.querySelector('[name="customer_level"]');
        const customerLevelNotesEl = form.querySelector('[name="customer_level_notes"]');
        const customerLevel = customerLevelEl?.value?.trim() || '';
        const customerLevelNotes = customerLevelNotesEl?.value?.trim() || '';
        
        if (customerLevel) {
            data.customer_level = customerLevel;
            // اگر سطح مشتری انتخاب شده باشد، توضیحات الزامی است
            if (!customerLevelNotes) {
                Swal.fire('خطا', 'لطفاً توضیحات سطح مشتری را وارد کنید', 'error');
                return;
            }
            data.customer_level_notes = customerLevelNotes;
        }
        
        // acquaintance_duration
        const acqYears = form.querySelector('[name="activity_acq_years"]')?.value || '0';
        const acqMonths = form.querySelector('[name="activity_acq_months"]')?.value || '0';
        const acqLess = form.querySelector('[name="activity_acq_less"]')?.checked;
        
        if (acqLess) {
            data.acquaintance_duration = 'کمتر از 1 ماه';
        } else if (acqYears !== '0' || acqMonths !== '0') {
            data.acquaintance_duration = `${acqYears} سال ${acqMonths} ماه`;
        }
        
        // has_previous_purchase
        const hasPurchaseEl = form.querySelector('[name="activity_has_purchase"]');
        if (hasPurchaseEl && hasPurchaseEl.value !== '') {
            data.has_previous_purchase = hasPurchaseEl.value;
        }
        
        // previous_purchase_number
        const prevPurchaseNum = form.querySelector('[name="activity_previous_number"]')?.value;
        if (prevPurchaseNum) {
            data.previous_purchase_number = prevPurchaseNum.replace(/\D/g, '');
        }
        
        // ثبت تاریخ جلالی/میلادی برای مرتب‌سازی
        if (typeof persianDate !== 'undefined') {
            try {
                const p = new persianDate();
                const jalaliStr = p.format('YYYY/MM/DD');
                data.register_time_jalali = jalaliStr;
                const greg = persianToGregorian(jalaliStr);
                if (greg) {
                    data.register_time = greg;
                }
            } catch (e) {
                console.warn('Failed to set jalali date for lead', e);
            }
        }
        
        // Backend will automatically check for duplicate phone and create activity if needed
        // No need to check here - backend handles it gracefully
        
        const res = await this.req(mode === 'edit' ? 'update_lead' : 'create_lead', data);
        if(res.status === 'success') {
            // Duplicate flow
            if (res.is_duplicate) {
                console.log('Duplicate response:', res); // Debug log
                const matches = Array.isArray(res.matches) ? res.matches : [];
                // اولویت: استفاده از owner_name و owner_id از پاسخ backend
                const currentOwnerName = res.owner_name || 'نامشخص';
                const currentOwnerId = res.owner_id || '';
                const contactName = res.contact_name || '---';
                const matchedPhone = res.matched_phone || (data.mobile_phone || '---');
                const existingDeals = Array.isArray(res.deal_ids) ? res.deal_ids.length : 0;
                const nextDealIndex = existingDeals + 1;

                const result = await Swal.fire({
                    icon: 'warning',
                    title: 'شماره تکراری',
                    html: `
                        <div class="text-right">
                            <div class="mb-2"><strong>لید:</strong> ${contactName}</div>
                            <div class="mb-2"><strong>شماره تکراری:</strong> ${matchedPhone}</div>
                            <div class="mb-2"><strong>مسئول فعلی:</strong> ${currentOwnerName || res.owner_id}</div>
                            ${matches.length ? `
                              <div class="mt-2 text-sm">
                                ${matches.map(m => `<div>شماره ${m.phone} → ${m.contact_name || '---'} (${m.owner_name || m.owner_id || '---'})</div>`).join('')}
                              </div>` : ''}
                            <div class="mt-3 text-sm text-gray-300">مسئول قابل تغییر نیست؛ معامله جدید برای همین مسئول ثبت می‌شود.</div>
                            <div class="mt-2 text-sm">عنوان معامله بعدی بر اساس شماره اصلی ساخته می‌شود.</div>
                        </div>
                    `,
                    showCancelButton: true,
                    confirmButtonText: 'ثبت معامله جدید',
                    cancelButtonText: 'انصراف',
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    focusConfirm: false
                });

                if (result.isConfirmed) {
                    // فقط برای همان مسئول فعلی معامله جدید بساز؛ عنوان را از سرور بگیر
                    const targetContactId = res.didar_contact_id || res.contact_id;
                    let dealTitle = '';
                    try {
                        const titleRes = await this.req('generate_deal_title', {contact_id: targetContactId});
                        if (titleRes?.status === 'success' && titleRes.title) {
                            dealTitle = titleRes.title;
                        }
                    } catch (err) {
                        console.error('generate_deal_title failed:', err);
                    }
                    // fallback در صورت بروز خطا
                    if (!dealTitle) {
                        const ordinals = ['اول', 'دوم', 'سوم', 'چهارم', 'پنجم', 'ششم', 'هفتم', 'هشتم', 'نهم', 'دهم'];
                        const idx = nextDealIndex - 1;
                        const ordinal = ordinals[idx] || `${nextDealIndex}م`;
                        dealTitle = `معامله ${ordinal} ${res.matched_phone || ''}`.trim();
                    }
                    if (!targetContactId || !currentOwnerId) {
                        Swal.fire('خطا', 'اطلاعات لید تکراری یا مسئول یافت نشد', 'error');
                        return;
                    }

                    const dealRes = await this.req('save_deal', {
                        contact_id: targetContactId,
                        title: dealTitle,
                        pipeline_stage: 'معامله جدید',
                        deal_status: 'Pending',
                        acquaintance_source: data.acquaintance_source || res.person?.acquaintance_source || '',
                        acquaintance_detail: data.acquaintance_detail || res.person?.acquaintance_detail || '',
                        content_topic: data.content_topic || res.person?.content_topic || '',
                        from_duplicate_lead_modal: '1'
                    });
                    if (dealRes.status !== 'success') {
                        Swal.fire('خطا', dealRes.message || 'خطا در ثبت معامله', 'error');
                        return;
                    }

                    Swal.fire({
                        icon: 'success',
                        title: 'ثبت شد',
                        html: `این شماره قبلاً ثبت شده بود. معامله جدید با عنوان "${dealTitle}" برای مسئول فعلی ثبت شد.`,
                        confirmButtonText: 'باشه'
                    });

                    this.closeModal('modalLead');
                    form.reset();
                    form.dataset.contactId = '';
                    form.dataset.mode = 'create';
                    this.loadLeads();

                    if (targetContactId) {
                        try {
                            this.openPersonProfile(targetContactId);
                        } catch (e) {
                            console.warn('Failed to load duplicate lead profile', e);
                        }
                    }
                } else {
                    Swal.fire({
                        icon: 'info',
                        title: 'لغو شد',
                        text: 'ثبت لید جدید انجام نشد',
                        timer: 2000,
                        showConfirmButton: false
                    });
                }

                return;
            }

            // Non-duplicate flow
            // Note: `saveLeadActivityExtras` call removed because `update_lead` API now handles
            // saving additional fields (job_desc, extra_info, etc.) directly.
            // Refresh current profile and leads list without needing manual refresh
            if (this.state.currentLead && this.state.currentLead.didar_contact_id === data.contact_id) {
                await this.openPersonProfile(data.contact_id);
            }
            // Always refresh leads list to show ownership changes
            this.loadLeads();

            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'انجام شد', showConfirmButton:false, timer:2000});
            this.closeModal('modalLead');
            form.reset();
            form.dataset.contactId = '';
            form.dataset.mode = 'create';
            
            if (mode === 'edit' && res.person) {
                this.state.currentLead = res.person;
                this.openPersonProfile(res.person.didar_contact_id);
            }

            // Hide duplicate warning if shown
            const warningDiv = document.getElementById('duplicate-phone-warning');
            if (warningDiv) {
                warningDiv.classList.add('hidden');
            }
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    async createNewRequestActivity(contactId = null) {
        // Function functionality pending implementation or restoration
        console.warn('createNewRequestActivity called but logic is missing.');
        if (!contactId) {
             const warningDiv = document.getElementById('duplicate-phone-warning');
             if (warningDiv && !warningDiv.classList.contains('hidden')) {
                 // Try to find contact ID from warning context if needed
             }
        }
        // Placeholder
    },
    
    // LOAD LEADS (restored)
	    async loadLeads(page = 1) {
	        try {
	            const search = document.getElementById('leads-search')?.value || '';
	            const customerCodeSearch = document.getElementById('leads-customer-code-search')?.value || '';
	            const saleStatus = document.getElementById('leads-stage-filter')?.value || 'all';
	            const ownerId = document.getElementById('leads-owner-filter')?.value || '';
	            const productRaw = document.getElementById('leads-product-filter')?.value || '';
	            const customerLevel = document.getElementById('leads-customer-level-filter')?.value || '';
	            const registerFromRaw = document.getElementById('leads-register-from')?.value || '';
	            const registerToRaw = document.getElementById('leads-register-to')?.value || '';
	            const activityFromRaw = document.getElementById('leads-activity-from')?.value || '';
	            const activityToRaw = document.getElementById('leads-activity-to')?.value || '';

	            const payload = {
	                search,
	                customer_code_search: customerCodeSearch,
	                sale_status: saleStatus,
	                bucket: 'all',
	                page,
	                per_page: this.state.leadsPagination?.perPage || 20
	            };
	            if (ownerId) {
	                payload.owner_id = ownerId;
	            }
	            if (customerLevel) {
	                payload.customer_level = customerLevel;
	            }

                const registerFromJ = toEnglishDigits((registerFromRaw || '').trim());
                const registerToJ = toEnglishDigits((registerToRaw || '').trim());
                const activityFromJ = toEnglishDigits((activityFromRaw || '').trim());
                const activityToJ = toEnglishDigits((activityToRaw || '').trim());

                const registerFromG = registerFromJ ? (persianToGregorian(registerFromJ, '00:00') || '') : '';
                const registerToG = registerToJ ? (persianToGregorian(registerToJ, '23:59') || '') : '';
                const activityFromG = activityFromJ ? (persianToGregorian(activityFromJ, '00:00') || '') : '';
                const activityToG = activityToJ ? (persianToGregorian(activityToJ, '23:59') || '') : '';

                if (registerFromG) payload.register_date_from = registerFromG.split(' ')[0];
                if (registerToG) payload.register_date_to = registerToG.split(' ')[0];
                if (registerFromJ) payload.register_date_from_jalali = registerFromJ;
                if (registerToJ) payload.register_date_to_jalali = registerToJ;
                if (activityFromG) payload.activity_date_from = activityFromG.split(' ')[0];
                if (activityToG) payload.activity_date_to = activityToG.split(' ')[0];
                if (productRaw) {
                    if (String(productRaw).startsWith('prod::')) {
                        payload.product_id = String(productRaw).slice(6);
                    } else if (String(productRaw).startsWith('svc::')) {
                        payload.product_name = String(productRaw).slice(5);
                    } else if (/^\d+$/.test(productRaw)) {
                        payload.product_id = productRaw;
                    } else {
                        payload.product_name = productRaw;
                    }
                }

                const res = await this.req('get_leads', payload);
                if (res.status === 'success') {
                    this.state.leads = res.leads || [];
                    if (res.pagination) {
                        this.state.leadsPagination = {
                            currentPage: res.pagination.current_page,
                            perPage: res.pagination.per_page,
                            totalCount: res.pagination.total_count,
                            totalPages: res.pagination.total_pages
                        };
                    }
                    this.renderLeads();
                    this.renderLeadsPagination();
                    if (this.featureFlags.historyV1) this.replaceCurrentHistoryUiState('leads_pagination');
                } else {
                    console.error('Error loading leads:', res.message || 'Unknown error');
                    this.state.leads = [];
                    const container = document.getElementById('leads-list');
                    if (container) {
                        container.innerHTML = `<div class="text-center py-8 text-red-500">خطا در بارگذاری لیدها: ${this.escapeHtml(res.message || 'خطای نامشخص')}</div>`;
                    }
                }
        } catch (error) {
            console.error('Exception loading leads:', error);
            this.state.leads = [];
            const container = document.getElementById('leads-list');
            if (container) {
                container.innerHTML = `<div class="text-center py-8 text-red-500">خطا در بارگذاری لیدها: ${this.escapeHtml(error.message || 'خطای اتصال')}</div>`;
            }
        }
    },

    async loadBlataklifLeads(page = 1) {
        const search = document.getElementById('blataklif-leads-search')?.value || '';
        const saleStatus = document.getElementById('blataklif-leads-sale-status-filter')?.value || 'all';

        const payload = {
            search,
            sale_status: saleStatus,
            bucket: 'blataklif',
            page,
            per_page: this.state.blataklifLeadsPagination?.perPage || 50
        };

        const res = await this.req('get_leads', payload);
        if (res.status === 'success') {
            this.state.blataklifLeads = res.leads || [];
            if (res.pagination) {
                this.state.blataklifLeadsPagination = {
                    currentPage: res.pagination.current_page,
                    perPage: res.pagination.per_page,
                    totalCount: res.pagination.total_count,
                    totalPages: res.pagination.total_pages
                };
            }
            this.renderBlataklifLeads();
            this.renderBlataklifLeadsPagination();
        }
    },

    renderBlataklifLeads() {
        const container = document.getElementById('blataklif-leads-list');
        if (!container) return;

        const list = Array.isArray(this.state.blataklifLeads) ? this.state.blataklifLeads : [];
        if (list.length === 0) {
            container.innerHTML = '<div class="text-center text-gray-500 py-8">لیدی یافت نشد</div>';
            return;
        }

        container.innerHTML = list.map(lead => {
            const statusKey = lead.sale_status === 1 ? 'success' : (lead.sale_status === 0 ? 'failed' : 'unknown');
            const statusMeta = this.state.saleStatusMap[statusKey] || this.state.saleStatusMap.unknown;
            const regDate = lead.register_time_jalali || (lead.register_time ? toPersianDate(lead.register_time) : '');
            const products = this.escapeHtml(this.getLeadProductsDisplayValue(lead)) || '-';
            const commissionRate = Number(lead.commission_rate_percent || 0);
            const commissionEstimated = Number(lead.commission_estimated_amount || 0);
            const hasCommission = !!lead.matched;
            const commissionHint = hasCommission
                ? (commissionEstimated > 0
                    ? `پورسانت احتمالی: ${this.formatMoney(commissionEstimated)} (نرخ ${commissionRate.toFixed(2)}%)`
                    : `نرخ پورسانت لید: ${commissionRate.toFixed(2)}%`)
                : 'قانون پورسانت فعال برای این لید یافت نشد';

            return `
                <div class="glass p-4 hover:bg-gray-800/50 transition cursor-pointer lead-card-desktop" onclick="app.openPersonProfile('${lead.didar_contact_id || lead.id || ''}')">
                    <div class="flex justify-between items-start mb-2">
                        <div>
                            <div class="font-bold text-white text-sm lead-name">${lead.first_name || ''} ${lead.last_name || ''}</div>
                            <div class="text-xs text-gray-400 mt-1">${lead.mobile_phone || '-'} ${lead.email ? '| ' + lead.email : ''}</div>
                            <div class="text-[11px] text-gray-500 mt-1">مسئول: ${lead.owner_name || '---'}</div>
                        </div>
                        <div class="flex flex-col items-end gap-1">
                            <span class="text-xs px-2 py-1 rounded ${statusMeta.bg} text-white">${statusMeta.icon} ${statusMeta.title}</span>
                        </div>
                    </div>
                    <div class="text-[11px] text-gray-400 mt-1 lead-products">محصولات: ${products}</div>
                    <div class="text-[11px] mt-1 ${hasCommission ? 'text-emerald-400' : 'text-gray-500'}">${this.escapeHtml(commissionHint)}</div>
                    <div class="flex justify-between items-center mt-2 text-xs text-gray-500">
                        <span>تاریخ ثبت: ${regDate || '-'}</span>
                        <span>${statusMeta.title}</span>
                    </div>
                </div>
            `;
        }).join('');
    },

    renderBlataklifLeadsPagination() {
        const container = document.getElementById('blataklif-leads-pagination');
        if (!container) return;

        const p = this.state.blataklifLeadsPagination || { currentPage: 1, perPage: 50, totalCount: 0, totalPages: 0 };
        if (p.totalPages <= 1) {
            container.innerHTML = '';
            return;
        }

        const maxVisiblePages = 5;
        let startPage = Math.max(1, p.currentPage - Math.floor(maxVisiblePages / 2));
        let endPage = Math.min(p.totalPages, startPage + maxVisiblePages - 1);
        if (endPage - startPage + 1 < maxVisiblePages) {
            startPage = Math.max(1, endPage - maxVisiblePages + 1);
        }

        let paginationHTML = '<div class="flex justify-center items-center gap-2 mt-4">';

        if (p.currentPage > 1) {
            paginationHTML += `<button onclick="app.loadBlataklifLeads(${p.currentPage - 1})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">قبلی</button>`;
        } else {
            paginationHTML += `<button disabled class="px-3 py-1 bg-gray-800 text-gray-600 rounded text-sm cursor-not-allowed">قبلی</button>`;
        }

        if (startPage > 1) {
            paginationHTML += `<button onclick="app.loadBlataklifLeads(1)" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">1</button>`;
            if (startPage > 2) paginationHTML += '<span class="text-gray-500">...</span>';
        }

        for (let i = startPage; i <= endPage; i++) {
            const activeClass = i === p.currentPage ? 'bg-yellow-600 text-white' : 'bg-gray-700 hover:bg-gray-600 text-white';
            paginationHTML += `<button onclick="app.loadBlataklifLeads(${i})" class="px-3 py-1 ${activeClass} rounded text-sm transition">${i}</button>`;
        }

        if (endPage < p.totalPages) {
            if (endPage < p.totalPages - 1) paginationHTML += '<span class="text-gray-500">...</span>';
            paginationHTML += `<button onclick="app.loadBlataklifLeads(${p.totalPages})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">${p.totalPages}</button>`;
        }

        if (p.currentPage < p.totalPages) {
            paginationHTML += `<button onclick="app.loadBlataklifLeads(${p.currentPage + 1})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">بعدی</button>`;
        } else {
            paginationHTML += `<button disabled class="px-3 py-1 bg-gray-800 text-gray-600 rounded text-sm cursor-not-allowed">بعدی</button>`;
        }

        paginationHTML += `<span class="text-xs text-gray-400 ml-4">نمایش ${(p.currentPage - 1) * p.perPage + 1} تا ${Math.min(p.currentPage * p.perPage, p.totalCount)} از ${p.totalCount}</span>`;
        paginationHTML += '</div>';
        container.innerHTML = paginationHTML;
    },

	    renderLeads() {
	        const container = document.getElementById('leads-list');
	        if(!container) return;
        
        if(this.state.leads.length === 0) {
            const searchValue = document.getElementById('leads-search')?.value || '';
            
            if (searchValue.trim()) {
                container.innerHTML = `
                    <div class="text-center py-12">
                        <div class="mb-4">
                            <i class="fa-solid fa-search text-4xl text-gray-600"></i>
                        </div>
                        <p class="text-gray-400 font-semibold text-lg">لیدی با این مشخصات یافت نشد</p>
                        <p class="text-gray-500 text-sm mt-3">جستجو: <span class="text-yellow-500 font-mono">${this.escapeHtml(searchValue)}</span></p>
                        <p class="text-gray-600 text-xs mt-4">
                            💡 احتمالاً این لید برای مسئول دیگری است یا هنوز ثبت نشده است
                        </p>
                        <button onclick="document.getElementById('leads-search').value = ''; app.loadLeads();" class="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition">
                            پاک کردن جستجو
                        </button>
                    </div>
                `;
            } else {
                container.innerHTML = '<div class="text-center text-gray-500 py-8">لیدی یافت نشد</div>';
            }
            return;
        }
        
	        container.innerHTML = this.state.leads.map(lead => {
	            const statusKey = lead.sale_status === 1 ? 'success' : (lead.sale_status === 0 ? 'failed' : 'unknown');
	            const statusMeta = this.state.saleStatusMap[statusKey] || this.state.saleStatusMap.unknown;
	            const regDate = lead.register_time_jalali || (lead.register_time ? toPersianDate(lead.register_time) : '');
	            const products = this.escapeHtml(this.getLeadProductsDisplayValue(lead)) || '-';
                const commissionRate = Number(lead.commission_rate_percent || 0);
                const commissionEstimated = Number(lead.commission_estimated_amount || 0);
                const hasCommission = !!lead.matched;
                const commissionHint = hasCommission
                    ? (commissionEstimated > 0
                        ? `پورسانت احتمالی: ${this.formatMoney(commissionEstimated)} (نرخ ${commissionRate.toFixed(2)}%)`
                        : `نرخ پورسانت لید: ${commissionRate.toFixed(2)}%`)
                    : 'قانون پورسانت فعال برای این لید یافت نشد';
	            
	            return `
	                <div class="glass p-4 hover:bg-gray-800/50 transition cursor-pointer" onclick="app.openPersonProfile('${lead.didar_contact_id || lead.id || ''}')">
	                    <div class="flex justify-between items-start mb-2">
	                        <div>
                            <div class="font-bold text-white text-sm">${lead.first_name || ''} ${lead.last_name || ''}</div>
                            <div class="text-xs text-gray-400 mt-1">${lead.mobile_phone || '-'} ${lead.email ? '| ' + lead.email : ''}</div>
                            <div class="text-[11px] text-gray-500 mt-1">مسئول: ${lead.owner_name || '---'}</div>
	                        </div>
	                        <div class="flex flex-col items-end gap-1">
	                            <span class="text-xs px-2 py-1 rounded ${statusMeta.bg} text-white">${statusMeta.icon} ${statusMeta.title}</span>
	                        </div>
	                    </div>
	                    <div class="text-[11px] text-gray-400 mt-1">محصولات: ${products}</div>
                        <div class="text-[11px] mt-1 ${hasCommission ? 'text-emerald-400' : 'text-gray-500'}">${this.escapeHtml(commissionHint)}</div>
	                    <div class="flex justify-between items-center mt-2 text-xs text-gray-500">
	                        <span>تاریخ ثبت: ${regDate || '-'}</span>
                        <span>${statusMeta.title}</span>
                    </div>
                </div>
            `;
        }).join('');
    },

    // DELETE LEAD (admin only)
	    async deleteLead(contactId) {
        if (!this.canActionClient('leads.delete')) {
            Swal.fire('خطا', 'دسترسی به حذف لید ندارید', 'error');
            return;
        }

        if (!contactId) {
            Swal.fire('خطا', 'شناسه لید مشخص نشده است', 'error');
            return;
        }

        const result = await Swal.fire({
            title: 'آیا مطمئن هستید؟',
            text: 'این لید و اطلاعات مرتبط (فعالیت‌ها و معاملات محلی) حذف خواهد شد',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            cancelButtonColor: '#3085d6',
            confirmButtonText: 'بله، حذف کن',
            cancelButtonText: 'لغو',
            reverseButtons: true
        });

        if (!result.isConfirmed) {
            return;
        }

        Swal.fire({
            title: 'در حال حذف...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('delete_lead', { contact_id: contactId });
            Swal.close();
            if (res.status === 'success') {
                Swal.fire('موفق', res.message || 'لید با موفقیت حذف شد', 'success');
                // Refresh leads list
                if (document.getElementById('leads-list')) {
                    this.loadLeads(1);
                }
                // Refresh deals list if open
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }
                // Close profile modal if this lead is open
                if (window.currentContactId && window.currentContactId === contactId) {
                    this.closeModal('modalProfile');
                    window.currentContactId = null;
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در حذف لید', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در حذف لید: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    renderLeadsPagination() {
        const container = document.getElementById('leads-pagination');
        if(!container) return;

        // On mobile the floating sticky pager (renderStickyPagination) is the
        // pagination UI; keep this node as an empty bottom spacer only so we
        // don't render a second, overlapping pagination bar.
        if (window.innerWidth < 768) { container.innerHTML = ''; return; }

        const p = this.state.leadsPagination;
        
        // Calculate page range to show
        const maxVisiblePages = 5;
        let startPage = Math.max(1, p.currentPage - Math.floor(maxVisiblePages / 2));
        let endPage = Math.min(p.totalPages, startPage + maxVisiblePages - 1);
        
        // Adjust if we're near the end
        if (endPage - startPage < maxVisiblePages - 1) {
            startPage = Math.max(1, endPage - maxVisiblePages + 1);
        }
        // Fix for negative startPage if totalPages is small
        startPage = Math.max(1, startPage);
        
        let paginationHTML = '<div class="flex items-center justify-center gap-2 mt-4 flex-wrap">';
        
        // Info text
        const from = p.totalCount > 0 ? (p.currentPage - 1) * p.perPage + 1 : 0;
        const to = Math.min(p.currentPage * p.perPage, p.totalCount);
        paginationHTML += `<span class="text-xs text-gray-400 ml-4 order-last md:order-first w-full md:w-auto text-center">نمایش ${from} تا ${to} از ${p.totalCount}</span>`;
        
        // Previous button
        if (p.currentPage > 1) {
            paginationHTML += `<button onclick="app.loadLeads(${p.currentPage - 1})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">قبلی</button>`;
        } else {
            paginationHTML += `<button disabled class="px-3 py-1 bg-gray-800 text-gray-600 rounded text-sm cursor-not-allowed">قبلی</button>`;
        }
        
        // First page
        if (startPage > 1) {
            paginationHTML += `<button onclick="app.loadLeads(1)" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">1</button>`;
            if (startPage > 2) {
                paginationHTML += `<span class="text-gray-500">...</span>`;
            }
        }
        
        // Page numbers
        for (let i = startPage; i <= endPage; i++) {
            if (i === p.currentPage) {
                paginationHTML += `<button class="px-3 py-1 bg-blue-600 text-white rounded text-sm font-bold">${i}</button>`;
            } else {
                paginationHTML += `<button onclick="app.loadLeads(${i})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">${i}</button>`;
            }
        }
        
        // Last page
        if (endPage < p.totalPages) {
            if (endPage < p.totalPages - 1) {
                paginationHTML += `<span class="text-gray-500">...</span>`;
            }
            paginationHTML += `<button onclick="app.loadLeads(${p.totalPages})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">${p.totalPages}</button>`;
        }
        
        // Next button
        if (p.currentPage < p.totalPages) {
            paginationHTML += `<button onclick="app.loadLeads(${p.currentPage + 1})" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition">بعدی</button>`;
        } else {
            paginationHTML += `<button disabled class="px-3 py-1 bg-gray-800 text-gray-600 rounded text-sm cursor-not-allowed">بعدی</button>`;
        }
        
        paginationHTML += '</div>';
        container.innerHTML = paginationHTML;
    },


    // DEALS
    async loadDeals() {
	        const container = document.getElementById('deals-container');
	        if(!container) return;
	        container.innerHTML = '<div class="text-center py-20 col-span-full"><div class="loading-spinner mb-4 mx-auto"></div><p class="text-gray-400">در حال دریافت معاملات...</p></div>';
	        
	        const pipelineStageFilter = document.getElementById('deals-pipeline-filter')?.value || 'all';
	        const statusFilterRaw = document.getElementById('deals-status-filter')?.value || 'all';
	        const ownerId = document.getElementById('deals-owner-filter')?.value || '';
            const search = document.getElementById('deals-search')?.value || '';
            const customerCodeSearch = document.getElementById('deals-customer-code-search')?.value || '';
            
            const dateFromJ = (document.getElementById('deals-register-from')?.value || '').trim();
            const dateToJ = (document.getElementById('deals-register-to')?.value || '').trim();
            const dateFromG = dateFromJ ? (persianToGregorian(this.toEnglishDigits(dateFromJ), '00:00') || '') : '';
            const dateToG = dateToJ ? (persianToGregorian(this.toEnglishDigits(dateToJ), '23:59') || '') : '';
            const registerFrom = dateFromG ? String(dateFromG).split(' ')[0] : '';
            const registerTo = dateToG ? String(dateToG).split(' ')[0] : '';

            const actFromJ = (document.getElementById('deals-activity-from')?.value || '').trim();
            const actToJ = (document.getElementById('deals-activity-to')?.value || '').trim();
            const actFromG = actFromJ ? (persianToGregorian(this.toEnglishDigits(actFromJ), '00:00') || '') : '';
            const actToG = actToJ ? (persianToGregorian(this.toEnglishDigits(actToJ), '23:59') || '') : '';
            const activityFrom = actFromG ? String(actFromG).split(' ')[0] : '';
            const activityTo = actToG ? String(actToG).split(' ')[0] : '';
            
	        const statusMap = { pending: 'Pending', won: 'Won', lost: 'Lost', all: 'all' };
	        const statusFilter = statusMap[statusFilterRaw] || 'all';
	        
            const payload = { 
                pipeline_stage: pipelineStageFilter, 
                status: statusFilter,
                search: search,
                customer_code_search: customerCodeSearch,
                legacy: '0'
            };
            // Parked/active filter: read the dropdown (source of truth). The dashboard
            // "parked stage" cards set this dropdown via goToDealsWithFilter so the two stay
            // in sync. 'all' => no bucket filter (show both).
            const bucketFilter = document.getElementById('deals-bucket-filter')?.value || 'all';
            this.state.dealsPipelineBucketFilter = (bucketFilter && bucketFilter !== 'all') ? bucketFilter : '';
            if (this.state.dealsPipelineBucketFilter) {
                payload.pipeline_bucket = this.state.dealsPipelineBucketFilter;
            }
	        if (ownerId) payload.owner_id = ownerId;
            if (registerFrom) payload.register_date_from = registerFrom;
            if (registerTo) payload.register_date_to = registerTo;
            if (activityFrom) payload.activity_date_from = activityFrom;
            if (activityTo) payload.activity_date_to = activityTo;
            
	        const PAGE_SIZE = 60;
            payload.limit = PAGE_SIZE;
            payload.offset = 0;
            this.state.dealsPayload = payload;
            this.state.dealsLoadedCount = 0;

            const res = await this.req('get_deals', payload);
            if (res.status === 'success' && res.deals && res.deals.length) {
                container.innerHTML = res.deals.map(d => this.dealCardHtml(d)).join('');
                this.state.dealsLoadedCount = res.deals.length;
                this.renderDealsLoadMore(res.deals.length >= PAGE_SIZE);
            } else {
                container.innerHTML = `
                <div class="col-span-full py-20 text-center">
                    <i class="fa-solid fa-sack-dollar text-5xl text-gray-800 mb-4 block"></i>
                    <p class="text-gray-500">معامله‌ای یافت نشد</p>
                </div>`;
                this.renderDealsLoadMore(false);
            }
            if (this.featureFlags.historyV1) this.replaceCurrentHistoryUiState('deals_filters');
	    },

    dealCardHtml(d) {
                    const price = d.estimated_price ? parseInt(d.estimated_price).toLocaleString() : '0';
                    const commission = d.commission || null;
                    const commissionAmount = Number(commission?.calculated_commission || 0);
                    const commissionRate = Number(commission?.applied_rate || commission?.base_rate_percent || 0);
                    const hasCommission = commissionAmount > 0 || commissionRate > 0;
                    
                    const statusConfig = {
                        'Won': { text: 'موفق', badge: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30' },
                        'Lost': { text: 'ناموفق', badge: 'bg-red-500/20 text-red-400 border-red-500/30' },
                        'Pending': { text: 'جاری', badge: 'bg-blue-500/20 text-blue-400 border-blue-500/30' }
                    };
                    const status = statusConfig[d.status] || statusConfig['Pending'];
                    // Shared accent-stripe class (mobile-only CSS; inert on desktop).
                    const dealSClass = d.status === 'Won' ? 's--success' : (d.status === 'Lost' ? 's--danger' : 's--info');

                    const pipelineStage = d.pipeline_stage || 'معامله جدید';
                    const isParked = String(d.pipeline_bucket || 'active') === 'parked';
                    const stageBadge = (()=>{
                        if (pipelineStage === 'برگزاری دوره') return '<span class="px-1.5 py-0.5 rounded text-[9px] bg-amber-500/20 text-amber-300 border border-amber-500/30 ml-1">دوره</span>';
                        if (pipelineStage === 'پشتیبانی') return '<span class="px-1.5 py-0.5 rounded text-[9px] bg-indigo-500/20 text-indigo-200 border border-indigo-500/30 ml-1">پشتیبانی</span>';
                        if (pipelineStage === 'مرجوعی' || (d.status === 'Lost' && (d.refund === '1' || d.refund_reason))) return '<span class="px-1.5 py-0.5 rounded text-[9px] bg-red-500/20 text-red-300 border border-red-500/30 ml-1">مرجوعی</span>';
                        return '';
                    })();

                    const productBadges = (d.products || []).map(p => 
                        `<span class="px-2 py-0.5 rounded-md text-[9px] bg-gray-700/50 text-gray-300 border border-gray-600/30 whitespace-nowrap">${this.escapeHtml(p.name)}</span>`
                    ).join('');

                    const contactId = d.contact_didar_id || d.contact_id || d.contactId || (d.contact ? d.contact.didar_contact_id : '') || d.local_person_id;
                    const dealId = d.didar_deal_id || d.id;
                    
                    return `
                    <div class="glass p-5 rounded-2xl border border-gray-700/50 hover:border-yellow-500/40 transition-all duration-500 group flex flex-col h-full relative overflow-hidden accent-card ${dealSClass}">
                        <!-- Top Row: Status & Price -->
                        <div class="flex justify-between items-start mb-4">
                            <span class="text-[9px] px-2.5 py-1 rounded-full font-bold border ${status.badge}">
                                ${status.text}
                            </span>
                            <div class="text-left">
                                <span class="block text-[8px] text-gray-500 mb-0.5">مبلغ معامله</span>
                                <span class="text-xs font-black text-yellow-500 font-mono">${price} <span class="text-[9px] font-normal opacity-70">T</span></span>
                            </div>
                        </div>

                        <!-- Customer Info -->
                        <div class="mb-4 cursor-pointer" onclick="app.openDealModal('${dealId}')">
                            <h4 class="text-white font-bold text-sm mb-1 group-hover:text-yellow-500 transition-colors truncate" title="${this.escapeHtml(d.contact_name || '')}">
                                ${this.escapeHtml(d.contact_name || 'نامشخص')}
                            </h4>
                            <div class="flex items-center gap-2 text-[10px] text-gray-400">
                                <i class="fa-solid fa-briefcase text-[8px] opacity-50"></i>
                                <span class="truncate opacity-80" title="${this.escapeHtml(d.title || '')}">${this.escapeHtml(d.title || 'بدون عنوان')}</span>
                            </div>
                        </div>

                        <!-- Details Section -->
                        <div class="space-y-2 mb-4">
                            <!-- Pipeline Stage: Full Width for better visibility -->
                            <div class="bg-gray-900/60 p-2.5 rounded-xl border border-gray-800/50 flex flex-col gap-1">
                                <span class="text-[8px] text-gray-500 font-bold flex items-center gap-1">
                                    <i class="fa-solid fa-layer-group opacity-50"></i>
                                    مرحله کاریز
                                </span>
                                <div class="flex items-center gap-2">
                                    <span class="text-[11px] text-gray-200 font-bold">${pipelineStage}</span>
                                    ${stageBadge}
                                    ${isParked ? '<span class="px-1.5 py-0.5 rounded text-[9px] bg-amber-500/20 text-amber-200 border border-amber-500/30">پارک‌شده</span>' : ''}
                                </div>
                            </div>
                            
                            <!-- Sales Owner -->
                            <div class="bg-gray-900/40 p-2 rounded-xl border border-gray-800/50 flex items-center justify-between">
                                <span class="text-[8px] text-gray-500 font-bold">مسئول فروش</span>
                                <div class="flex items-center gap-1.5">
                                    <div class="w-4 h-4 rounded-full bg-blue-500/20 flex items-center justify-center text-[8px] text-blue-400 font-bold border border-blue-500/30">
                                        ${(d.owner_name || '?').charAt(0)}
                                    </div>
                                    <span class="text-[10px] text-gray-300 font-bold">${this.escapeHtml(d.owner_name || '---')}</span>
                                </div>
                            </div>
                            <div class="bg-emerald-900/20 p-2 rounded-xl border border-emerald-700/40 flex items-center justify-between">
                                <span class="text-[8px] text-emerald-300 font-bold">پورسانت</span>
                                <div class="text-left">
                                    <div class="text-[10px] text-emerald-300 font-bold">${hasCommission ? this.formatMoney(commissionAmount) : '---'}</div>
                                    <div class="text-[9px] text-emerald-400/80">${hasCommission && commissionRate > 0 ? `نرخ ${commissionRate.toFixed(2)}%` : 'بدون قانون فعال'}</div>
                                </div>
                            </div>
                        </div>

                        <!-- Products List -->
                        ${productBadges ? `
                        <div class="mb-4 px-1">
                            <div class="flex flex-wrap gap-1.5">
                                ${productBadges}
                            </div>
                        </div>` : ''}

                        <!-- Footer Actions -->
                        <div class="mt-auto pt-4 border-t border-gray-800/50 flex justify-between items-center gap-2">
                            <div class="flex items-center gap-1 text-[9px] text-gray-500 italic">
                                <i class="fa-solid fa-calendar-day opacity-50"></i>
                                ${d.register_time ? toPersianDate(d.register_time) : '---'}
                            </div>
                            <div class="flex gap-1.5">
                                <button onclick="app.openDealModal('${dealId}')" 
                                    class="text-[10px] bg-amber-500/10 hover:bg-amber-500 text-amber-500 hover:text-black px-3 py-1.5 rounded-xl border border-amber-500/20 transition-all font-bold flex items-center gap-1">
                                    <i class="fa-solid fa-edit text-[9px]"></i>
                                    ویرایش
                                </button>
                                <button onclick="app.openPersonProfile('${contactId}')" 
                                    class="text-[10px] bg-blue-600/10 hover:bg-blue-600 text-blue-400 hover:text-white px-3 py-1.5 rounded-xl border border-blue-500/20 transition-all font-bold flex items-center gap-1">
                                    <i class="fa-solid fa-user text-[9px]"></i>
                                    مشاهده لید
                                </button>
                            </div>
                        </div>
                    </div>`;
                },

    renderDealsLoadMore(hasMore) {
        const container = document.getElementById('deals-container');
        if (!container) return;
        let lm = document.getElementById('deals-load-more');
        if (!hasMore) { if (lm) lm.remove(); return; }
        if (!lm) {
            lm = document.createElement('div');
            lm.id = 'deals-load-more';
            lm.className = 'col-span-full flex justify-center py-6';
        }
        lm.innerHTML = `<button onclick="app.loadMoreDeals()" class="px-5 py-2.5 rounded-xl bg-amber-500/10 hover:bg-amber-500 text-amber-400 hover:text-black border border-amber-500/30 transition-all font-bold text-sm flex items-center gap-2"><i class="fa-solid fa-chevron-down text-[11px]"></i>نمایش بیشتر</button>`;
        container.appendChild(lm);
    },

    async loadMoreDeals() {
        const container = document.getElementById('deals-container');
        if (!container || !this.state.dealsPayload) return;
        const PAGE_SIZE = this.state.dealsPayload.limit || 60;
        const payload = Object.assign({}, this.state.dealsPayload, { offset: this.state.dealsLoadedCount || 0 });
        const lm = document.getElementById('deals-load-more');
        if (lm) lm.innerHTML = '<div class="loading-spinner mx-auto"></div>';
        const res = await this.req('get_deals', payload);
        if (res.status === 'success' && res.deals && res.deals.length) {
            const html = res.deals.map(d => this.dealCardHtml(d)).join('');
            if (lm) lm.insertAdjacentHTML('beforebegin', html);
            else container.insertAdjacentHTML('beforeend', html);
            this.state.dealsLoadedCount = (this.state.dealsLoadedCount || 0) + res.deals.length;
            this.renderDealsLoadMore(res.deals.length >= PAGE_SIZE);
        } else {
            this.renderDealsLoadMore(false);
        }
    },

    ensureVendor(name) {
        if (!this._vendorPromises) this._vendorPromises = {};
        const assetBase = (window.APP_ASSET_BASE || '/assets').replace(/\/$/, '');
        const sources = {
            xlsx: assetBase + '/vendor/sheetjs/xlsx.full.min.js',
            apexcharts: assetBase + '/vendor/apexcharts/apexcharts.min.js',
            jspdf: assetBase + '/vendor/jspdf/jspdf.umd.min.js',
        };
        const ready = {
            xlsx: () => typeof XLSX !== 'undefined',
            apexcharts: () => typeof ApexCharts !== 'undefined',
            jspdf: () => !!(window.jspdf && window.jspdf.jsPDF),
        };
        const src = sources[name];
        if (!src) return Promise.resolve(false);
        if (ready[name] && ready[name]()) return Promise.resolve(true);
        if (this._vendorPromises[name]) return this._vendorPromises[name];
        this._vendorPromises[name] = new Promise((resolve, reject) => {
            const s = document.createElement('script');
            s.src = src;
            s.async = true;
            s.onload = () => resolve(true);
            s.onerror = () => { this._vendorPromises[name] = null; reject(new Error('vendor load failed: ' + src)); };
            document.head.appendChild(s);
        });
        return this._vendorPromises[name];
    },


        // Helper for quick filters
        setQuickStatusFilter(status) {
            const statusFilter = document.getElementById('deals-status-filter');
            if (statusFilter) {
                statusFilter.value = status;
                
                // Update chip active state
                document.querySelectorAll('.quick-filter-chip').forEach(chip => {
                    if (chip.getAttribute('data-status') === status) {
                        chip.classList.add('active');
                    } else {
                        chip.classList.remove('active');
                    }
                });
                
                this.loadDeals();
            }
        },
    
    // PERSONS
    async loadPersons() {
        const res = await this.req('get_leads', {});
        if(res.status === 'success') {
            this.state.leads = res.leads || [];
            this.renderLeads();
        }
    },

    async loadLegacyDeals() {
        const container = document.getElementById('legacy-deals-container');
        if (!container) return;
        container.innerHTML = '<div class="text-center py-20 col-span-full"><div class="loading-spinner mb-4 mx-auto"></div><p class="text-gray-400">\u062f\u0631 \u062d\u0627\u0644 \u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc \u0645\u0639\u0627\u0645\u0644\u0627\u062a \u06af\u0630\u0634\u062a\u0647...</p></div>';

        const statusFilterRaw = document.getElementById('legacy-deals-status-filter')?.value || 'all';
        const ownerId = document.getElementById('legacy-deals-owner-filter')?.value || '';
        const search = document.getElementById('legacy-deals-search')?.value || '';

        const statusMap = { pending: 'Pending', won: 'Won', lost: 'Lost', all: 'all' };
        const statusFilter = statusMap[statusFilterRaw] || 'all';

        const payload = {
            status: statusFilter,
            search: search,
            legacy: '1'
        };
        if (ownerId) payload.owner_id = ownerId;

        const res = await this.req('get_deals', payload);
        container.innerHTML = '';

        if (res.status === 'success' && res.deals && res.deals.length) {
            res.deals.forEach(d => {
                const price = d.estimated_price ? parseInt(d.estimated_price).toLocaleString() : '0';

                const statusConfig = {
                    'Won': { text: '\u0645\u0648\u0641\u0642', badge: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30' },
                    'Lost': { text: '\u0646\u0627\u0645\u0648\u0641\u0642', badge: 'bg-red-500/20 text-red-400 border-red-500/30' },
                    'Pending': { text: '\u062f\u0631 \u062c\u0631\u06cc\u0627\u0646', badge: 'bg-blue-500/20 text-blue-400 border-blue-500/30' }
                };
                const status = statusConfig[d.status] || statusConfig['Pending'];

                const pipelineStage = d.pipeline_stage || d.pipeline_stage_title || '\u0645\u0631\u062d\u0644\u0647 \u0646\u0627\u0645\u0634\u062e\u0635';
                const dealTitle = d.title || d.pipeline_stage_title || d.pipeline_stage || d.contact_name || '\u0646\u0627\u0645\u0634\u062e\u0635';
                const contactId = d.contact_didar_id || d.contact_id || d.contactId || (d.contact ? d.contact.didar_contact_id : '') || d.local_person_id;
                const dealId = d.didar_deal_id || d.id;

                container.innerHTML += `
                <div class="glass p-5 rounded-2xl border border-gray-700/50 hover:border-yellow-500/40 transition-all duration-500 group flex flex-col h-full relative overflow-hidden">
                    <div class="flex justify-between items-start mb-4">
                        <div class="flex items-center gap-2">
                            <span class="text-[9px] px-2.5 py-1 rounded-full font-bold border ${status.badge}">
                                ${status.text}
                            </span>
                            <span class="text-[9px] px-2 py-1 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-300">\u0645\u0639\u0627\u0645\u0644\u0647 \u06af\u0630\u0634\u062a\u0647</span>
                        </div>
                        <div class="text-left">
                            <span class="block text-[8px] text-gray-500 mb-0.5">\u0627\u0631\u0632\u0634 \u0645\u0639\u0627\u0645\u0644\u0647</span>
                            <span class="text-xs font-black text-yellow-500 font-mono">${price} <span class="text-[9px] font-normal opacity-70">T</span></span>
                        </div>
                    </div>

                    <div class="mb-4 cursor-pointer" onclick="app.openDealModal('${dealId}')">
                        <h4 class="text-white font-bold text-sm mb-1 group-hover:text-yellow-500 transition-colors truncate" title="${this.escapeHtml(d.contact_name || '')}">
                            ${this.escapeHtml(d.contact_name || '\u0646\u0627\u0645\u0634\u062e\u0635')}
                        </h4>
                        <div class="flex items-center gap-2 text-[10px] text-gray-400">
                            <i class="fa-solid fa-briefcase text-[8px] opacity-50"></i>
                            <span class="truncate opacity-80" title="${this.escapeHtml(dealTitle)}">${this.escapeHtml(dealTitle)}</span>
                        </div>
                    </div>

                    <div class="space-y-2 mb-4">
                        <div class="bg-gray-900/60 p-2.5 rounded-xl border border-gray-800/50 flex flex-col gap-1">
                            <span class="text-[8px] text-gray-500 font-bold flex items-center gap-1">
                                <i class="fa-solid fa-layer-group opacity-50"></i>
                                \u0645\u0631\u062d\u0644\u0647 \u0641\u0631\u0648\u0634
                            </span>
                            <div class="flex items-center gap-2">
                                <span class="text-[11px] text-gray-200 font-bold">${this.escapeHtml(pipelineStage)}</span>
                            </div>
                        </div>

                        <div class="bg-gray-900/40 p-2 rounded-xl border border-gray-800/50 flex items-center justify-between">
                            <span class="text-[8px] text-gray-500 font-bold">\u0645\u0633\u0626\u0648\u0644</span>
                            <div class="flex items-center gap-1.5">
                                <div class="w-4 h-4 rounded-full bg-blue-500/20 flex items-center justify-center text-[8px] text-blue-400 font-bold border border-blue-500/30">
                                    ${(d.owner_name || '?').charAt(0)}
                                </div>
                                <span class="text-[10px] text-gray-300 font-bold">${this.escapeHtml(d.owner_name || '---')}</span>
                            </div>
                        </div>
                    </div>

                    <div class="mt-auto pt-4 border-t border-gray-800/50 flex justify-between items-center gap-2">
                        <div class="flex items-center gap-1 text-[9px] text-gray-500 italic">
                            <i class="fa-solid fa-calendar-day opacity-50"></i>
                            <span>${d.register_time ? toPersianDateTime(d.register_time) : '\u0646\u0627\u0645\u0634\u062e\u0635'}</span>
                        </div>
                        ${contactId ? `<button class="text-[10px] text-yellow-500 hover:text-yellow-300" onclick="event.stopPropagation(); app.openPersonProfile('${contactId}')">\u0645\u0634\u0627\u0647\u062f\u0647 \u0644\u06cc\u062f</button>` : ''}
                    </div>
                </div>
                `;
            });
        } else {
            container.innerHTML = '<div class="text-center py-12 text-gray-500">\u0647\u06cc\u0686 \u0645\u0639\u0627\u0645\u0644\u0647 \u06af\u0630\u0634\u062a\u0647\u200c\u0627\u06cc \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f.</div>';
        }
    },

    // PERSON PROFILE
    openPersonProfile: async function(contactId) {
        const profileToken = this.createContextToken('profile', { contactId: String(contactId || '') });
        this.resetProfileModalState({ forOpen: true });
        const previousContactId = window.currentContactId;
        console.log('[CONTACT_SWITCH] Switching contact profile', {
            timestamp: new Date().toISOString(),
            previousContactId: previousContactId,
            newContactId: contactId,
            contactChanged: previousContactId !== contactId,
            url: window.location.href,
            userAgent: navigator.userAgent
        });

        window.currentContactId = contactId;
        document.getElementById('activity-contact-id').value = contactId;
        const dealContactId = document.getElementById('deal-contact-id');
        if (dealContactId) {
            dealContactId.value = contactId;
        }
        // Clear deal context when switching to a different contact to avoid cross-lead updates.
        if (previousContactId && previousContactId !== contactId) {
            const dealIdInput = document.getElementById('deal-id');
            const dealModal = document.getElementById('modalDeal');
            const isDealModalOpen = dealModal && !dealModal.classList.contains('hidden');
            
            if (dealIdInput) {
                // If deal modal is open and contact changed, clear deal-id to prevent errors
                if (isDealModalOpen && dealIdInput.value) {
                    dealIdInput.value = '';
                    const modalTitle = document.getElementById('deal-modal-title');
                    if (modalTitle) modalTitle.textContent = 'ثبت معامله جدید';
                    console.log('[CONTACT_SWITCH] Cleared deal-id because deal modal is open and contact changed');
                } else {
                    dealIdInput.value = '';
                }
            }
            const dealActDeal = document.getElementById('deal-activity-deal-id');
            if (dealActDeal) dealActDeal.value = '';
            const activityDealId = document.getElementById('activity-deal-id');
            if (activityDealId) activityDealId.value = '';
            this.state.currentDeal = null;
            console.log('[CONTACT_SWITCH] Cleared deal context to prevent cross-lead updates', {
                timestamp: new Date().toISOString(),
                previousContactId: previousContactId,
                newContactId: contactId,
                dealModalWasOpen: isDealModalOpen
            });
        }
        
        const res = await this.req('get_person_profile', {contact_id: contactId}, {
            requestKey: `profile:${String(contactId || '')}`,
            silentAbort: true
        });
        if (res?.aborted) return;
        if (this.dropIfStaleContext('profile', profileToken, { contactId: String(contactId || '') })) return;
        if(res.status === 'success') {
            const person = res.person;
            const fullName = `${person.first_name || ''} ${person.last_name || ''}`.trim() || 'بدون نام';
            this.state.currentLead = person;
            
            // ذخیره activities برای بررسی بعدی
            this.state.currentLeadActivities = res.activities || [];
            const stageSelect = document.getElementById('activity-virtual-stage');
            if (stageSelect) {
                stageSelect.value = '';
            }
            if (Array.isArray(res.deals)) {
                this.state.currentDeals = res.deals;
                this.populateActivityDealSelect(res.deals);
            } else {
                this.state.currentDeals = [];
                const activityDealId = document.getElementById('activity-deal-id');
                if (activityDealId) activityDealId.value = '';
                const stageDisplay = document.getElementById('activity-current-stage-display');
                if (stageDisplay) stageDisplay.innerHTML = '<span class="text-sm text-gray-500">مرحله تعیین نشده</span>';
            }
            const referralNoteInput = document.querySelector('textarea[name="referral_note"]');
            if (referralNoteInput) referralNoteInput.value = '';
            
            const referralPhones = [person.mobile_phone, person.secondary_mobile_phone].filter(Boolean).join(' ، ');
            const referralPhonesEl = document.getElementById('referral-phones');
            if (referralPhonesEl) referralPhonesEl.textContent = referralPhones || '-';
            
            document.getElementById('prof-name').textContent = fullName;
            document.getElementById('prof-mobile').textContent = person.mobile_phone || '-';
            const profEmail = document.getElementById('prof-email');
            if (profEmail) profEmail.textContent = person.email || '-';
            
            // Info tab
            document.getElementById('prof-info-name').textContent = fullName;
            const customerCodeEl = document.getElementById('prof-info-customer-code');
            if (customerCodeEl) customerCodeEl.textContent = String(person.customer_code || '-');
            document.getElementById('prof-info-mobile').textContent = person.mobile_phone || '-';
            document.getElementById('prof-info-mobile2').textContent = person.secondary_mobile_phone || '-';
            const mobile3El = document.getElementById('prof-info-mobile3');
            const mobile4El = document.getElementById('prof-info-mobile4');
            if (mobile3El) mobile3El.textContent = person.mobile_phone_3 || '-';
            if (mobile4El) mobile4El.textContent = person.mobile_phone_4 || '-';
            const profInfoEmail = document.getElementById('prof-info-email');
            if (profInfoEmail) profInfoEmail.textContent = person.email || '-';
            document.getElementById('prof-info-stage').innerHTML = '';
            document.getElementById('prof-info-owner').textContent = person.owner_name || '---';
            const prevOwnerEl = document.getElementById('prof-info-prev-owner');
            if (prevOwnerEl) prevOwnerEl.textContent = person.previous_owner_name || '-';
            const regDateDisplay = person.register_time_jalali || (person.register_time ? toPersianDate(person.register_time) : '-');
            const regEl = document.getElementById('prof-info-register');
            if (regEl) regEl.textContent = regDateDisplay || '-';
            const createdByContainer = document.getElementById('prof-info-created-by-container');
            const createdViaContainer = document.getElementById('prof-info-created-via-container');
            const createdByEl = document.getElementById('prof-info-created-by');
            const createdViaEl = document.getElementById('prof-info-created-via');
            const leadCreatedBy = String(person.created_by_name || '').trim();
            const leadCreatedVia = this.translateCreatedVia(person.created_via || '');
            if (createdByEl) createdByEl.textContent = leadCreatedBy || '-';
            if (createdViaEl) createdViaEl.textContent = leadCreatedVia || '-';
            if (createdByContainer) createdByContainer.classList.toggle('hidden', !this.isAdmin());
            if (createdViaContainer) createdViaContainer.classList.toggle('hidden', !this.isAdmin());
            const saleEl = document.getElementById('prof-info-sale-status');
            if (saleEl) {
                const ok = String(person.sale_status) === '1';
                saleEl.innerHTML = `<span class="s-pill ${ok ? 's--success' : 's--danger'}">${ok ? 'موفق' : 'ناموفق'}</span>`;
            }
            const nationalEl = document.getElementById('prof-info-national-id');
            if (nationalEl) nationalEl.textContent = person.national_id || '-';
            document.getElementById('prof-info-city').textContent = person.city || '-';
            document.getElementById('prof-info-job').textContent = person.job_title || '-';
            const jobDescEl = document.getElementById('prof-info-job-desc');
            if (jobDescEl) jobDescEl.textContent = person.job_description || '-';
            document.getElementById('prof-info-source').textContent = this.getLeadSourceLabel(person.acquaintance_source);
            document.getElementById('prof-info-source-detail').textContent = this.getLeadDetailLabel(person.acquaintance_detail);
            document.getElementById('prof-info-topic').textContent = this.getContentTopicLabel(person.content_topic);
            document.getElementById('prof-info-products').textContent = this.getLeadProductsDisplayValue(person);
            const acqEl = document.getElementById('prof-info-acq-duration');
            if (acqEl) acqEl.textContent = person.acquaintance_duration || '-';
            const extraEl = document.getElementById('prof-info-extra-info');
            if (extraEl) extraEl.textContent = person.extra_info || '-';

            const financialLabel = (val) => {
                switch (val) {
                    case 'high': return 'بالا';
                    case 'medium_high': return 'متوسط رو به بالا';
                    case 'medium_low': return 'متوسط رو به پایین';
                    case 'low': return 'پایین';
                    default: return '-';
                }
            };
            const rangeLabel = (val, type) => {
                const map = type === 'asset' ? {
                    '0-50': '0 تا 50 میلیون تومان',
                    '50-200': '50 تا 200 میلیون تومان',
                    '200-500': '200 تا 500 میلیون تومان',
                    '500-1000': '500 تا 1000 میلیون تومان',
                    '1000-plus': 'بیش از 1 میلیارد تومان'
                } : {
                    '0-20': '0 تا 20 میلیون تومان',
                    '20-100': '20 تا 100 میلیون تومان',
                    '100-200': '100 تا 200 میلیون تومان',
                    '200-500': '200 تا 500 میلیون تومان',
                    '500-plus': 'بیش از 500 میلیون تومان'
                };
                return map[val] || '-';
            };

        // Customer level
        const customerLevelEl = document.getElementById('prof-info-customer-level');
        if (customerLevelEl) {
            const lvl = person.customer_level;
            // Shared metal-tier pill (mobile-only CSS; plain text on desktop).
            const metal = lvl === 'طلایی' ? 's--gold' : (lvl === 'نقره‌ای' ? 's--silver' : (lvl === 'برنز' ? 's--bronze' : ''));
            if (metal) {
                customerLevelEl.innerHTML = `<span class="s-pill ${metal}">${lvl}</span>`;
            } else {
                customerLevelEl.textContent = '-';
            }
        }
        const customerLevelNotesEl = document.getElementById('prof-info-customer-level-notes');
        if (customerLevelNotesEl) {
            customerLevelNotesEl.textContent = person.customer_level_notes || '';
        }

        const finEl = document.getElementById('prof-info-financial-level');
        if (finEl) finEl.textContent = financialLabel(person.financial_level);
        const assetEl = document.getElementById('prof-info-asset');
        if (assetEl) assetEl.textContent = rangeLabel(person.asset_estimation, 'asset');
        const incomeEl = document.getElementById('prof-info-income');
        if (incomeEl) incomeEl.textContent = rangeLabel(person.income_estimation, 'income');
            const purchaseBadge = document.getElementById('prof-info-purchase-status');
            if (purchaseBadge) {
                if (person.has_previous_purchase === 1 || person.has_previous_purchase === '1') {
                    purchaseBadge.textContent = 'خرید قبلی دارد';
                } else if (person.has_previous_purchase === 0 || person.has_previous_purchase === '0') {
                    purchaseBadge.textContent = 'خرید قبلی ندارد';
                } else {
                    purchaseBadge.textContent = '-';
                }
            }

            // Requested services: try person first, then latest deal
            let reqServices = person.requested_services || '';
            if (typeof reqServices === 'string') {
                try {
                    const parsed = JSON.parse(reqServices);
                    if (Array.isArray(parsed)) reqServices = parsed;
                } catch (e) {
                    // keep string
                }
            }
            if (!reqServices || (Array.isArray(reqServices) && reqServices.length === 0)) {
                const currentDeal = (this.state.currentDeals || [])[0] || this.state.currentDeal;
                if (currentDeal && currentDeal.requested_services) {
                    try {
                        const parsed = JSON.parse(currentDeal.requested_services);
                        if (Array.isArray(parsed)) reqServices = parsed;
                    } catch (e) {
                        reqServices = currentDeal.requested_services;
                    }
                }
            }
            const reqEl = document.getElementById('prof-info-requested-services');
            if (reqEl) {
                if (Array.isArray(reqServices)) {
                    reqEl.textContent = reqServices.length ? reqServices.join('، ') : '-';
                } else if (reqServices) {
                    reqEl.textContent = reqServices;
                } else {
                    reqEl.textContent = '-';
                }
            }
            
            // مخفی کردن فیلد شیوه آشنایی برای کاربران غیر ادمین و غیر CRM
            const sourceContainer = document.getElementById('prof-info-source-container');
            if (sourceContainer) {
                if (this.isAdminOrCrmSpecialist()) {
                    sourceContainer.classList.remove('hidden');
                } else {
                    sourceContainer.classList.add('hidden');
                }
            }
            document.getElementById('prof-info-background').textContent = person.background_info || '-';
            const editBtn = document.getElementById('lead-edit-btn');
            if (editBtn) {
                // RBAC-driven edit visibility; کارشناس فروش remains view-only via capability config.
                const canEditByPermission = this.canActionClient('leads.edit');
                const canEdit = canEditByPermission && (this.isAdminOrCrmSpecialist() || this.isLeadOwner(person));
                editBtn.classList.toggle('hidden', !canEdit);
            }
            
            // Render deals
        const dealsDiv = document.getElementById('prof-deals');
        dealsDiv.innerHTML = '';
        const currentDeals = Array.isArray(res.deals) ? res.deals : [];
        const legacyDeals = Array.isArray(res.legacy_deals) ? res.legacy_deals : [];

        const renderDealCard = (d, isLegacy = false) => {
            const price = d.estimated_price ? parseInt(d.estimated_price).toLocaleString() : '0';
            const statusColor = d.status === 'Won' ? 'border-green-500' : (d.status === 'Lost' ? 'border-red-500' : 'border-blue-500');
            const statusText = d.status === 'Won' ? '\u0645\u0648\u0641\u0642' : (d.status === 'Lost' ? '\u0646\u0627\u0645\u0648\u0641\u0642' : '\u062f\u0631 \u062c\u0631\u06cc\u0627\u0646');
            const dealId = d.didar_deal_id || d.id;
            const legacyBadge = isLegacy
                ? '<span class="inline-block ml-2 px-2 py-0.5 rounded bg-purple-500/20 text-purple-300 text-[10px]">\u0645\u0639\u0627\u0645\u0644\u0647 \u06af\u0630\u0634\u062a\u0647 \u062f\u06cc\u062f\u0627\u0631</span>'
                : '';

            const deleteBtn = this.isAdmin() ? `
                    <button onclick="event.stopPropagation(); app.deleteDeal('${dealId}')"
                            class="text-red-500 hover:text-red-700 text-xs px-2 py-1 rounded transition"
                            title="\u062d\u0630\u0641 \u0645\u0639\u0627\u0645\u0644\u0647">
                        <i class="fa-solid fa-trash"></i>
                    </button>` : '';

            return `
                    <div class="bg-gray-700/30 p-3 rounded border-l-4 ${statusColor} mb-2 hover:bg-gray-700/50 transition relative">
                        ${deleteBtn ? `<div class="absolute top-2 left-2 z-10">${deleteBtn}</div>` : ''}
                        <div class="cursor-pointer" onclick="app.openDealModal('${dealId}')">
                            <div class="flex justify-between items-start">
                                <div class="flex-1">
                                    <span class="text-sm text-white font-bold">${d.title || '-'}</span>
                                    ${legacyBadge}
                                    <div class="text-[10px] text-gray-500 mt-1">
                                        <span class="inline-block px-2 py-0.5 rounded ${d.status === 'Won' ? 'bg-green-500/20 text-green-400' : (d.status === 'Lost' ? 'bg-red-500/20 text-red-400' : 'bg-blue-500/20 text-blue-400')}">${statusText}</span>
                                        ${d.register_time ? ' | ' + toPersianDate(d.register_time) : ''}
                                    </div>
                                </div>
                                <span class="text-xs text-yellow-500 font-mono font-bold ml-2">${price} T</span>
                            </div>
                        </div>
                    </div>`;
        };

        if (currentDeals.length) {
            currentDeals.forEach(d => {
                dealsDiv.innerHTML += renderDealCard(d, false);
            });
        }

        if (legacyDeals.length) {
            if (currentDeals.length) {
                dealsDiv.innerHTML += '<div class="text-xs text-gray-400 mt-4 mb-2">\u0645\u0639\u0627\u0645\u0644\u0627\u062a \u06af\u0630\u0634\u062a\u0647 \u062f\u06cc\u062f\u0627\u0631</div>';
            }
            legacyDeals.forEach(d => {
                dealsDiv.innerHTML += renderDealCard(d, true);
            });
        }

        if (!currentDeals.length && !legacyDeals.length) {
            dealsDiv.innerHTML = '<p class="text-sm text-gray-500">\u0647\u06cc\u0686 \u0645\u0639\u0627\u0645\u0644\u0647\u200c\u0627\u06cc \u0628\u0631\u0627\u06cc \u0646\u0645\u0627\u06cc\u0634 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f.</p>';
        }

        // Render activities
        const actDiv = document.getElementById('prof-activities');
        actDiv.innerHTML = '';
        if(res.activities && res.activities.length) {
            res.activities.forEach(a => {
                // Extract virtual stage and purchase info from result_note
                let displayNote = a.result_note || a.note || 'بدون توضیحات';
                let stageBadge = '';
                let purchaseBadge = '';
                
                // Check for virtual stage in stage field or result_note
                if (a.stage) {
                    const stageTitles = {
                        'contact_no_answer': 'تماس عدم پاسخ',
                        'contact_followup': 'تماس پیگیری',
                        'deal_registered': 'ثبت معامله و لینک پرداخت',
                        'payment': 'پرداخت',
                        'payment_followup': 'پیگیری مانده حساب',
                        'course_delivered': 'برگزاری دوره',
                        'support': 'پشتیبانی'
                    };
                    const stageTitle = stageTitles[a.stage];
                    if (stageTitle) {
                        stageBadge = `<span class="inline-flex items-center bg-purple-600 text-white text-[11px] px-2 py-0.5 rounded">📍 ${stageTitle}</span>`;
                    }
                }
                
                // Check for purchase info in result_note
                if (displayNote.includes('🟢 این لید قبلاً خرید دیگری')) {
                    purchaseBadge = `<span class="inline-flex items-center bg-green-600 text-white text-[11px] px-2 py-0.5 rounded">🟢 خرید قبلی با شماره دیگر: بله</span>`;
                    // Remove from display note
                    displayNote = displayNote.replace(/🟢 این لید قبلاً خرید دیگری با شماره دیگری داشته است\./g, '').trim();
                    displayNote = displayNote.replace(/⚪ این لید خرید قبلی با شماره دیگر نداشته است\./g, '').trim();
                    displayNote = displayNote.replace(/⚪ خرید قبلی با شماره دیگر: نامشخص/g, '').trim();
                    displayNote = displayNote.replace(/📍 مرحله کاریز:.*/g, '').trim();
                } else if (displayNote.includes('⚪ این لید خرید قبلی') || displayNote.includes('⚪ خرید قبلی با شماره دیگر')) {
                    if (displayNote.includes('نامشخص')) {
                        purchaseBadge = `<span class="inline-flex items-center bg-yellow-600 text-white text-[11px] px-2 py-0.5 rounded">⚪ خرید قبلی با شماره دیگر: نامشخص</span>`;
                    } else {
                        purchaseBadge = `<span class="inline-flex items-center bg-gray-600 text-white text-[11px] px-2 py-0.5 rounded">⚪ خرید قبلی با شماره دیگر: خیر</span>`;
                    }
                    // Remove from display note
                    displayNote = displayNote.replace(/⚪ این لید خرید قبلی با شماره دیگر نداشته است\./g, '').trim();
                    displayNote = displayNote.replace(/⚪ خرید قبلی با شماره دیگر: نامشخص/g, '').trim();
                    displayNote = displayNote.replace(/📍 مرحله کاریز:.*/g, '').trim();
                }
                
                const activityId = a.didar_activity_id || a.id;
                const deleteBtn = this.isAdmin() ? `<button type="button" onclick="event.stopPropagation(); app.deleteActivity('${activityId}')" class="rounded-lg border bg-red-500/15 hover:bg-red-500/30 text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="border-color:rgba(239,68,68,0.72);padding:6px 14px;min-height:34px;color:#fecaca" title="حذف فعالیت"><i class="fa-solid fa-trash"></i> حذف</button>` : '';

                // Check if activity is scheduled - check multiple conditions
                const hasDueDate = a.due_date && a.due_date.trim() !== '';
                const isScheduledFlag = a.is_scheduled === 1 || a.is_scheduled === true || a.is_scheduled === '1';
                const isScheduled = isScheduledFlag || (!a.is_done && hasDueDate);
                const isEdited = a.last_update_time && a.last_update_time !== a.register_date && a.last_update_time !== a.created_at;
                
                // Parse and format dates
                let dueDate = '';
                if (hasDueDate) {
                    try {
                        dueDate = toPersianDateTime(a.due_date);
                    } catch (e) {
                        console.warn('Error parsing due_date:', a.due_date, e);
                        dueDate = a.due_date; // Fallback to raw value
                    }
                }
                
                const registerDate = a.register_date ? toPersianDateTime(a.register_date) : '';
                const durationMinutes = Math.max(0, parseInt(a.duration ?? a.activity_duration ?? 0, 10) || 0);
                
                // State tokens — same visual language as the payment cards (accent bar + pill + dark cells)
                const st = (a.is_done === 1 || a.is_done === true || a.is_done === '1')
                    ? { accent: 'bg-green-500/70', pillBg: 'bg-green-500/15', pillBorder: 'border-green-500/40', color: '#86efac', icon: 'circle-check', status: 'انجام شده' }
                    : isScheduled
                    ? { accent: 'bg-amber-500/70', pillBg: 'bg-amber-500/15', pillBorder: 'border-amber-500/40', color: '#fde68a', icon: 'clock', status: 'برنامه‌ریزی شده' }
                    : { accent: 'bg-blue-500/70', pillBg: 'bg-blue-500/15', pillBorder: 'border-blue-500/40', color: '#bfdbfe', icon: 'calendar-check', status: 'ثبت شده' };

                // Labeled detail cells — identical shell to the payment card cells
                const cell = (label, value, full = false) => `
                    <div class="min-w-0 p-3 rounded-lg ${full ? 'sm:col-span-2 lg:col-span-3' : ''}" style="background:rgba(15,23,42,0.74);border:1px solid rgba(148,163,184,0.14)">
                        <div class="text-[11px] font-bold" style="color:#9ca3af;margin-bottom:8px">${label}</div>
                        <div class="text-sm font-semibold break-words leading-6" style="color:#f3f4f6;text-align:right">${value}</div>
                    </div>`;

                const callTypeLabels = { voip: 'Voip', phone: 'مکالمه تلفنی', sms: 'پیامک' };
                const callTypeLabel = callTypeLabels[a.call_type] || '';
                const activityOwnerName = a.activity_owner_name_snapshot
                    || a.owner_name
                    || this.resolveUserName?.(a.activity_owner_didar_id || a.owner_didar_id)
                    || a.activity_owner_didar_id
                    || a.owner_didar_id
                    || 'نامشخص';
                // "YYYY/MM/DD HH:mm" → "YYYY/MM/DD - HH:mm" (one space, between date and time)
                const withDash = (s) => (s || '').replace(' ', ' - ');

                const cells = [];
                cells.push(cell('کارشناس ثبت‌کننده فعالیت', this.escapeHtml(activityOwnerName)));
                if (registerDate) cells.push(cell('تاریخ ثبت', withDash(registerDate)));
                if (dueDate) cells.push(cell('تاریخ/ساعت برنامه‌ریزی', withDash(dueDate)));
                else if (isScheduled) cells.push(cell('تاریخ/ساعت برنامه‌ریزی', 'تاریخ مشخص نشده'));
                if (callTypeLabel) cells.push(cell('نوع مکالمه', callTypeLabel));
                cells.push(cell('مدت فعالیت', durationMinutes > 0 ? `${this.formatNumber(durationMinutes)} دقیقه` : 'ثبت نشده'));
                cells.push(cell('توضیحات', displayNote ? displayNote : '<span style="color:#94a3b8" class="italic">بدون توضیحات</span>', true));

                const tagsRow = (stageBadge || purchaseBadge)
                    ? `<div class="flex flex-wrap gap-2 mb-3">${stageBadge || ''}${purchaseBadge || ''}</div>`
                    : '';

                actDiv.innerHTML += `
                <div class="relative overflow-hidden rounded-lg p-3 shadow-lg transition" style="background:#172334;border:1px solid rgba(148,163,184,0.28);box-shadow:0 14px 32px rgba(2,6,23,0.22)">
                    <div class="absolute top-0 right-0 h-full w-1.5 ${st.accent}"></div>
                    <div class="flex items-center justify-between gap-3 flex-wrap">
                        <div class="flex items-center gap-2 flex-wrap">
                            <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full ${st.pillBg} border ${st.pillBorder} text-xs font-bold whitespace-nowrap" style="min-height:34px;color:${st.color}">
                                <i class="fa-solid fa-${st.icon}"></i> ${a.activity_type_title || 'فعالیت'}
                            </span>
                            <span class="inline-flex items-center px-2.5 py-1 rounded-full text-[11px] font-bold whitespace-nowrap" style="min-height:30px;background:rgba(15,23,42,0.6);border:1px solid rgba(148,163,184,0.2);color:${st.color}">${st.status}</span>
                            ${isEdited ? `<span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-bold whitespace-nowrap" style="min-height:30px;background:rgba(168,85,247,0.15);border:1px solid rgba(168,85,247,0.4);color:#e9d5ff" title="ویرایش شده"><i class="fa-solid fa-pencil"></i> ویرایش‌شده</span>` : ''}
                        </div>
                        ${deleteBtn ? `<div class="flex gap-2 shrink-0 flex-wrap">${deleteBtn}</div>` : ''}
                    </div>
                    <div class="access-divider mt-3 mb-4" style="border-bottom:1px solid rgba(148,163,184,0.18)"></div>
                    ${tagsRow}
                    <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">${cells.join('')}</div>
                </div>`;
            });
            } else {
                actDiv.innerHTML = '<p class="text-sm text-gray-500">هیچ فعالیتی ثبت نشده است.</p>';
            }
            
            // Load activity types for modal
            this.loadActivityTypesForModal();
            
            // Render satisfactions if available
            if (res.satisfactions) {
                this.renderSatisfactions(res.satisfactions);
            }
            
            this.switchProfileTab('info');
            this.openModal('modalProfile', { source: 'user', entityId: String(contactId || '') });
            
            // بررسی نمایش فیلد خرید قبلی بعد از باز شدن مودال
            setTimeout(() => {
                this.handleActivityFormVisibility();
            }, 200);
        }
    },
    
    renderSatisfactions(satisfactions) {
        const container = document.getElementById('prof-satisfactions');
        if (!container) return;
        
        if (satisfactions && satisfactions.length > 0) {
            container.innerHTML = satisfactions.map(sat => {
                const score = sat.satisfaction_score || 0;
                const stars = '⭐'.repeat(score);
                // Shared accent/pill by score (mobile-only CSS; inert on desktop).
                const satClass = score >= 4 ? 's--success' : (score === 3 ? 's--pending' : (score >= 1 ? 's--danger' : 's--neutral'));
                const date = sat.register_date ? toPersianDateTime(sat.register_date) : '';
                const registeredBy = sat.registered_by_name || 'نامشخص';
                const note = sat.satisfaction_note ? `<p class="text-sm text-gray-300 mt-2">${this.escapeHtml(sat.satisfaction_note)}</p>` : '';

                return `
                    <div class="glass p-4 rounded-lg accent-card ${satClass}">
                        <div class="flex justify-between items-start mb-2">
                            <div>
                                <div class="text-lg mb-1">${stars}<span class="s-pill ${satClass}" style="margin-inline-start:6px">${score}/5</span></div>
                                <div class="text-xs text-gray-400">ثبت شده توسط: ${this.escapeHtml(registeredBy)}</div>
                            </div>
                            <div class="text-xs text-gray-500">${date}</div>
                        </div>
                        ${note}
                    </div>
                `;
            }).join('');
        } else {
            container.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">هیچ رضایتی ثبت نشده است.</p>';
        }
    },
    
    openSatisfactionModal() {
        const contactId = window.currentContactId;
        if (!contactId) {
            Swal.fire('خطا', 'لید انتخاب نشده است', 'error');
            return;
        }
        document.getElementById('satisfaction-contact-id').value = contactId;
        document.querySelectorAll('input[name="satisfaction_score"]').forEach(r => r.checked = false);
        document.getElementById('satisfaction-score-text').textContent = 'امتیاز را انتخاب کنید';
        document.querySelector('textarea[name="satisfaction_note"]').value = '';
        this.openModal('modalSatisfaction');
    },
    
    selectSatisfactionScore(score) {
        document.getElementById(`score-${score}`).checked = true;
        const texts = {
            1: 'خیلی ضعیف',
            2: 'ضعیف',
            3: 'متوسط',
            4: 'خوب',
            5: 'عالی'
        };
        document.getElementById('satisfaction-score-text').textContent = texts[score] || '';
    },
    
    async saveSatisfaction(e) {
        e.preventDefault();
        const form = e.target;
        const data = Object.fromEntries(new FormData(form));
        
        if (!data.satisfaction_score) {
            Swal.fire('خطا', 'لطفاً امتیاز رضایت را انتخاب کنید', 'error');
            return;
        }
        
        Swal.fire({
            title: 'در حال ثبت...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('save_satisfaction', data);
            if (res.status === 'success') {
                Swal.fire({toast: true, position: 'bottom-end', icon: 'success', title: 'رضایت ثبت شد', showConfirmButton: false, timer: 2000});
                this.closeModal('modalSatisfaction');
                form.reset();
                
                // Reload person profile to show new satisfaction
                if (window.currentContactId) {
                    await this.openPersonProfile(window.currentContactId);
                    this.switchProfileTab('satisfactions');
                }
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
            }
        } catch (error) {
            console.error('Error in saveSatisfaction:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در ثبت رضایت: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    openDidarSearchModal() {
        if (!this.isDidarUiEnabled()) {
            this.showDidarUiDisabledMessage();
            return;
        }
        document.getElementById('didar-search-phone').value = '';
        document.getElementById('didar-search-keywords').value = '';
        document.getElementById('didar-search-results').innerHTML = '<p class="text-center text-gray-500 py-8">برای جستجو، شماره تلفن یا کلمات کلیدی را وارد کنید</p>';
        this.openModal('modalDidarSearch');
    },

    async openLocalMobileLookup() {
        const { value } = await Swal.fire({
            title: 'استعلام سریع شماره موبایل',
            input: 'text',
            inputPlaceholder: '۱۰ رقم موبایل بدون صفر اول (مثل: 9123456789)',
            inputAttributes: {
                maxlength: 10,
                oninput: function() {
                    // فقط اجازه تایپ اعداد بده و اعداد فارسی/عربی را به انگلیسی تبدیل کن
                    this.value = this.value.replace(/[^\d]/g, '').replace(/[۰-۹]/g, function(d) {
                        return String.fromCharCode(d.charCodeAt(0) - 1728);
                    }).replace(/[٠-٩]/g, function(d) {
                        return String.fromCharCode(d.charCodeAt(0) - 1584);
                    });
                }
            },
            showCancelButton: true,
            confirmButtonText: 'جستجو',
            cancelButtonText: 'لغو',
            inputValidator: (v) => {
                const raw = (v || '').trim();
                if (!raw) return 'شماره موبایل را وارد کنید';
                const digits = raw.replace(/[^\d]/g, '');
                if (digits.length !== 10) return 'شماره موبایل باید دقیقاً 10 رقم باشد (بدون صفر اول)';
                if (!digits.startsWith('9')) return 'شماره موبایل باید با رقم 9 شروع شود';
                return null;
            }
        });
        if (!value) return;

        const digits = toEnglishDigits(String(value).trim()).replace(/[^\d]/g, '');

        Swal.fire({
            title: 'در حال بررسی...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('lookup_mobile', { mobile_phone: digits });
            Swal.close();

            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
                return;
            }

            const matches = Array.isArray(res.matches) ? res.matches : [];
            if (matches.length === 0) {
                Swal.fire('یافت نشد', 'این شماره در دیتابیس محلی موجود نیست.', 'info');
                return;
            }

            const html = matches.map(m => {
                const displayName = m.display_name || 'بدون نام';
                const ownerName = m.owner_name || 'نامشخص';
                const previousOwnerName = m.previous_owner_name || '-';
                const contactId = m.didar_contact_id || '-';

                return `
                    <div class="text-right p-3 bg-gray-800 rounded mb-2">
                        <div class="text-sm text-white font-bold">${this.escapeHtml(displayName)}</div>
                        <div class="text-xs text-gray-300 mt-1">شناسه: ${this.escapeHtml(String(contactId))}</div>
                        <div class="text-xs text-gray-300 mt-1">مسئول فعلی: ${this.escapeHtml(ownerName)}</div>
                        <div class="text-xs text-gray-400 mt-1">مسئول قبلی: ${this.escapeHtml(previousOwnerName)}</div>
                    </div>
                `;
            }).join('');

            Swal.fire({
                title: `یافت شد (${matches.length})`,
                html,
                confirmButtonText: 'بستن'
            });
        } catch (e) {
            console.error('Local mobile lookup failed:', e);
            Swal.close();
            Swal.fire('خطا', 'خطا در ارتباط با سرور', 'error');
        }
    },

    openBulkPhoneLookupModal() {
        this.bulkPhoneLookupState = { file: null, results: null };
        document.getElementById('bulk-lookup-step-1').classList.remove('hidden');
        document.getElementById('bulk-lookup-step-2').classList.add('hidden');
        document.getElementById('bulk-lookup-btn-run').classList.add('hidden');
        document.getElementById('bulk-lookup-btn-reset').classList.add('hidden');
        document.getElementById('bulk-lookup-file-selected').classList.add('hidden');
        document.getElementById('bulk-lookup-file-input').value = '';
        this.openModal('bulkPhoneLookupModal');
    },

    handleBulkLookupFileSelect(event) {
        const file = event.target.files?.[0];
        if (!file) return;

        const ext = (file.name.split('.').pop() || '').toLowerCase();
        if (!['xlsx', 'xls', 'csv'].includes(ext)) {
            Swal.fire('خطا', 'فقط فایل‌های Excel یا CSV قابل قبول هستند', 'error');
            return;
        }
        if (file.size > 5 * 1024 * 1024) {
            Swal.fire('خطا', 'حجم فایل نباید بیشتر از 5 مگابایت باشد', 'error');
            return;
        }

        this.bulkPhoneLookupState = this.bulkPhoneLookupState || {};
        this.bulkPhoneLookupState.file = file;
        document.getElementById('bulk-lookup-file-name').textContent = file.name;
        document.getElementById('bulk-lookup-file-selected').classList.remove('hidden');
        document.getElementById('bulk-lookup-btn-run').classList.remove('hidden');
    },

    async runBulkPhoneLookup() {
        const state = this.bulkPhoneLookupState || {};
        if (!state.file) {
            Swal.fire('خطا', 'ابتدا فایل را انتخاب کنید', 'error');
            return;
        }

        Swal.fire({ title: 'در حال استعلام...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });

        try {
            const formData = new FormData();
            formData.append('file', state.file);

            const response = await fetch('?api=bulk_phone_lookup', {
                method: 'POST',
                body: formData,
                credentials: 'same-origin'
            });
            const res = await response.json();
            Swal.close();

            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
                return;
            }

            this.bulkPhoneLookupState.results = res.results || [];
            this._renderBulkLookupResults(res);
        } catch (e) {
            console.error('Bulk phone lookup failed:', e);
            Swal.close();
            Swal.fire('خطا', 'خطا در ارتباط با سرور', 'error');
        }
    },

    _renderBulkLookupResults(res) {
        document.getElementById('bulk-lookup-found-count').textContent = res.found_count ?? 0;
        document.getElementById('bulk-lookup-notfound-count').textContent = res.not_found_count ?? 0;
        document.getElementById('bulk-lookup-error-count').textContent = res.error_count ?? 0;

        const tbody = document.getElementById('bulk-lookup-results-table');
        tbody.innerHTML = '';

        const results = Array.isArray(res.results) ? res.results : [];
        results.forEach(r => {
            if (r.error) {
                const tr = document.createElement('tr');
                tr.className = 'bg-red-900/20';
                tr.innerHTML = `
                    <td class="border border-gray-700 p-2">${r.row}</td>
                    <td class="border border-gray-700 p-2 font-mono">${this.escapeHtml(r.phone)}</td>
                    <td class="border border-gray-700 p-2 text-red-400 text-center" colspan="4">${this.escapeHtml(r.error)}</td>
                `;
                tbody.appendChild(tr);
                return;
            }

            if (!r.found) {
                const tr = document.createElement('tr');
                tr.className = 'bg-gray-800';
                tr.innerHTML = `
                    <td class="border border-gray-700 p-2">${r.row}</td>
                    <td class="border border-gray-700 p-2 font-mono">${this.escapeHtml(r.phone)}</td>
                    <td class="border border-gray-700 p-2 text-gray-400 text-center" colspan="4">موجود نیست</td>
                `;
                tbody.appendChild(tr);
                return;
            }

            const matches = Array.isArray(r.matches) ? r.matches : [];
            matches.forEach((m, idx) => {
                const tr = document.createElement('tr');
                tr.className = 'bg-green-900/20';
                tr.innerHTML = `
                    <td class="border border-gray-700 p-2">${idx === 0 ? r.row : ''}</td>
                    <td class="border border-gray-700 p-2 font-mono">${idx === 0 ? this.escapeHtml(r.phone) : ''}</td>
                    <td class="border border-gray-700 p-2 text-center"><span class="text-green-400 font-bold">موجود</span></td>
                    <td class="border border-gray-700 p-2">${this.escapeHtml(m.display_name || '-')}</td>
                    <td class="border border-gray-700 p-2">${this.escapeHtml(m.owner_name || '-')}</td>
                    <td class="border border-gray-700 p-2 text-gray-400">${this.escapeHtml(m.previous_owner_name || '-')}</td>
                `;
                tbody.appendChild(tr);
            });
        });

        document.getElementById('bulk-lookup-step-1').classList.add('hidden');
        document.getElementById('bulk-lookup-step-2').classList.remove('hidden');
        document.getElementById('bulk-lookup-btn-run').classList.add('hidden');
        document.getElementById('bulk-lookup-btn-reset').classList.remove('hidden');
    },

    resetBulkPhoneLookup() {
        this.bulkPhoneLookupState = { file: null, results: null };
        document.getElementById('bulk-lookup-step-1').classList.remove('hidden');
        document.getElementById('bulk-lookup-step-2').classList.add('hidden');
        document.getElementById('bulk-lookup-btn-run').classList.add('hidden');
        document.getElementById('bulk-lookup-btn-reset').classList.add('hidden');
        document.getElementById('bulk-lookup-file-selected').classList.add('hidden');
        document.getElementById('bulk-lookup-file-input').value = '';
    },

    exportBulkLookupResults(mode) {
        const results = (this.bulkPhoneLookupState || {}).results;
        if (!results || results.length === 0) return;

        let rows = [];
        const header = ['ردیف', 'شماره', 'وضعیت', 'نام', 'مسئول فعلی', 'مسئول قبلی', 'شناسه CRM خارجی'];

        results.forEach(r => {
            if (r.error) {
                if (mode === 'all') {
                    rows.push([r.row, r.phone, 'نامعتبر', '', '', '', '']);
                }
                return;
            }

            if (!r.found) {
                if (mode === 'all' || mode === 'not_found') {
                    rows.push([r.row, r.phone, 'موجود نیست', '', '', '', '']);
                }
                return;
            }

            if (mode === 'all' || mode === 'found') {
                const matches = Array.isArray(r.matches) ? r.matches : [{}];
                matches.forEach(m => {
                    rows.push([
                        r.row,
                        r.phone,
                        'موجود',
                        m.display_name || '',
                        m.owner_name || '',
                        m.previous_owner_name || '',
                        m.didar_contact_id || ''
                    ]);
                });
            }
        });

        if (rows.length === 0) {
            Swal.fire('توجه', 'داده‌ای برای خروجی وجود ندارد', 'info');
            return;
        }

        const csvLines = [header, ...rows].map(row =>
            row.map(cell => {
                const val = String(cell ?? '');
                return val.includes(',') || val.includes('"') || val.includes('\n')
                    ? '"' + val.replace(/"/g, '""') + '"'
                    : val;
            }).join(',')
        );

        const bom = '﻿';
        const blob = new Blob([bom + csvLines.join('\n')], { type: 'text/csv;charset=utf-8;' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'bulk_phone_lookup_' + mode + '.csv';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    },

    async searchDidar(e) {
        if (!this.isDidarUiEnabled()) {
            if (e && typeof e.preventDefault === 'function') e.preventDefault();
            this.showDidarUiDisabledMessage();
            return;
        }
        e.preventDefault();
        const form = e.target;
        const phone = form.phone.value.trim();
        const keywords = form.keywords.value.trim();
        
        if (!phone && !keywords) {
            Swal.fire('خطا', 'لطفاً شماره تلفن یا کلمات کلیدی را وارد کنید', 'error');
            return;
        }
        
        Swal.fire({
            title: 'در حال جستجو...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('search_didar', { phone, keywords });
            Swal.close();
            
            if (res.status === 'success') {
                this.renderDidarSearchResults(res.results || []);
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
            }
        } catch (error) {
            console.error('Error in searchDidar:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در جستجو: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    renderDidarSearchResults(results) {
        const container = document.getElementById('didar-search-results');
        
        // Store results in state for later use
        this.state.didarSearchResults = results || [];
        
        if (!results || results.length === 0) {
            container.innerHTML = '<p class="text-center text-gray-500 py-8">نتیجه‌ای یافت نشد</p>';
            return;
        }
        
        container.innerHTML = results.map(person => {
            const name = person.DisplayName || (person.FirstName + ' ' + person.LastName) || 'بدون نام';
            const mobile = person.MobilePhone || '-';
            const email = person.Email || '-';
            const contactId = person.Id || '';
            
            return `
                <div class="glass p-4 rounded-lg hover:bg-gray-800/50 cursor-pointer" onclick="app.viewDidarPersonDetails('${contactId}')">
                    <div class="flex justify-between items-start">
                        <div class="flex-1">
                            <div class="font-bold text-white mb-1">${this.escapeHtml(name)}</div>
                            <div class="text-sm text-gray-400">موبایل: ${this.escapeHtml(mobile)}</div>
                            ${email !== '-' ? `<div class="text-sm text-gray-400">ایمیل: ${this.escapeHtml(email)}</div>` : ''}
                        </div>
                        <button class="btn-primary px-3 py-1 rounded text-xs">مشاهده جزئیات</button>
                    </div>
                </div>
            `;
        }).join('');
    },
    
    async viewDidarPersonDetails(contactId) {
        if (!this.isDidarUiEnabled()) {
            this.showDidarUiDisabledMessage();
            return;
        }
        // First, try to get person from search results
        let person = null;
        if (this.state.didarSearchResults && Array.isArray(this.state.didarSearchResults)) {
            person = this.state.didarSearchResults.find(p => p.Id === contactId);
        }
        
        // If found in search results, use it directly and fetch deals/activities
        if (person) {
            Swal.fire({
                title: 'در حال بارگذاری معاملات و فعالیت‌ها...',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            
            try {
                const res = await this.req('get_didar_person', { contact_id: contactId, person_from_search: 'true' });
                Swal.close();
                
                console.log('get_didar_person response:', res);
                console.log('Deals count:', res.deals?.length || 0);
                console.log('Activities count:', res.activities?.length || 0);
                
                if (res.status === 'success') {
                    // Use person from search results (more complete) but use deals/activities from API
                    console.log('Rendering with deals:', res.deals?.length || 0, 'activities:', res.activities?.length || 0);
                    this.renderDidarPersonDetails(person, res.deals || [], res.activities || []);
                    this.openModal('modalDidarPerson');
                    // No warning needed since person data is available from search results
                } else {
                    // If API fails, still show person data from search
                    this.renderDidarPersonDetails(person, [], []);
                    this.openModal('modalDidarPerson');
                    Swal.fire('هشدار', 'اطلاعات کامل دریافت نشد، اما اطلاعات پایه نمایش داده می‌شود', 'warning');
                }
            } catch (error) {
                console.error('Error in viewDidarPersonDetails:', error);
                Swal.close();
                // Still show person data from search even if API fails
                this.renderDidarPersonDetails(person, [], []);
                this.openModal('modalDidarPerson');
                Swal.fire('هشدار', 'اطلاعات کامل دریافت نشد، اما اطلاعات پایه نمایش داده می‌شود', 'warning');
            }
        } else {
            // If not in search results, try API (which will search for the person)
            Swal.fire({
                title: 'در حال بارگذاری...',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            
            try {
                const res = await this.req('get_didar_person', { contact_id: contactId });
                Swal.close();
                
                console.log('get_didar_person response (no search):', res);
                console.log('Deals count:', res.deals?.length || 0);
                console.log('Activities count:', res.activities?.length || 0);
                
                if (res.status === 'success' && res.person) {
                    console.log('Rendering with deals:', res.deals?.length || 0, 'activities:', res.activities?.length || 0);
                    this.renderDidarPersonDetails(res.person, res.deals || [], res.activities || []);
                    this.openModal('modalDidarPerson');
                } else if (res.status === 'partial') {
                    // Person not found but deals/activities available
                    console.log('Partial response - showing deals/activities anyway');
                    if (res.deals && res.deals.length > 0 || res.activities && res.activities.length > 0) {
                        this.renderDidarPersonDetails(null, res.deals || [], res.activities || []);
                        this.openModal('modalDidarPerson');
                    } else {
                        Swal.fire('خطا', 'اطلاعات شخص یافت نشد. لطفاً دوباره جستجو کنید.', 'error');
                    }
                } else {
                    Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
                }
            } catch (error) {
                console.error('Error in viewDidarPersonDetails:', error);
                Swal.close();
                Swal.fire('خطا', 'خطا در دریافت اطلاعات: ' + (error.message || 'خطای نامشخص'), 'error');
            }
        }
    },
    
    renderDidarPersonDetails(person, deals, activities) {
        console.log('renderDidarPersonDetails called with:', {
            person: person ? 'exists' : 'null',
            dealsCount: deals?.length || 0,
            activitiesCount: activities?.length || 0
        });
        
        const container = document.getElementById('didar-person-details');
        const nameEl = document.getElementById('didar-person-name');
        const customerCodeEl = document.getElementById('didar-person-customer-code');
        
        if (!container) {
            console.error('didar-person-details container not found!');
            return;
        }
        
        if (!person) {
            console.warn('Person is null, using placeholder');
        }
        
        const name = person ? (person.DisplayName || (person.FirstName + ' ' + person.LastName) || 'بدون نام') : 'اطلاعات شخص در دسترس نیست';
        if (nameEl) nameEl.textContent = name;
        if (customerCodeEl) customerCodeEl.textContent = String(person?.code || person?.Code || person?.CustomerCode || '-');
        
        // Store activities in state for filtering
        this.state.didarActivities = activities || [];
        this.state.didarActivitiesFiltered = activities || [];
        
        console.log('About to render HTML with', deals?.length || 0, 'deals and', activities?.length || 0, 'activities');
        
        container.innerHTML = `
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">نام</div>
                    <div class="text-white font-bold">${this.escapeHtml(name)}</div>
                </div>
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">موبایل</div>
                    <div class="text-white font-bold">${this.escapeHtml(person?.MobilePhone || '-')}</div>
                </div>
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">ایمیل</div>
                    <div class="text-white font-bold">${this.escapeHtml(person?.Email || '-')}</div>
                </div>
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">شهر</div>
                    <div class="text-white font-bold">${this.escapeHtml(person?.City || '-')}</div>
                </div>
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">شغل</div>
                    <div class="text-white font-bold">${this.escapeHtml(person?.JobTitle || '-')}</div>
                </div>
                <div class="glass p-4">
                    <div class="text-xs text-gray-400 mb-1">مسئول</div>
                    <div class="text-white font-bold">${this.escapeHtml(person?.Owner?.DisplayName || '-')}</div>
                </div>
            </div>
            
            <div class="mb-6">
                <h4 class="font-bold text-white mb-3">معاملات (${deals?.length || 0})</h4>
                <div class="space-y-2">
                    ${deals && deals.length > 0 ? deals.map(deal => {
                        const dealId = deal.Id || deal.id || deal.Code || '';
                        const status = deal.Status || 'Pending';
                        const isClosed = status === 'Won' || status === 'Lost';
                        const reactivateBtn = this.isAdmin() && isClosed ? `
                            <button onclick="event.stopPropagation(); app.reactivateDealFromProfile('${dealId}')" class="text-blue-500 hover:text-blue-700 text-xs px-2 py-1 rounded transition ml-2" title="فعال‌سازی مجدد">
                                <i class="fa-solid fa-undo"></i>
                            </button>` : '';
                        return `
                            <div class="glass p-3 rounded relative">
                                ${reactivateBtn ? `<div class="absolute top-2 left-2">${reactivateBtn}</div>` : ''}
                                <div class="font-bold text-white ${reactivateBtn ? 'mr-8' : ''}">${this.escapeHtml(deal.Title || 'بدون عنوان')}</div>
                                <div class="text-sm text-gray-400">کد: ${deal.Code || '-'} | وضعیت: ${status === 'Won' ? 'موفق' : (status === 'Lost' ? 'ناموفق' : 'جاری')}</div>
                            </div>
                        `;
                    }).join('') : '<p class="text-gray-500 text-sm">معامله‌ای ثبت نشده</p>'}
                </div>
            </div>
            
            <div>
                <div class="flex justify-between items-center mb-3">
                    <h4 class="font-bold text-white">تاریخچه فعالیت‌ها و یادداشت‌ها (${activities?.length || 0})</h4>
                    <div class="flex gap-2">
                        <input type="text" id="activity-search-input" placeholder="جستجو در فعالیت‌ها..." 
                            class="input text-sm w-48" onkeyup="app.filterActivities(this.value)">
                        <select id="activity-filter-type" class="input text-sm w-32" onchange="app.filterActivities()">
                            <option value="">همه انواع</option>
                        </select>
                        <select id="activity-filter-status" class="input text-sm w-32" onchange="app.filterActivities()">
                            <option value="">همه وضعیت‌ها</option>
                            <option value="done">انجام شده</option>
                            <option value="pending">در انتظار</option>
                        </select>
                    </div>
                </div>
                <div id="activities-list" class="space-y-3 max-h-96 overflow-y-auto">
                    ${this.renderActivitiesList(activities)}
                </div>
            </div>
        `;
        
        // Store activities in state for filtering
        this.state.didarActivities = activities || [];
        this.state.didarActivitiesFiltered = activities || [];
        
        // Load activity types for filter dropdown
        this.loadActivityTypesForDidarFilter();
    },
    
    async viewDidarDeals(contactId) {
        if (!this.isDidarUiEnabled()) {
            this.showDidarUiDisabledMessage();
            return;
        }
        if (!contactId) {
            Swal.fire('خطا', 'شناسه شخص مشخص نشده است', 'error');
            return;
        }
        
        console.log('viewDidarDeals called with contactId:', contactId, 'type:', typeof contactId);
        
        Swal.fire({
            title: 'در حال بارگذاری معاملات...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('get_legacy_deals', { contact_id: contactId });
            Swal.close();
            
            console.log('get_legacy_deals response:', res);
            
            if (res.status === 'success') {
                // Store deals in state for later use (to avoid API inconsistency)
                this.state.didarDeals = res.deals || [];
                this.renderDidarDeals(this.state.didarDeals);
                this.openModal('modalDidarDeals');
            } else {
                Swal.fire('خطا', res.message || 'خطا در دریافت معاملات', 'error');
            }
        } catch (error) {
            console.error('Error in viewDidarDeals:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در دریافت معاملات: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    renderDidarDeals(deals) {
        const container = document.getElementById('didar-deals-list');

        if (!container) {
            console.error('didar-deals-list container not found!');
            return;
        }

        if (!deals || deals.length === 0) {
            container.innerHTML = '<p class="text-center text-gray-500 py-8">معامله‌ای برای نمایش وجود ندارد</p>';
            return;
        }

        // Sort by register time (newest first)
        const sorted = [...deals].sort((a, b) => {
            const dateA = new Date(a.RegisterTime || a.register_time || a.register_date || 0);
            const dateB = new Date(b.RegisterTime || b.register_time || b.register_date || 0);
            return dateB - dateA;
        });

        container.innerHTML = sorted.map(deal => {
            const status = deal.Status || deal.status || 'Pending';
            const statusText = status === 'Won' ? 'موفق' : (status === 'Lost' ? 'ناموفق' : 'در جریان');
            const statusColor = status === 'Won' ? 'border-green-500 bg-green-500/10' :
                              (status === 'Lost' ? 'border-red-500 bg-red-500/10' : 'border-blue-500 bg-blue-500/10');
            const priceValue = deal.Price || deal.estimated_price || deal.final_price || 0;
            const price = priceValue ? parseInt(priceValue).toLocaleString() : '0';
            const registerDateValue = deal.RegisterTime || deal.register_time || deal.register_date;
            const registerDate = registerDateValue ? toPersianDateTime(registerDateValue) : '';

            const dealId = deal.Id || deal.didar_deal_id || deal.id || '';
            const title = deal.Title || deal.title || 'عنوان معامله';
            const code = deal.Code || deal.code || deal.didar_deal_id || '-';
            const description = deal.Description || deal.description || '';

            if (!dealId) {
                return `
                <div class="glass p-4 rounded-lg border-l-4 ${statusColor}">
                    <div class="flex justify-between items-start">
                        <div class="flex-1">
                            <div class="font-bold text-white mb-1">${this.escapeHtml(title)}</div>
                            <div class="text-sm text-gray-400">
                                <span>کد: ${this.escapeHtml(code)}</span>
                                <span class="mx-2">|</span>
                                <span class="px-2 py-0.5 rounded ${status === 'Won' ? 'bg-green-500/20 text-green-400' : (status === 'Lost' ? 'bg-red-500/20 text-red-400' : 'bg-blue-500/20 text-blue-400')}">${statusText}</span>
                                ${registerDate ? `<span class=\"mx-2\">|</span><span>${registerDate}</span>` : ''}
                            </div>
                            ${description ? `<div class=\"text-sm text-gray-300 mt-2\">${this.escapeHtml(description)}</div>` : ''}
                        </div>
                        <div class="text-left ml-4">
                            <div class="text-lg font-bold text-yellow-500">${price}</div>
                            <div class="text-xs text-gray-500">ارزش معامله</div>
                        </div>
                    </div>
                </div>
            `;
            }

            return `
                <div class="glass p-4 rounded-lg border-l-4 ${statusColor} cursor-pointer hover:bg-gray-800/50 transition" onclick="app.openDealModal('${this.escapeHtml(dealId)}')">
                    <div class="flex justify-between items-start">
                        <div class="flex-1">
                            <div class="font-bold text-white mb-1">${this.escapeHtml(title)}</div>
                            <div class="text-sm text-gray-400">
                                <span>کد: ${this.escapeHtml(code)}</span>
                                <span class="mx-2">|</span>
                                <span class="px-2 py-0.5 rounded ${status === 'Won' ? 'bg-green-500/20 text-green-400' : (status === 'Lost' ? 'bg-red-500/20 text-red-400' : 'bg-blue-500/20 text-blue-400')}">${statusText}</span>
                                ${registerDate ? `<span class=\"mx-2\">|</span><span>${registerDate}</span>` : ''}
                            </div>
                            ${description ? `<div class=\"text-sm text-gray-300 mt-2\">${this.escapeHtml(description)}</div>` : ''}
                        </div>
                        <div class="text-left ml-4">
                            <div class="text-lg font-bold text-yellow-500">${price}</div>
                            <div class="text-xs text-gray-500">ارزش معامله</div>
                        </div>
                    </div>
                </div>
            `;
        }).join('');
    },
    
    async viewDealDetails(dealId) {
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }
        
        // First, try to find deal in cached state (from deals list)
        let deal = null;
        if (this.state.didarDeals && this.state.didarDeals.length > 0) {
            deal = this.state.didarDeals.find(d => (d.Id || d.id) === dealId);
            if (deal) {
                console.log('Found deal in cache, using cached data:', {
                    Id: deal.Id,
                    Code: deal.Code,
                    Title: deal.Title
                });
            }
        }
        
        // If deal found in cache, use it directly and only fetch activities
        if (deal) {
            Swal.fire({
                title: 'در حال بارگذاری فعالیت‌ها...',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            
            try {
                // Only fetch activities for this deal
                const activitiesRes = await this.req('get_deal_activities', { deal_id: dealId });
                Swal.close();
                
                if (activitiesRes.status === 'success') {
                    this.renderDealDetails(deal, activitiesRes.activities || []);
                    this.openModal('modalDealDetails');
                } else {
                    // Even if activities fail, show deal details without activities
                    this.renderDealDetails(deal, []);
                    this.openModal('modalDealDetails');
                }
            } catch (error) {
                console.error('Error fetching activities:', error);
                Swal.close();
                // Show deal details even if activities fetch fails
                this.renderDealDetails(deal, []);
                this.openModal('modalDealDetails');
            }
        } else {
            // Deal not in cache, fetch full details from API
            Swal.fire({
                title: 'در حال بارگذاری جزئیات معامله...',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            
            try {
                const res = await this.req('get_deal_details', { deal_id: dealId });
                Swal.close();
                
                if (res.status === 'success') {
                    this.renderDealDetails(res.deal || {}, res.activities || []);
                    this.openModal('modalDealDetails');
                } else {
                    Swal.fire('خطا', res.message || 'خطا در دریافت جزئیات معامله', 'error');
                }
            } catch (error) {
                console.error('Error in viewDealDetails:', error);
                Swal.close();
                Swal.fire('خطا', 'خطا در دریافت جزئیات معامله: ' + (error.message || 'خطای نامشخص'), 'error');
            }
        }
    },
    
    renderDealDetails(deal, activities) {
        // Update title
        const titleEl = document.getElementById('deal-details-title');
        if (titleEl) {
            titleEl.textContent = `جزئیات معامله: ${this.escapeHtml(deal.Title || 'بدون عنوان')}`;
        }
        
        // Render deal info
        const infoContainer = document.getElementById('deal-details-content');
        if (!infoContainer) {
            console.error('deal-details-content container not found!');
            return;
        }
        
        const status = deal.Status || 'Pending';
        const statusText = status === 'Won' ? 'موفق' : (status === 'Lost' ? 'ناموفق' : 'جاری');
        const statusColor = status === 'Won' ? 'text-green-400' : (status === 'Lost' ? 'text-red-400' : 'text-blue-400');
        const price = deal.Price ? parseInt(deal.Price).toLocaleString() : '0';
        const finalPrice = deal.FinalAdjustmentPrice ? parseInt(deal.FinalAdjustmentPrice).toLocaleString() : price;
        const registerDate = deal.RegisterTime ? toPersianDateTime(deal.RegisterTime) : '';
        const wonDate = deal.ChangeToWonTime ? toPersianDateTime(deal.ChangeToWonTime) : '';
        const lostDate = deal.ChangeToLossTime ? toPersianDateTime(deal.ChangeToLossTime) : '';
        const lastUpdate = deal.LastUpdateTime ? toPersianDateTime(deal.LastUpdateTime) : '';
        
        const contact = deal.Contact || deal.Person || {};
        const owner = deal.Owner || {};
        const items = deal.Items || [];
        const customerCode = person?.customer_code || person?.CustomerCode || person?.code || person?.Code || contact?.customer_code || contact?.CustomerCode || deal.CustomerCode || '-';
        
        infoContainer.innerHTML = `
            <!-- Basic Info -->
            <div class="glass p-6 rounded-lg">
                <h4 class="font-bold text-white text-lg mb-4">اطلاعات پایه</h4>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                        <div class="text-xs text-gray-400 mb-1">عنوان</div>
                        <div class="text-white font-bold">${this.escapeHtml(deal.Title || 'بدون عنوان')}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">کد معامله</div>
                        <div class="text-white">${deal.Code || '-'}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">وضعیت</div>
                        <div class="${statusColor} font-bold">${statusText}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">مبلغ</div>
                        <div class="text-yellow-500 font-bold">${price} تومان</div>
                    </div>
                    ${finalPrice !== price ? `
                    <div>
                        <div class="text-xs text-gray-400 mb-1">مبلغ نهایی (پس از تعدیل)</div>
                        <div class="text-yellow-500 font-bold">${finalPrice} تومان</div>
                    </div>
                    ` : ''}
                    <div>
                        <div class="text-xs text-gray-400 mb-1">احتمال موفقیت</div>
                        <div class="text-white">${deal.Probability || 0}%</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">تاریخ ثبت</div>
                        <div class="text-white">${registerDate || '-'}</div>
                    </div>
                    ${wonDate ? `
                    <div>
                        <div class="text-xs text-gray-400 mb-1">تاریخ موفقیت</div>
                        <div class="text-green-400">${wonDate}</div>
                    </div>
                    ` : ''}
                    ${lostDate ? `
                    <div>
                        <div class="text-xs text-gray-400 mb-1">تاریخ ناموفق</div>
                        <div class="text-red-400">${lostDate}</div>
                    </div>
                    ` : ''}
                    ${lastUpdate ? `
                    <div>
                        <div class="text-xs text-gray-400 mb-1">آخرین به‌روزرسانی</div>
                        <div class="text-white">${lastUpdate}</div>
                    </div>
                    ` : ''}
                </div>
                ${deal.Description ? `
                <div class="mt-4">
                    <div class="text-xs text-gray-400 mb-1">توضیحات</div>
                    <div class="text-white text-sm whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(deal.Description))}</div>
                </div>
                ` : ''}
            </div>
            
            <!-- Contact Info -->
            <div class="glass p-6 rounded-lg">
                <h4 class="font-bold text-white text-lg mb-4">اطلاعات تماس</h4>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                        <div class="text-xs text-gray-400 mb-1">نام</div>
                        <div class="text-white">${this.escapeHtml(contact.DisplayName || '-')}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">کد تماس</div>
                        <div class="text-white">${contact.Code || '-'}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">کد مشتری</div>
                        <div class="text-white font-bold">${this.escapeHtml(customerCode)}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">موبایل</div>
                        <div class="text-white">${contact.MobilePhone || '-'}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">ایمیل</div>
                        <div class="text-white">${contact.Email || '-'}</div>
                    </div>
                </div>
            </div>
            
            <!-- Owner Info -->
            <div class="glass p-6 rounded-lg">
                <h4 class="font-bold text-white text-lg mb-4">مسئول معامله</h4>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div>
                        <div class="text-xs text-gray-400 mb-1">نام</div>
                        <div class="text-white">${this.escapeHtml(owner.DisplayName || '-')}</div>
                    </div>
                    <div>
                        <div class="text-xs text-gray-400 mb-1">ایمیل</div>
                        <div class="text-white">${owner.Email || '-'}</div>
                    </div>
                </div>
            </div>
            
            <!-- Deal Items -->
            ${items.length > 0 ? `
            <div class="glass p-6 rounded-lg">
                <h4 class="font-bold text-white text-lg mb-4">آیتم‌های معامله</h4>
                <div class="space-y-3">
                    ${items.map(item => `
                        <div class="bg-gray-800/50 p-4 rounded-lg">
                            <div class="flex justify-between items-start">
                                <div class="flex-1">
                                    <div class="font-bold text-white mb-1">${this.escapeHtml(item.Title || item.TitleForInvoice || 'بدون عنوان')}</div>
                                    <div class="text-sm text-gray-400">
                                        <span>تعداد: ${item.Quantity || 1}</span>
                                        ${item.Unit ? `<span class="mx-2">|</span><span>واحد: ${item.Unit}</span>` : ''}
                                    </div>
                                    ${item.Description ? `<div class="text-sm text-gray-300 mt-2">${this.escapeHtml(this.stripHtml(item.Description))}</div>` : ''}
                                </div>
                                <div class="text-left ml-4">
                                    <div class="text-yellow-500 font-bold">${item.PriceAfterDiscount ? parseInt(item.PriceAfterDiscount).toLocaleString() : (item.Price ? parseInt(item.Price).toLocaleString() : '0')} تومان</div>
                                    ${item.Discount && item.Discount > 0 ? `
                                    <div class="text-xs text-gray-400 line-through">${parseInt(item.Price || 0).toLocaleString()} تومان</div>
                                    <div class="text-xs text-green-400">تخفیف: ${item.DiscountPercent ? item.DiscountPercent + '%' : parseInt(item.Discount).toLocaleString() + ' تومان'}</div>
                                    ` : ''}
                                </div>
                            </div>
                        </div>
                    `).join('')}
                </div>
            </div>
            ` : ''}
        `;
        
        // Render activities
        const activitiesContainer = document.getElementById('deal-activities-content');
        if (activitiesContainer) {
            if (activities && activities.length > 0) {
                this.renderDidarActivities(activities, activitiesContainer);
            } else {
                activitiesContainer.innerHTML = '<p class="text-center text-gray-500 py-8">فعالیتی برای این معامله ثبت نشده است</p>';
            }
        }
    },
    
    showDealDetailsTab(tab) {
        // Hide all tabs
        document.querySelectorAll('.deal-details-tab').forEach(el => el.classList.add('hidden'));
        
        // Remove active class from all tab buttons
        document.querySelectorAll('[id^="deal-tab-"]').forEach(el => {
            el.classList.remove('text-white', 'border-blue-500');
            el.classList.add('text-gray-400', 'border-transparent');
        });
        
        // Show selected tab
        const tabContent = document.getElementById(`deal-details-${tab}`);
        const tabButton = document.getElementById(`deal-tab-${tab}`);
        
        if (tabContent) {
            tabContent.classList.remove('hidden');
        }
        
        if (tabButton) {
            tabButton.classList.remove('text-gray-400', 'border-transparent');
            tabButton.classList.add('text-white', 'border-blue-500');
        }
    },
    
    async viewDidarActivities(contactId) {
        if (!this.isDidarUiEnabled()) {
            this.showDidarUiDisabledMessage();
            return;
        }
        if (!contactId) {
            Swal.fire('خطا', 'شناسه شخص مشخص نشده است', 'error');
            return;
        }
        
        console.log('viewDidarActivities called with contactId:', contactId, 'type:', typeof contactId);
        
        Swal.fire({
            title: 'در حال بارگذاری فعالیت‌ها...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('get_lead_didar_data', { contact_id: contactId });
            Swal.close();
            
            console.log('get_lead_didar_data response:', res);
            
            if (res.status === 'success') {
                this.renderDidarActivities(res.activities || []);
                this.openModal('modalDidarActivities');
            } else {
                Swal.fire('خطا', res.message || 'خطا در دریافت فعالیت‌ها', 'error');
            }
        } catch (error) {
            console.error('Error in viewDidarActivities:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در دریافت فعالیت‌ها: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    renderDidarActivities(activities, container = null) {
        // If container not provided, use default
        if (!container) {
            container = document.getElementById('didar-activities-list');
        }
        
        if (!container) {
            console.error('Activities container not found!');
            return;
        }
        
        if (!activities || activities.length === 0) {
            container.innerHTML = '<p class="text-center text-gray-500 py-8">فعالیتی یافت نشد</p>';
            return;
        }
        
        const resolveUserName = (didarId) => {
            const users = this.state.didarUsers || [];
            if (!didarId || !users.length) return '';
            const user = users.find(u =>
                u.owner_id === didarId ||
                u.didar_user_id === didarId ||
                u.id === didarId
            );
            return user?.display_name || '';
        };

        // Sort by RegisterDate (newest first)
        const sorted = [...activities].sort((a, b) => {
            const dateA = a.RegisterDate || a.register_date ? new Date(a.RegisterDate || a.register_date) : new Date(0);
            const dateB = b.RegisterDate || b.register_date ? new Date(b.RegisterDate || b.register_date) : new Date(0);
            return dateB - dateA;
        });

        container.innerHTML = sorted.map(act => {
            const isDone = act.IsDone === true || act.IsDone === 1 || act.is_done === 1 || act.is_done === true;
            const isNote = (act.ActivityTypeId || act.activity_type_id) === '00000000-0000-0000-0000-000000000000';
            const activityType = act.ActivityType?.Title || act.ActivityTypeTitle || act.activity_type_title || (isNote ? 'یادداشت' : 'فعالیت');

            let raw = act.raw_json;
            if (typeof raw === 'string') {
                try {
                    raw = JSON.parse(raw);
                } catch (_) {
                    raw = null;
                }
            }

            const creator = act.Creator?.DisplayName
                || act.CreatorName
                || act.activity_owner_name_snapshot
                || act.creator_name
                || raw?.creator_name
                || resolveUserName(act.creator_didar_id)
                || resolveUserName(act.owner_didar_id)
                || 'نامشخص';

            const owner = act.Owner?.DisplayName
                || act.OwnerName
                || act.activity_owner_name_snapshot
                || act.owner_name
                || raw?.owner_name
                || resolveUserName(act.owner_didar_id)
                || 'نامشخص';

            const note = act.Note || act.note || '';
            const resultNote = act.ResultNote || act.result_note || '';
            const registerDateRaw = act.RegisterDate || act.register_date || act.registerDate || '';
            const doneDateRaw = act.DoneDate || act.done_date || act.doneDate || '';
            const dueDateRaw = act.DueDate || act.due_date || act.dueDate || '';
            const registerDate = registerDateRaw ? toPersianDateTime(registerDateRaw) : '';
            const doneDate = doneDateRaw ? toPersianDateTime(doneDateRaw) : '';
            const dueDate = dueDateRaw ? toPersianDateTime(dueDateRaw) : '';
            const isScheduled = act.is_scheduled === 1 || act.is_scheduled === true || (!isDone && dueDate);
            const registerDateCompare = act.register_date || act.RegisterDate || '';
            const isEdited = act.last_update_time && act.last_update_time !== registerDateCompare;
            
            // Determine status and date to show
            let statusBadge = '';
            let dateInfo = '';
            
            if (isDone) {
                statusBadge = '<span class="text-xs bg-green-500/20 text-green-300 px-2 py-0.5 rounded">✓ انجام شده</span>';
                if (doneDate) {
                    dateInfo = `<div class="text-xs text-green-400 mt-1"><i class="fa-solid fa-check-circle ml-1"></i> تاریخ انجام: ${doneDate}</div>`;
                }
            } else if (isScheduled) {
                statusBadge = '<span class="text-xs bg-yellow-500/20 text-yellow-300 px-2 py-0.5 rounded">⏰ برنامه‌ریزی شده</span>';
                if (dueDate) {
                    dateInfo = `<div class="text-xs text-yellow-400 mt-1"><i class="fa-solid fa-clock ml-1"></i> موعد: ${dueDate}</div>`;
                }
            } else {
                statusBadge = '<span class="text-xs bg-blue-500/20 text-blue-300 px-2 py-0.5 rounded">📝 ثبت شده</span>';
            }
            
            const activityId = act.Id || act.didar_activity_id || act.id;
            const deleteBtn = this.isAdmin() ? `
                <button onclick="event.stopPropagation(); app.deleteActivity('${activityId}')" 
                        class="text-red-500 hover:text-red-700 text-xs px-2 py-1 rounded transition" 
                        title="حذف فعالیت">
                    <i class="fa-solid fa-trash"></i>
                </button>` : '';
            
            const editBtn = !isDone && isScheduled ? `
                <button onclick="event.stopPropagation(); app.openEditScheduledActivityModal('${activityId}')" 
                        class="text-blue-500 hover:text-blue-700 text-xs px-2 py-1 rounded transition" 
                        title="ویرایش فعالیت">
                    <i class="fa-solid fa-edit"></i>
                </button>` : '';
            
            return `
                <div class="glass p-4 rounded-lg border-l-4 ${isDone ? 'border-green-500' : isScheduled ? 'border-yellow-500' : 'border-blue-500'} relative cursor-default">
                    ${deleteBtn || editBtn ? `<div class="absolute top-2 left-2 z-10 flex gap-1">${editBtn}${deleteBtn}</div>` : ''}
                    ${isEdited ? `<div class="absolute top-2 right-2 z-10"><span class="text-xs bg-purple-500/20 text-purple-300 px-2 py-0.5 rounded" title="ویرایش شده"><i class="fa-solid fa-pencil ml-1"></i> ویرایش شده</span></div>` : ''}
                    <div class="flex justify-between items-start mb-2">
                        <div class="flex-1">
                            <div class="flex items-center gap-2 mb-1">
                                <span class="text-sm font-bold ${isNote ? 'text-purple-300' : 'text-blue-300'}">
                                    <i class="fa-solid fa-${isNote ? 'sticky-note' : 'calendar-check'} ml-1"></i>
                                    ${this.escapeHtml(activityType)}
                                </span>
                                ${statusBadge}
                            </div>
                            <div class="text-xs text-gray-400 mb-2 space-y-1">
                                ${registerDate ? `<div><i class="fa-solid fa-calendar-plus ml-1"></i> تاریخ ثبت: ${registerDate}</div>` : ''}
                                ${dueDate ? `<div><i class="fa-solid fa-calendar-clock ml-1"></i> تاریخ/ساعت برنامه‌ریزی: ${dueDate}</div>` : ''}
                                ${isScheduled && !dueDate ? `<div><i class="fa-solid fa-clock ml-1"></i> فعالیت برنامه‌ریزی شده (تاریخ مشخص نشده)</div>` : ''}
                                ${doneDate && isDone ? `<div><i class="fa-solid fa-check-double ml-1"></i> تاریخ انجام: ${doneDate}</div>` : ''}
                            </div>
                            <div class="text-xs text-gray-500 mb-2">
                                <span>ایجاد کننده: ${this.escapeHtml(creator)}</span>
                                ${owner !== creator ? `<span class="mx-2">|</span><span>مسئول: ${this.escapeHtml(owner)}</span>` : ''}
                            </div>
                            ${dateInfo}
                        </div>
                    </div>
                    
                    ${note ? `
                        <div class="mb-2">
                            <div class="text-xs text-gray-400 mb-1"><i class="fa-solid fa-note-sticky ml-1"></i> یادداشت:</div>
                            <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(note))}</div>
                        </div>
                    ` : ''}
                    
                    ${resultNote ? `
                        <div class="mb-2">
                            <div class="text-xs text-gray-400 mb-1"><i class="fa-solid fa-clipboard-check ml-1"></i> نتیجه:</div>
                            <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(resultNote))}</div>
                        </div>
                    ` : ''}
                    
                    ${!note && !resultNote ? `
                        <div class="text-xs text-gray-500 italic">بدون توضیحات</div>
                    ` : ''}
                </div>
            `;
        }).join('');
    },
    
    renderActivitiesList(activities) {
        if (!activities || activities.length === 0) {
            return '<p class="text-gray-500 text-sm text-center py-4">فعالیتی ثبت نشده است</p>';
        }
        
        const resolveUserName = (didarId) => {
            const users = this.state.didarUsers || [];
            if (!didarId || !users.length) return '';
            const user = users.find(u =>
                u.owner_id === didarId ||
                u.didar_user_id === didarId ||
                u.id === didarId
            );
            return user?.display_name || '';
        };

        // Sort by RegisterDate (newest first)
        const sorted = [...activities].sort((a, b) => {
            const dateA = a.RegisterDate || a.register_date ? new Date(a.RegisterDate || a.register_date) : new Date(0);
            const dateB = b.RegisterDate || b.register_date ? new Date(b.RegisterDate || b.register_date) : new Date(0);
            return dateB - dateA;
        });

        return sorted.map(act => {
            const isDone = act.IsDone === true || act.IsDone === 1 || act.is_done === 1 || act.is_done === true;
            const isNote = (act.ActivityTypeId || act.activity_type_id) === '00000000-0000-0000-0000-000000000000';
            const activityType = act.ActivityType?.Title || act.ActivityTypeTitle || act.activity_type_title || (isNote ? 'یادداشت' : 'فعالیت');

            let raw = act.raw_json;
            if (typeof raw === 'string') {
                try {
                    raw = JSON.parse(raw);
                } catch (_) {
                    raw = null;
                }
            }

            const creator = act.Creator?.DisplayName
                || act.CreatorName
                || act.activity_owner_name_snapshot
                || act.creator_name
                || raw?.creator_name
                || resolveUserName(act.creator_didar_id)
                || resolveUserName(act.owner_didar_id)
                || 'نامشخص';

            const owner = act.Owner?.DisplayName
                || act.OwnerName
                || act.activity_owner_name_snapshot
                || act.owner_name
                || raw?.owner_name
                || resolveUserName(act.owner_didar_id)
                || 'نامشخص';

            const note = act.Note || act.note || '';
            const resultNote = act.ResultNote || act.result_note || '';
            const registerDateRaw = act.RegisterDate || act.register_date || act.registerDate || '';
            const doneDateRaw = act.DoneDate || act.done_date || act.doneDate || '';
            const registerDate = registerDateRaw ? toPersianDateTime(registerDateRaw) : '';
            const doneDate = doneDateRaw ? toPersianDateTime(doneDateRaw) : '';
            
            // Check for due date - try multiple possible field names
            let dueDate = '';
            if (act.DueDate || act.due_date) {
                try {
                    dueDate = toPersianDateTime(act.DueDate || act.due_date);
                } catch (e) {
                    dueDate = act.DueDate || act.due_date; // Fallback to raw value
                }
            }

            
            // Check if activity is scheduled
            const hasDueDate = dueDate && dueDate.trim() !== '';
            const isScheduledFlag = act.is_scheduled === 1 || act.is_scheduled === true || act.is_scheduled === '1';
            const isScheduled = isScheduledFlag || (!isDone && hasDueDate);
            
            // Determine status badge
            let statusBadge = '';
            if (isDone) {
                statusBadge = '<span class="text-xs bg-green-500/20 text-green-300 px-2 py-0.5 rounded">✓ انجام شده</span>';
            } else if (isScheduled) {
                statusBadge = '<span class="text-xs bg-yellow-500/20 text-yellow-300 px-2 py-0.5 rounded">⏰ برنامه‌ریزی شده</span>';
            } else {
                statusBadge = '<span class="text-xs bg-blue-500/20 text-blue-300 px-2 py-0.5 rounded">📝 ثبت شده</span>';
            }
            
            const activityId = act.Id || act.didar_activity_id || act.id;
            
            return `
                <div class="glass p-4 rounded-lg border-r-4 ${isDone ? 'border-green-500' : isScheduled ? 'border-yellow-500' : 'border-blue-500'} cursor-default">
                    <div class="flex justify-between items-start mb-2">
                        <div class="flex-1">
                            <div class="flex items-center gap-2 mb-1">
                                <span class="text-xs font-bold ${isNote ? 'text-purple-300' : 'text-blue-300'}">
                                    <i class="fa-solid fa-${isNote ? 'sticky-note' : (isDone ? 'check-circle' : isScheduled ? 'clock' : 'calendar-check')} ml-1"></i>
                                    ${this.escapeHtml(activityType)}
                                </span>
                                ${statusBadge}
                            </div>
                            <div class="font-bold text-white text-sm mb-1">${this.escapeHtml(act.Title || 'بدون عنوان')}</div>
                        </div>
                        <div class="text-left text-xs text-gray-400">
                            ${registerDate ? `<div><i class="fa-solid fa-calendar-plus ml-1"></i> ثبت: ${registerDate}</div>` : ''}
                            ${doneDate ? `<div class="text-green-400 mt-1"><i class="fa-solid fa-check-circle ml-1"></i> انجام: ${doneDate}</div>` : ''}
                            ${dueDate && isScheduled ? `<div class="text-yellow-400 mt-1"><i class="fa-solid fa-calendar-clock ml-1"></i> موعد: ${dueDate}</div>` : ''}
                            ${isScheduled && !dueDate ? `<div class="text-yellow-400 mt-1"><i class="fa-solid fa-clock ml-1"></i> برنامه‌ریزی شده (تاریخ مشخص نشده)</div>` : ''}
                        </div>
                    </div>
                    
                    ${note ? `
                        <div class="mb-2">
                            <div class="text-xs text-gray-400 mb-1">یادداشت:</div>
                            <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(note))}</div>
                        </div>
                    ` : ''}
                    
                    ${resultNote ? `
                        <div class="mb-2">
                            <div class="text-xs text-gray-400 mb-1">نتیجه:</div>
                            <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(resultNote))}</div>
                        </div>
                    ` : ''}
                    
                    <div class="flex justify-between items-center mt-2 pt-2 border-t border-gray-700">
                        <div class="text-xs text-gray-500">
                            <span>ثبت‌کننده: <span class="text-gray-400">${this.escapeHtml(creator)}</span></span>
                            ${owner !== creator ? `<span class="mr-3">مسئول: <span class="text-gray-400">${this.escapeHtml(owner)}</span></span>` : ''}
                        </div>
                        ${act.Duration ? `<div class="text-xs text-gray-500">مدت: ${act.Duration} دقیقه</div>` : ''}
                    </div>
                </div>
            `;
        }).join('');
    },
    
    filterActivities(searchText = null, filterType = null, filterStatus = null) {
        if (!this.state.didarActivities) return;
        
        const search = searchText !== null ? searchText : (document.getElementById('activity-search-input')?.value || '');
        const type = filterType !== null ? filterType : (document.getElementById('activity-filter-type')?.value || '');
        const status = filterStatus !== null ? filterStatus : (document.getElementById('activity-filter-status')?.value || '');
        
        let filtered = [...this.state.didarActivities];
        
        // Filter by search text
        if (search) {
            const searchLower = search.toLowerCase();
            filtered = filtered.filter(act => {
                const title = (act.Title || '').toLowerCase();
                const note = (act.Note || '').toLowerCase();
                const resultNote = (act.ResultNote || '').toLowerCase();
                const activityType = (act.ActivityType?.Title || act.ActivityTypeTitle || '').toLowerCase();
                const creator = (act.Creator?.DisplayName || act.CreatorName || '').toLowerCase();
                return title.includes(searchLower) || 
                       note.includes(searchLower) || 
                       resultNote.includes(searchLower) ||
                       activityType.includes(searchLower) ||
                       creator.includes(searchLower);
            });
        }
        
        // Filter by activity type
        if (type) {
            filtered = filtered.filter(act => {
                const actTypeId = act.ActivityTypeId || '';
                return actTypeId === type;
            });
        }
        
        // Filter by status
        if (status) {
            if (status === 'done') {
                filtered = filtered.filter(act => act.IsDone === true || act.IsDone === 1);
            } else if (status === 'pending') {
                filtered = filtered.filter(act => !act.IsDone || act.IsDone === 0 || act.IsDone === false);
            }
        }
        
        this.state.didarActivitiesFiltered = filtered;
        
        const listContainer = document.getElementById('activities-list');
        if (listContainer) {
            listContainer.innerHTML = this.renderActivitiesList(filtered);
        }
    },
    
    async loadActivityTypesForDidarFilter() {
        try {
            const res = await this.req('get_activity_types');
            if (res.types && Array.isArray(res.types)) {
                const select = document.getElementById('activity-filter-type');
                if (select) {
                    // Clear existing options except first one
                    select.innerHTML = '<option value="">همه انواع</option>';
                    res.types.forEach(type => {
                        const option = document.createElement('option');
                        option.value = type.Id;
                        option.textContent = type.Title;
                        select.appendChild(option);
                    });
                }
            }
        } catch (error) {
            console.error('Error loading activity types for filter:', error);
        }
    },
    
    switchProfileTab(tab) {
        document.querySelectorAll('.profile-tab').forEach(t => {
            t.classList.remove('active', 'text-yellow-500', 'border-b-2', 'border-yellow-500');
            t.classList.add('text-gray-400');
        });
        document.querySelectorAll('.profile-tab-content').forEach(c => c.classList.add('hidden'));
        
        const tabEl = document.querySelector(`.profile-tab[onclick*="${tab}"]`);
        if(tabEl) {
            tabEl.classList.add('active', 'text-yellow-500', 'border-b-2', 'border-yellow-500');
            tabEl.classList.remove('text-gray-400');
        }
        
        const contentEl = document.getElementById(`profile-tab-${tab}`);
        if(contentEl) contentEl.classList.remove('hidden');
        
        // Load support logs when switching to support tab
        if (tab === 'support' && window.currentContactId) {
            this.loadSupportLogs(window.currentContactId);
        }
        // Load customer-access requests when switching to that tab
        if (tab === 'customer-access' && window.currentContactId) {
            this.loadAccessRequests(window.currentContactId);
        }
    },
    
    async loadActivityTypesForModal() {
        const selects = [
            document.getElementById('activity-type-select'),
            document.getElementById('deal-activity-type-select')
        ].filter(Boolean);
        if (selects.length === 0) return;

        selects.forEach(sel => sel.innerHTML = '<option value="">-- انتخاب نوع فعالیت --</option>');

        // Load activity types if not already loaded
        if (!this.state.activityTypes || this.state.activityTypes.length === 0) {
            try {
                await this.loadActivityTypes();
            } catch (error) {
                console.error('Error loading activity types:', error);
                // Fallback to hardcoded types if API fails
                this.state.activityTypes = [
                    { Id: '2a335dc1-4834-484b-b6dc-9ddd97eef67e', Title: 'تماس ورودی', Direction: 'incoming', Category: 'call', IsDisabled: false },
                    { Id: '790079e8-74ac-4760-9527-097627d3e7ec', Title: 'تماس خروجی', Direction: 'outgoing', Category: 'call', IsDisabled: false },
                    { Id: '3b446dc2-5945-595c-c7ed-0eee08ff78fd', Title: 'چت ورودی', Direction: 'incoming', Category: 'chat', IsDisabled: false },
                    { Id: '4c557ed3-6a56-606d-d8fe-1fff19gg89ge', Title: 'چت خروجی', Direction: 'outgoing', Category: 'chat', IsDisabled: false }
                ];
            }
        }

        if (!this.state.activityTypes || this.state.activityTypes.length === 0) {
            const fallback = this.getFallbackActivityTypes();
            if (fallback.length) {
                this.state.activityTypes = fallback;
            }
        }

        const types = (this.state.activityTypes && this.state.activityTypes.length > 0)
            ? this.state.activityTypes.filter(t => !t.IsDisabled)
            : [
                { Id: '2a335dc1-4834-484b-b6dc-9ddd97eef67e', Title: 'تماس ورودی', Direction: 'incoming', Category: 'call' },
                { Id: '790079e8-74ac-4760-9527-097627d3e7ec', Title: 'تماس خروجی', Direction: 'outgoing', Category: 'call' },
                { Id: '3b446dc2-5945-595c-c7ed-0eee08ff78fd', Title: 'چت ورودی', Direction: 'incoming', Category: 'chat' },
                { Id: '4c557ed3-6a56-606d-d8fe-1fff19gg89ge', Title: 'چت خروجی', Direction: 'outgoing', Category: 'chat' }
            ];

        const buildOptions = (sel) => {
            if (!sel) return;
            sel.innerHTML = '<option value="">-- انتخاب نوع فعالیت --</option>';
            types.forEach(type => {
                const opt = document.createElement('option');
                opt.value = type.Id;
                opt.textContent = type.Title || type.Id;
                opt.dataset.direction = type.Direction || 'outgoing';
                sel.appendChild(opt);
            });
        };

        selects.forEach(buildOptions);

        const handleChange = (sel) => {
            sel.addEventListener('change', () => {
                const selectedOption = sel.options[sel.selectedIndex];
                const directionInput = sel.id === 'deal-activity-type-select'
                    ? document.getElementById('deal-activity-direction')
                    : document.getElementById('activity-direction');
                if (selectedOption && directionInput) {
                    directionInput.value = selectedOption.dataset.direction || 'outgoing';
                }
            });
        };

        selects.forEach(handleChange);

        // Reset purchase question visibility
        const purchaseSelect = document.getElementById('activity-has-purchase');
        if (purchaseSelect) {
            purchaseSelect.value = '';
        }
        const dealPurchaseSelect = document.getElementById('deal-activity-has-purchase');
        if (dealPurchaseSelect) {
            dealPurchaseSelect.value = '';
        }
        this.handlePurchaseOtherNumberChange();
    },
    
    // SYNC
    async sync() {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند همگام‌سازی را انجام دهد', 'error');
            return;
        }
        
        Swal.fire({
            title: 'همگام‌سازی',
            text: 'در حال همگام‌سازی با CRM خارجی...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        const res = await this.req('sync', {entity_type: 'all'});
        Swal.close();
        
        if(res.status === 'success') {
            Swal.fire({
                icon: 'success',
                title: 'موفق',
                text: res.message || 'همگام‌سازی با موفقیت انجام شد',
                timer: 2000
            });
            this.refresh();
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },

    // SAVE USER
    async saveUser(e) {
        e.preventDefault();
        const form = e.target;
        const data = Object.fromEntries(new FormData(form));
        const isCreate = !data.user_id;
        if (isCreate || data.password || data.password_confirm) {
            if (!data.password || !data.password_confirm) {
                Swal.fire('خطا', 'رمز عبور و تکرار آن الزامی است', 'error');
                return;
            }
            if (data.password !== data.password_confirm) {
                Swal.fire('خطا', 'رمز عبور و تکرار آن یکسان نیستند', 'error');
                return;
            }
        }
        const res = await this.req('save_user', data);
        if(res.status === 'success') {
            this.closeModal('modalUser');
            this.loadUsers();
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'ذخیره شد', showConfirmButton:false, timer:2000});
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    openUserModal(userId = null) {
        const form = document.querySelector('#modalUser form');
        const title = document.getElementById('modal-user-title');
        form.reset();
        document.getElementById('user-form-id').value = userId || '';
        const didarField = document.getElementById('user-didar-select');
        if (didarField) didarField.value = '';
        if (title) title.textContent = userId ? 'ویرایش کاربر' : 'تعریف کاربر جدید';
        
        if(userId) {
            this.req('get_user', { user_id: userId }).then(res => {
                if (res.status !== 'success' || !res.user) {
                    Swal.fire('خطا', res.message || 'خطا در بارگذاری کاربر', 'error');
                    return;
                }
                const user = res.user;
                form.querySelector('input[name="email"]').value = user.email || '';
                form.querySelector('input[name="first_name"]').value = user.first_name || '';
                form.querySelector('input[name="last_name"]').value = user.last_name || '';
                form.querySelector('input[name="display_name"]').value = user.display_name || '';
                form.querySelector('input[name="username"]').value = user.username || '';
                form.querySelector('select[name="role"]').value = user.role || 'agent';
                form.querySelector('select[name="is_active"]').value = String(user.is_active ?? 1);
                if (didarField) didarField.value = user.didar_user_id || '';
            });
            const password = form.querySelector('input[name="password"]');
            const passwordConfirm = form.querySelector('input[name="password_confirm"]');
            if (password) password.required = false;
            if (passwordConfirm) passwordConfirm.required = false;
        } else {
            const password = form.querySelector('input[name="password"]');
            const passwordConfirm = form.querySelector('input[name="password_confirm"]');
            if (password) password.required = true;
            if (passwordConfirm) passwordConfirm.required = true;
        }
        
        this.openModal('modalUser');
    },

    async transferUserData(userId, userName) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم مجاز است', 'error');
            return;
        }
        const users = Array.isArray(this.state.lastUsersList) ? this.state.lastUsersList : [];
        const fromUser = users.find(u => String(u.id) === String(userId));
        if (!fromUser || !fromUser.didar_user_id) {
            Swal.fire('خطا', 'کاربر مبدا معتبر نیست', 'error');
            return;
        }
        const options = users
            .filter(u => u.didar_user_id && String(u.id) !== String(userId))
            .map(u => `<option value="${this.escapeHtml(u.didar_user_id)}">${this.escapeHtml(u.display_name || `${u.first_name || ''} ${u.last_name || ''}`)}${u.username ? ` (${this.escapeHtml(u.username)})` : ''}</option>`)
            .join('');
        const ownerOptions = users
            .filter(u => u.didar_user_id)
            .map(u => `<option value="${this.escapeHtml(u.didar_user_id)}">${this.escapeHtml(u.display_name || `${u.first_name || ''} ${u.last_name || ''}`)}${u.username ? ` (${this.escapeHtml(u.username)})` : ''}</option>`)
            .join('');
        const productOptions = (Array.isArray(this.state.products) ? this.state.products : [])
            .map(p => `<option value="${this.escapeHtml(p.id)}">${this.escapeHtml(p.name || '')}</option>`)
            .join('');
        const serviceOptions = this.getLookupOptions('allowed_services')
            .map(item => `<option value="${this.escapeHtml(item.value)}">${this.escapeHtml(item.label || item.value)}</option>`)
            .join('');
        const topicOptions = this.getLookupOptions('content_topics')
            .map(item => `<option value="${this.escapeHtml(item.value)}">${this.escapeHtml(item.label || item.value)}</option>`)
            .join('');
        const levelOptions = this.getLookupOptions('customer_levels')
            .map(item => `<option value="${this.escapeHtml(item.value)}">${this.escapeHtml(item.label || item.value)}</option>`)
            .join('');
        const previewAsk = await Swal.fire({
            title: 'انتقال دیتا',
            html: `
                <div class="text-right space-y-3 text-sm">
                    <div class="text-gray-300">کاربر مبدا: <b>${this.escapeHtml(userName)}</b></div>
                    <label class="block text-xs text-gray-400">کاربر مقصد</label>
                    <select id="transfer-user-target" class="swal2-input" style="width:100%;margin:0">${options}</select>
                    <label class="block text-xs text-gray-400">نوع انتقال</label>
                    <select id="transfer-user-mode" class="swal2-input" style="width:100%;margin:0">
                        <option value="filtered">فقط لیدهای فیلترشده</option>
                        <option value="full">انتقال کامل همه دیتاهای مالکیتی</option>
                    </select>
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-2">
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">مسئول قبلی</label>
                            <select id="transfer-prev-owner" class="swal2-input" style="width:100%;margin:0"><option value="">همه</option>${ownerOptions}</select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">وضعیت فروش</label>
                            <select id="transfer-sale-status" class="swal2-input" style="width:100%;margin:0">
                                <option value="all">همه</option>
                                <option value="success">موفق</option>
                                <option value="failed">ناموفق</option>
                            </select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">محصول خریداری شده</label>
                            <select id="transfer-product-id" class="swal2-input" style="width:100%;margin:0"><option value="">همه</option>${productOptions}</select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">خدمت درخواستی</label>
                            <select id="transfer-requested-service" class="swal2-input" style="width:100%;margin:0"><option value="">همه</option>${serviceOptions}</select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">موضوع محتوا</label>
                            <select id="transfer-content-topic" class="swal2-input" style="width:100%;margin:0"><option value="">همه</option>${topicOptions}</select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">سطح مشتری</label>
                            <select id="transfer-customer-level" class="swal2-input" style="width:100%;margin:0"><option value="">همه</option>${levelOptions}</select>
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">تعداد انتقال</label>
                            <input id="transfer-lead-limit" type="number" min="1" step="1" class="swal2-input" style="width:100%;margin:0" placeholder="مثلاً 100">
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">جستجو</label>
                            <input id="transfer-search" class="swal2-input" style="width:100%;margin:0" placeholder="نام، موبایل، کد مشتری">
                        </div>
                    </div>
                    <div class="text-xs text-amber-300">در حالت فیلترشده فقط لیدها منتقل می‌شوند و مالک‌های قبلی در تاریخچه باقی می‌مانند.</div>
                </div>`,
            width: 860,
            showCancelButton: true,
            confirmButtonText: 'پیش‌نمایش',
            cancelButtonText: 'انصراف',
            preConfirm: () => {
                const value = document.getElementById('transfer-user-target')?.value || '';
                if (!value) {
                    Swal.showValidationMessage('کاربر مقصد را انتخاب کنید');
                    return false;
                }
                return {
                    targetDidarId: value,
                    mode: document.getElementById('transfer-user-mode')?.value || 'filtered',
                    previousOwnerId: document.getElementById('transfer-prev-owner')?.value || '',
                    saleStatus: document.getElementById('transfer-sale-status')?.value || 'all',
                    productId: document.getElementById('transfer-product-id')?.value || '',
                    requestedService: document.getElementById('transfer-requested-service')?.value || '',
                    contentTopic: document.getElementById('transfer-content-topic')?.value || '',
                    customerLevel: document.getElementById('transfer-customer-level')?.value || '',
                    leadLimit: document.getElementById('transfer-lead-limit')?.value || '',
                    search: document.getElementById('transfer-search')?.value || ''
                };
            }
        });
        if (!previewAsk.isConfirmed) return;

        const transferPayload = {
            from_user_didar_id: fromUser.didar_user_id,
            to_user_didar_id: previewAsk.value.targetDidarId,
            preview: 1,
            leads_only: previewAsk.value.mode === 'filtered' ? 1 : 0
        };
        if (previewAsk.value.mode === 'filtered') {
            Object.assign(transferPayload, {
                previous_owner_id: previewAsk.value.previousOwnerId,
                sale_status: previewAsk.value.saleStatus,
                product_id: previewAsk.value.productId,
                requested_service: previewAsk.value.requestedService,
                content_topic: previewAsk.value.contentTopic,
                customer_level: previewAsk.value.customerLevel,
                lead_limit: previewAsk.value.leadLimit,
                search: previewAsk.value.search
            });
        }
        const preview = await this.req('transfer_user_data', transferPayload);
        if (preview.status !== 'success') {
            Swal.fire('خطا', preview.message || 'خطا در پیش‌نمایش انتقال', 'error');
            return;
        }
        const counts = preview.counts || {};
        const details = preview.details || {};
        const targetUser = preview.to_user || {};
        const html = Object.keys(counts).map(key => {
            const item = counts[key] || {};
            const detail = details[key] || {};
            const ids = Array.isArray(detail.identifiers) ? detail.identifiers : [];
            return `<div class="border border-slate-700 rounded p-2">
                <div>${this.escapeHtml(item.label)}: <b>${this.formatNumber(item.count || 0)}</b>${item.matched_count ? ` <span class="text-xs text-gray-400">(از ${this.formatNumber(item.matched_count)} مورد مطابق فیلتر)</span>` : ''}</div>
                <div class="text-xs text-gray-500 mt-1 break-all">${ids.length ? this.escapeHtml(ids.join('، ')) : 'شناسه‌ای وجود ندارد'}</div>
            </div>`;
        }).join('');
        const notes = Array.isArray(preview.notes) ? preview.notes : [];
        const confirm = await Swal.fire({
            title: 'تأیید انتقال',
            html: `<div class="text-right text-sm space-y-2"><div>از <b>${this.escapeHtml(userName)}</b> به <b>${this.escapeHtml(targetUser.display_name || targetUser.email || '')}</b></div>${html || '<div>موردی برای انتقال پیدا نشد</div>'}${notes.length ? `<div class="text-xs text-amber-300 pt-2">${notes.map(n => this.escapeHtml(n)).join('<br>')}</div>` : ''}</div>`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'اجرای انتقال',
            cancelButtonText: 'انصراف'
        });
        if (!confirm.isConfirmed) return;

        delete transferPayload.preview;
        const res = await this.req('transfer_user_data', transferPayload);
        if (res.status === 'success') {
            const doneCounts = res.counts || {};
            const doneDetails = res.details || {};
            const doneNotes = Array.isArray(res.notes) ? res.notes : [];
            const doneHtml = Object.keys(doneCounts).map(key => {
                const item = doneCounts[key] || {};
                const detail = doneDetails[key] || {};
                const ids = Array.isArray(detail.identifiers) ? detail.identifiers : [];
                return `<div class="border border-slate-700 rounded p-2">
                    <div>${this.escapeHtml(item.label)}: <b>${this.formatNumber(item.updated || 0)}</b></div>
                    <div class="text-xs text-gray-500 mt-1 break-all">${ids.length ? this.escapeHtml(ids.join('، ')) : 'شناسه‌ای وجود ندارد'}</div>
                </div>`;
            }).join('');
            Swal.fire({
                icon: 'success',
                title: 'انتقال انجام شد',
                html: `<div class="text-right text-sm space-y-2">${doneHtml || (res.message || 'انتقال انجام شد')}${doneNotes.length ? `<div class="text-xs text-slate-400 pt-2">${doneNotes.map(n => this.escapeHtml(n)).join('<br>')}</div>` : ''}</div>`,
                width: 760
            });
            this.loadUsers();
        } else {
            Swal.fire('خطا', res.message || 'خطا در انتقال دیتا', 'error');
        }
    },

    async deleteUser(userId, userName) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم مجاز است', 'error');
            return;
        }
        const users = Array.isArray(this.state.lastUsersList) ? this.state.lastUsersList : [];
        const user = users.find(u => String(u.id) === String(userId));
        if (!user) {
            Swal.fire('خطا', 'کاربر یافت نشد', 'error');
            return;
        }
        const preview = await this.req('get_user_transfer_report', {
            user_didar_id: user.didar_user_id
        }).catch(() => null);
        const counts = preview && preview.status === 'success' ? (preview.counts || {}) : {};
        const details = preview && preview.status === 'success' ? (preview.details || {}) : {};
        const html = Object.keys(counts).map(key => {
            const item = counts[key] || {};
            const detail = details[key] || {};
            const ids = Array.isArray(detail.identifiers) ? detail.identifiers : [];
            return `<div class="border border-slate-700 rounded p-2">
                <div>${this.escapeHtml(item.label)}: <b>${this.formatNumber(item.count || 0)}</b></div>
                <div class="text-xs text-gray-500 mt-1 break-all">${ids.length ? this.escapeHtml(ids.join('، ')) : 'شناسه‌ای وجود ندارد'}</div>
            </div>`;
        }).join('');
        const confirm = await Swal.fire({
            title: 'حذف کاربر',
            html: `<div class="text-right text-sm space-y-2"><div>کاربر <b>${this.escapeHtml(userName)}</b> حذف می‌شود.</div><div class="text-red-300">اگر دیتای مالکیتی دارد، بهتر است ابتدا انتقال دیتا انجام شود.</div>${html}</div>`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف کاربر',
            cancelButtonText: 'انصراف',
            confirmButtonColor: '#dc2626'
        });
        if (!confirm.isConfirmed) return;

        const res = await this.req('delete_user', { user_id: userId });
        if (res.status === 'success') {
            const reportDetails = res.details || {};
            const notes = Array.isArray(res.notes) ? res.notes : [];
            const reportHtml = Object.keys(reportDetails).map(key => {
                const item = reportDetails[key] || {};
                const ids = Array.isArray(item.identifiers) ? item.identifiers : [];
                return `<div class="border border-slate-700 rounded p-2">
                    <div>${this.escapeHtml(item.label || key)}: <b>${this.formatNumber(ids.length)}</b></div>
                    <div class="text-xs text-gray-500 mt-1 break-all">${ids.length ? this.escapeHtml(ids.join('، ')) : 'شناسه‌ای وجود ندارد'}</div>
                </div>`;
            }).join('');
            Swal.fire({
                icon: 'success',
                title: 'کاربر حذف شد',
                html: `<div class="text-right text-sm space-y-2">${reportHtml}${notes.length ? `<div class="text-xs text-slate-400 pt-2">${notes.map(n => this.escapeHtml(n)).join('<br>')}</div>` : ''}</div>`,
                width: 760
            });
            this.loadUsers();
        } else {
            Swal.fire('خطا', res.message || 'خطا در حذف کاربر', 'error');
        }
    },
    
    openEditUserRoleModal(userId, currentRole, userName) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند نقش کاربران را تغییر دهد', 'error');
            return;
        }
        const form = document.querySelector('#modalEditUserRole form');
        form.reset();
        document.getElementById('edit-role-user-id').value = userId;
        document.getElementById('edit-role-user-name').textContent = userName;
        document.getElementById('edit-role-select').value = currentRole;
        this.openModal('modalEditUserRole');
    },
    
    async saveUserRole(e) {
        e.preventDefault();
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند نقش کاربران را تغییر دهد', 'error');
            return;
        }
        const form = e.target;
        const data = Object.fromEntries(new FormData(form));
        
        Swal.fire({
            title: 'در حال ذخیره...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('update_user_role', data);
            Swal.close();
            if (res.status === 'success') {
                Swal.fire({toast: true, position: 'bottom-end', icon: 'success', title: 'نقش کاربر تغییر یافت', showConfirmButton: false, timer: 2000});
                this.closeModal('modalEditUserRole');
                this.loadUsers();
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
            }
        } catch (error) {
            console.error('Error in saveUserRole:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در تغییر نقش: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    openChangePasswordModal(userId = null) {
        const form = document.querySelector('#modalChangePassword form');
        form.reset();
        const targetUserId = userId || window.currentUserId || window.CURRENT_USER_ID || 0;
        document.getElementById('change-password-user-id').value = targetUserId;
        
        // If admin changing someone else's password, hide current password field
        const isAdmin = window.APP_USER?.role === 'admin';
        const isOwnPassword = String(targetUserId) === String(window.CURRENT_USER_ID || 0);
        
        const currentSection = document.getElementById('change-password-current-section');
        const currentInput = document.getElementById('change-password-current');
        
        if (isAdmin && !isOwnPassword) {
            currentSection.style.display = 'none';
            currentInput.value = '';
            currentInput.removeAttribute('required');
        } else {
            currentSection.style.display = 'block';
            currentInput.setAttribute('required', 'required');
        }
        
        this.openModal('modalChangePassword');
    },
    
    async changePassword(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);
        
        const res = await this.req('change_password', data);
        if(res.status === 'success') {
            this.closeModal('modalChangePassword');
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'رمز عبور تغییر یافت', showConfirmButton:false, timer:2000});
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    openChangePasswordModal(userId = null) {
        const form = document.querySelector('#modalChangePassword form');
        form.reset();
        const targetUserId = userId || window.CURRENT_USER_ID || 0;
        document.getElementById('change-password-user-id').value = targetUserId;
        
        // If admin changing someone else's password, hide current password field
        const isAdmin = window.APP_USER?.role === 'admin';
        const isOwnPassword = String(targetUserId) === String(window.CURRENT_USER_ID || 0);
        
        const currentSection = document.getElementById('change-password-current-section');
        const currentInput = document.getElementById('change-password-current');
        
        if (isAdmin && !isOwnPassword) {
            currentSection.style.display = 'none';
            currentInput.value = '';
            currentInput.removeAttribute('required');
        } else {
            currentSection.style.display = 'block';
            currentInput.setAttribute('required', 'required');
        }
        
        this.openModal('modalChangePassword');
    },
    
    async changePassword(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);
        
        const res = await this.req('change_password', data);
        if(res.status === 'success') {
            this.closeModal('modalChangePassword');
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'رمز عبور تغییر یافت', showConfirmButton:false, timer:2000});
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    async changePasswordFromSettings(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);

        const res = await this.req('change_password', data);
        if(res.status === 'success') {
            // Reset form
            e.target.reset();
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'رمز عبور تغییر یافت', showConfirmButton:false, timer:2000});
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },

    async setMasterPassword(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const data = Object.fromEntries(formData);

        // Client-side validation
        if (data.new_master_password !== data.confirm_master_password) {
            Swal.fire('خطا', 'رمز عبور اصلی جدید و تأیید آن مطابقت ندارند', 'error');
            return;
        }

        if (data.new_master_password.length < 6) {
            Swal.fire('خطا', 'رمز عبور اصلی باید حداقل 6 کاراکتر باشد', 'error');
            return;
        }

        const res = await this.req('set_master_password', data);
        if(res.status === 'success') {
            // Reset form
            e.target.reset();
            Swal.fire({
                toast: true, 
                position: 'bottom-end', 
                icon: 'success', 
                title: res.message || 'رمز عبور اصلی تنظیم شد', 
                showConfirmButton: false, 
                timer: 3000
            });
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    // SAVE ACTIVITY
    async saveActivity(e) {
        e.preventDefault();
        
        try {
            console.log('saveActivity called');
            const form = e.target;
            const contactId = window.currentContactId || form.contact_id.value;
            const dealSelectVal = document.getElementById('activity-deal-select')?.value || '';
            const dealHiddenVal = document.getElementById('activity-deal-id')?.value || '';
            const dealId = dealSelectVal || dealHiddenVal;
            
            // Check if selected deal is closed
            if (this.state.currentDeals && dealId) {
                const currentDeal = this.state.currentDeals.find(d => (d.didar_deal_id || d.id || d.deal_id) === dealId);
                if (currentDeal && this.isLegacyDidarDeal(currentDeal)) {
                    this.blockLegacyDidarMutation();
                    return;
                }
                if (currentDeal && (currentDeal.status === 'Won' || currentDeal.status === 'Lost')) {
                    Swal.fire('خطا', 'این معامله بسته شده است و امکان ثبت فعالیت ندارد.', 'error');
                    return;
                }
            }
            
            if(!contactId) {
                Swal.fire('خطا', 'لطفاً شخص را انتخاب کنید', 'error');
                return;
            }
            
            console.log('Contact ID:', contactId);
            
            // بررسی و تنظیم وضعیت required فیلد خرید قبلی قبل از submit
            try {
                this.handleActivityFormVisibility();
            } catch(err) {
                console.error('Error in handleActivityFormVisibility:', err);
            }
            
                const pipelineStage = form.pipeline_stage?.value || '';
                const isScheduled = form.is_scheduled?.checked || false;

                // pipelineStage is only required when a deal is selected
                if (dealId && !pipelineStage) {
                    Swal.fire('خطا', 'لطفاً مرحله کاریز معامله را انتخاب کنید', 'error');
                return;
            }

            if (!form.checkValidity()) {
                form.reportValidity();
                return;
            }
            
            // Get due date if scheduled
            let dueDate = '';
            // isScheduled already declared above
            if (isScheduled) {
                const persianDateValue = form.due_date?.value || '';
                const hourSelect = document.getElementById('activity-due-hour');
                const minuteSelect = document.getElementById('activity-due-minute');
                const hour = hourSelect?.value || '00';
                const minute = minuteSelect?.value || '00';
                
                if (!persianDateValue || !persianDateValue.trim()) {
                    Swal.fire('خطا', 'لطفاً تاریخ را انتخاب کنید', 'error');
                    return;
                }
                
                if (!hour || hour === '') {
                    Swal.fire('خطا', 'لطفاً ساعت را انتخاب کنید', 'error');
                    return;
                }
                
                if (!minute || minute === '') {
                    Swal.fire('خطا', 'لطفاً دقیقه را انتخاب کنید', 'error');
                    return;
                }
                
                try {
                    // Convert Persian/Farsi digits to English digits
                    const persianToEnglish = (str) => {
                        const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
                        const englishDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
                        let result = str;
                        for (let i = 0; i < persianDigits.length; i++) {
                            result = result.replace(new RegExp(persianDigits[i], 'g'), englishDigits[i]);
                        }
                        return result;
                    };
                    
                    // Convert Persian digits to English
                    const englishDateValue = persianToEnglish(persianDateValue.trim());
                    console.log('Original date:', persianDateValue);
                    console.log('Converted date:', englishDateValue);
                    
                    // Parse Persian date string (format: YYYY/MM/DD)
                    const dateParts = englishDateValue.split('/');
                    if (dateParts.length !== 3) {
                        throw new Error('فرمت تاریخ نامعتبر است');
                    }
                    
                    const year = parseInt(dateParts[0]);
                    const month = parseInt(dateParts[1]);
                    const day = parseInt(dateParts[2]);
                    const hours = parseInt(hour);
                    const minutes = parseInt(minute);
                    
                    console.log('Parsed values:', { year, month, day, hours, minutes });
                    
                    // Validate
                    if (isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hours) || isNaN(minutes)) {
                        throw new Error('مقادیر تاریخ یا زمان نامعتبر است');
                    }
                    
                    // Additional validation
                    if (year < 1300 || year > 1500) {
                        throw new Error('سال نامعتبر است');
                    }
                    if (month < 1 || month > 12) {
                        throw new Error('ماه نامعتبر است');
                    }
                    if (day < 1 || day > 31) {
                        throw new Error('روز نامعتبر است');
                    }
                    
                    // Use the helper function that already works
                    // Format: HH:mm
                    const timeString = `${hour}:${minute}`;
                    dueDate = persianToGregorian(englishDateValue, timeString);
                    
                    if (!dueDate) {
                        throw new Error('خطا در تبدیل تاریخ. لطفاً تاریخ معتبری انتخاب کنید.');
                    }
                    
                    // persianToGregorian now returns format: YYYY-MM-DD HH:mm:ss (local timezone)
                    console.log('Converted date (local timezone):', dueDate);
                    
                    // Validate the final date
                    if (!dueDate || dueDate.length < 19) {
                        throw new Error('فرمت تاریخ نهایی نامعتبر است');
                    }
                    
                    console.log('Sending due date to server:', dueDate);
                    
                } catch (e) {
                    console.error('Error converting date:', e, 'Input:', persianDateValue);
                    Swal.fire('خطا', 'تاریخ وارد شده معتبر نیست. لطفاً تاریخ و زمان را به درستی وارد کنید. (' + (e.message || 'خطای نامشخص') + ')', 'error');
                    return;
                }
            }
            
                const activityTypeSelect = form.activity_type_id;
                const activityTypeTitle = activityTypeSelect?.selectedOptions?.[0]?.textContent?.trim?.() || '';
                const activityDuration = Math.max(0, parseInt(form.activity_duration?.value || '0', 10) || 0);
                if (!activityTypeSelect?.value) {
                    Swal.fire('خطا', 'نوع فعالیت را انتخاب کنید', 'error');
                    return;
                }
                if (activityDuration <= 0) {
                    Swal.fire('خطا', 'مدت زمان فعالیت الزامی است', 'error');
                    if (form.activity_duration?.focus) form.activity_duration.focus();
                    return;
                }
                const callType = form.call_type?.value || '';
                if (callType !== 'voip' && callType !== 'phone' && callType !== 'sms') {
                    Swal.fire('خطا', 'نوع مکالمه را انتخاب کنید', 'error');
                    if (form.call_type?.focus) form.call_type.focus();
                    return;
                }

                const data = {
                    contact_id: contactId,
                    deal_id: dealId,
                    activity_type_id: activityTypeSelect?.value || '',
                    activity_type_title: activityTypeTitle,
                    note: form.note?.value || '',
                    result_note: form.result_note?.value || '',
                    activity_duration: activityDuration,
                    call_type: callType,
                    direction: form.direction?.value || 'outgoing',
                    current_stage: form.current_stage?.value || 'keep_current',
                    pipeline_stage: form.pipeline_stage?.value || '',
                    has_previous_purchase_other_number: form.has_previous_purchase_other_number?.value || '',
                    previous_purchase_number: form.previous_purchase_number?.value || '',
                    referral_note: form.referral_note?.value || '',
                    failure_reason: form.failure_reason?.value || '',
                    failure_other_description: form.failure_other_description?.value || '',
                    is_done: form.is_done?.checked ? '1' : '0',
                    is_scheduled: isScheduled ? '1' : '0',
                    due_date: dueDate,
                    reminder_enabled: form.reminder_enabled?.checked ? '1' : '0',
                    reminder_offset_minutes: this.calculateReminderOffset(form)
                };
            
            console.log('Sending data:', data);
            
            Swal.showLoading();
            let res;
            try {
                res = await this.req('save_activity', data);
            } catch (err) {
                console.error('saveActivity failed', err);
                this.closeBlockingSwal();
                Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
                return;
            }
            this.closeBlockingSwal();
            
            console.log('Response received:', res);
            
            if(res.status === 'success') {
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'فعالیت ثبت شد', showConfirmButton:false, timer:2000});
                this.closeModal('modalActivity');
                form.reset();
                if(window.currentContactId) {
                    // Reload person profile to get updated virtual stage
                    await this.openPersonProfile(window.currentContactId);
                    // Reload deals to منعکس کردن تغییر مرحله کاریز
                    await this.loadDeals();
                    // اگر مودال معامله باز است، آن را بدون بستن رفرش کنیم
                    const dealModal = document.getElementById('modalDeal');
                    if (dealModal && !dealModal.classList.contains('hidden')) {
                        const currentDealId = this.state.currentDeal?.didar_deal_id || document.getElementById('deal-id')?.value || '';
                        if (currentDealId) {
                            await this.openDealModal(currentDealId);
                            this.showDealFormTab('activities');
                        }
                    }
                    // Also reload leads list if we're on leads view
                    if (document.getElementById('view-leads') && !document.getElementById('view-leads').classList.contains('hidden')) {
                        await this.loadLeads();
                    }
                }
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
            }
        } catch(error) {
            console.error('Error in saveActivity:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در ثبت فعالیت: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    // HANDLE DEAL STATUS CHANGE
    handleDealStatusChange() {
        const status = document.getElementById('deal-status').value;
        const form = document.getElementById('deal-form');
        const paymentDetails = document.getElementById('deal-payment-details');
        const failureReasonSection = document.getElementById('deal-failure-reason-section');
        const failureReason = document.getElementById('deal-failure-reason');
        const failureOtherReason = document.getElementById('deal-failure-other-reason');
        const failureOtherDesc = document.getElementById('deal-failure-other-description');
        const pipelineStageSelect = document.getElementById('deal-pipeline-stage');
        const pipelineStageIndicator = document.getElementById('pipeline-stage-required-indicator');
        
        const isLost = status === 'lost';
        const shouldRequirePayments = status === 'won';

        // Only force the new-payment form open when the deal is being marked successful
        // (payments are mandatory then); otherwise leave its collapsed/expanded state as-is.
        if (shouldRequirePayments) this.setPaymentFormVisible(true);

        if (failureReasonSection) failureReasonSection.classList.toggle('hidden', !isLost);
        if (failureReason) {
            failureReason.required = isLost;
            if (!isLost) failureReason.value = '';
        }
        if (failureOtherReason) failureOtherReason.classList.toggle('hidden', !isLost);
        if (failureOtherDesc) {
            failureOtherDesc.required = isLost;
            if (!isLost) {
                failureOtherDesc.value = '';
            } else {
                failureOtherDesc.placeholder = 'لطفاً توضیحات دلیل ناموفق بودن را وارد کنید';
            }
        }

        // Make pipeline stage optional when status is lost
        if (pipelineStageSelect) {
            pipelineStageSelect.required = !isLost;
            if (isLost && !pipelineStageSelect.value) {
                // Optionally set a default value for lost deals
                pipelineStageSelect.value = 'معامله ناموفق';
            }
        }
        // Update the required indicator
        if (pipelineStageIndicator) {
            pipelineStageIndicator.style.display = isLost ? 'none' : 'inline';
        }

        const payableAmount = document.getElementById('deal-payable-amount');
        const paymentConditions = document.getElementById('deal-payment-conditions');
        if (payableAmount) payableAmount.required = true;
        if (paymentConditions) paymentConditions.required = false;

        if (form) {
            form.querySelectorAll('#payment-rows input, #payment-rows select, #payment-rows textarea')
                .forEach(field => field.required = false);
            const visiblePaymentRows = Array.from(form.querySelectorAll('#payment-rows > [data-existing-payment]'))
                .filter(row => row.offsetParent !== null);
            const paymentDates = visiblePaymentRows.map(row => row.querySelector('input[name="payment_date[]"]')).filter(Boolean);
            const paymentAmounts = visiblePaymentRows.map(row => row.querySelector('input[name="payment_amount[]"]')).filter(Boolean);
            const paymentMethods = visiblePaymentRows.map(row => row.querySelector('select[name="payment_method[]"]')).filter(Boolean);
            const paymentDescriptions = visiblePaymentRows.map(row => row.querySelector('textarea[name="payment_description[]"]')).filter(Boolean);
            paymentDates.forEach(input => input.required = shouldRequirePayments);
            paymentAmounts.forEach(input => input.required = shouldRequirePayments);
            paymentMethods.forEach(select => select.required = shouldRequirePayments);
            paymentDescriptions.forEach(textarea => textarea.required = shouldRequirePayments);
        }

        this.syncParkedDealUi(this.state.currentDeal);
        this.updateDealCurrentFields(null, status);
    },

    handleDealPipelineStageChange() {
        const stageSelect = document.getElementById('deal-pipeline-stage');
        const statusSelect = document.getElementById('deal-status');
        const stageVal = stageSelect?.value || '';
        const isFailed = stageVal === 'معامله ناموفق' || stageVal === 'deal_failed';
        const isSuccess = stageVal === 'اتمام' || stageVal === 'deal_success';
        if (statusSelect) {
            if (isFailed) {
                statusSelect.value = 'lost';
            } else if (isSuccess) {
                statusSelect.value = 'won';
            }
        }
        this.handleDealStatusChange();
        this.updateDealCurrentFields(stageVal, statusSelect?.value || '');
    },

    handleActivityStageChange(stageVal) {
        const statusSelect = document.getElementById('deal-status');
        const contactId = document.getElementById('deal-activity-contact-id')?.value || window.currentContactId || '';
        const dealId = document.getElementById('deal-activity-deal-id')?.value || document.getElementById('deal-id')?.value || '';
        const paymentsOk = this.hasEnoughPaymentsForSuccess();
        const failureBox = document.getElementById('failure-reason-extra');
        const stageSelect = document.getElementById('activity-virtual-stage') || document.getElementById('deal-activity-stage');
        console.log('handleActivityStageChange', { stageVal, paymentsOk, contactId, dealId });

        if (stageVal === 'deal_failed' || stageVal === 'معامله ناموفق') {
            if (statusSelect) {
                statusSelect.value = 'lost';
                this.handleDealStatusChange();
            }
            const pipelineStageSelect = document.getElementById('deal-pipeline-stage');
            if (pipelineStageSelect) {
                pipelineStageSelect.value = 'معامله ناموفق';
            }
            this.toggleDealActivityFailure();
            this.toggleActivityFailureOtherReason();
            return;
        }

        if (stageVal === 'deal_success') {
            if (!paymentsOk) {
                Swal.fire('خطا', 'برای ثبت معامله موفق، جزئیات پرداخت باید کامل باشد.', 'error');
                if (stageSelect) stageSelect.value = this.state.activityStagePrev || '';
                console.warn('Blocked deal_success due to missing payments');
                return;
            }
            Swal.fire({
                icon: 'info',
                title: 'ثبت معامله موفق',
                text: 'معامله به عنوان فروش موفق ثبت و بایگانی می‌شود.',
                confirmButtonText: 'متوجه شدم'
            });
            if (statusSelect) {
                statusSelect.value = 'won';
                this.handleDealStatusChange();
            }
            const pipelineStageSelect = document.getElementById('deal-pipeline-stage');
            if (pipelineStageSelect) {
                pipelineStageSelect.value = 'اتمام';
            }
        }

        this.toggleDealActivityFailure();
        this.toggleActivityFailureOtherReason();
        this.updateDealCurrentFields(stageVal, statusSelect?.value || '');
        this.state.activityStagePrev = stageVal || '';
    },
    
	    // TOGGLE FAILURE OTHER REASON (deal form + deal-failed modal)
	    toggleFailureOtherReason() {
	        // Deal form in modalDeal (inline reason select)
	        const dealReasonSelect = document.getElementById('deal-failure-reason');
	        const dealOtherDiv = document.getElementById('deal-failure-other-reason');
	        const dealOtherDesc = document.getElementById('deal-failure-other-description');
	        const dealStatusSelect = document.getElementById('deal-status');

	        if (dealReasonSelect && dealOtherDiv && dealOtherDesc && dealStatusSelect) {
	            const reason = dealReasonSelect.value;
	            const dealStatus = dealStatusSelect.value;

	            // For lost deals, description is always shown + required
	            if (dealStatus === 'lost') {
	                dealOtherDiv.classList.remove('hidden');
	                dealOtherDesc.required = true;
	                dealOtherDesc.placeholder = 'لطفاً توضیحات دلیل ناموفق بودن را وارد کنید';
	            } else {
	                // For other statuses, only show description when "other" is selected
	                if (reason === 'other') {
	                    dealOtherDiv.classList.remove('hidden');
	                    dealOtherDesc.required = true;
	                } else {
	                    dealOtherDiv.classList.add('hidden');
	                    dealOtherDesc.required = false;
	                    dealOtherDesc.value = '';
	                }
	            }
	        }

	        // ModalDealFailed (quick mark as failed)
	        const modalReasonSelect = document.getElementById('failure-reason');
	        const modalOtherDiv = document.getElementById('failure-other-reason');
	        const modalOtherDesc = document.getElementById('failure-other-description');
	        if (modalReasonSelect && modalOtherDiv && modalOtherDesc) {
	            const reason = modalReasonSelect.value;
	            const shouldShow = !!reason;
	            modalOtherDiv.classList.toggle('hidden', !shouldShow);
	            modalOtherDesc.required = shouldShow;
	            if (!shouldShow) {
	                modalOtherDesc.value = '';
	            }
	        }
	    },

    populateActivityDealSelect(deals = [], selectedDealId = '', fallbackStage = '') {
        const select = document.getElementById('activity-deal-select');
        const hiddenDealId = document.getElementById('activity-deal-id');
        const stageDisplay = document.getElementById('activity-current-stage-display');
        const currentStageInput = document.getElementById('activity-current-stage');
        const pipelineSelect = document.getElementById('activity-virtual-stage');
        if (!select) return;

        const normalizedDeals = Array.isArray(deals) ? deals : [];
        // Cache for reuse when modal is opened بدون رفرش
        this.state.currentDeals = normalizedDeals;

        select.innerHTML = '<option value=\"\">-- انتخاب معامله مرتبط --</option>';
        normalizedDeals.forEach(d => {
            const option = document.createElement('option');
            const id = d.didar_deal_id || d.id || d.deal_id || '';
            option.value = id;
            option.dataset.stage = d.pipeline_stage || '';
            
            const isClosed = d.status === 'Won' || d.status === 'Lost';
            if (isClosed) {
                option.disabled = true;
                const statusText = d.status === 'Won' ? 'موفق' : 'ناموفق';
                option.textContent = `${d.title || 'معامله'} (${statusText}) - بسته شده`;
            } else {
                const stageLabel = option.dataset.stage ? ` | ${option.dataset.stage}` : '';
                option.textContent = `${d.title || 'معامله'}${stageLabel}`;
            }
            select.appendChild(option);
        });

        const updateStageDisplay = () => {
            const opt = select.selectedOptions[0];
            const stage = opt?.dataset.stage || fallbackStage || '';
            if (hiddenDealId) hiddenDealId.value = select.value || '';
            if (currentStageInput) currentStageInput.value = stage || 'keep_current';
            if (pipelineSelect) {
                pipelineSelect.value = stage || '';
            }
            if (stageDisplay) {
                stageDisplay.innerHTML = stage
                    ? `<span class=\"text-sm text-gray-200\">${stage}</span>`
                    : '<span class=\"text-sm text-gray-500\">مرحله تعیین نشده</span>';
            }
        };

        select.onchange = () => {
            updateStageDisplay();
            this.handleActivityDealSelection();
        };

        let presetId = selectedDealId || select.dataset.defaultSelected || '';
        if (!presetId && normalizedDeals.length === 1) {
            const d = normalizedDeals[0];
            const isClosed = d.status === 'Won' || d.status === 'Lost';
            if (!isClosed) {
                presetId = d.didar_deal_id || d.id || d.deal_id || '';
            }
        }
        if (presetId) {
            select.value = presetId;
        } else {
            select.value = '';
        }

        updateStageDisplay();
        this.handleActivityDealSelection();
    },
    
    // SAVE DEAL
    async saveDeal(e) {
        e.preventDefault();
        const form = e.target;
        const contactId = this.state.currentLead?.didar_contact_id || window.currentContactId || form.contact_id.value;
        const dealId = form.deal_id.value;
        if (dealId && this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!dealId && !this.canActionClient('deals.create')) {
            Swal.fire('خطا', 'دسترسی به ایجاد معامله ندارید', 'error');
            return;
        }
        if (dealId && !this.canActionClient('deals.edit')) {
            const hasAnyPaymentPermission =
                this.canActionClient('payments.create') ||
                this.canActionClient('payments.edit') ||
                this.canActionClient('payments.delete');
            if (!hasAnyPaymentPermission) {
                Swal.fire('خطا', 'دسترسی به ویرایش معامله ندارید', 'error');
                return;
            }
        }
        let dealStatus = form.deal_status.value;
        let pipelineStage = form.pipeline_stage?.value || '';

        // COMPREHENSIVE LOGGING FOR TROUBLESHOOTING DATA FRAGMENTATION
        console.log('[DEAL_SAVE] Starting deal save process', {
            timestamp: new Date().toISOString(),
            contactId: contactId,
            dealId: dealId,
            windowCurrentContactId: window.currentContactId,
            formContactId: form.contact_id?.value,
            formTitle: form.title?.value,
            dealStatus: dealStatus,
            pipelineStage: pipelineStage,
            isNewDeal: !dealId,
            formDataKeys: Array.from(new FormData(form).keys()),
            userAgent: navigator.userAgent,
            url: window.location.href,
            sessionStorage: Object.keys(sessionStorage),
            localStorage: Object.keys(localStorage)
        });

        // For existing deals, status and stage are ALWAYS required
        // No fallback to existing values - user must explicitly select new values

        const isNewDeal = !dealId;
        const productCheckboxes = form.querySelectorAll('input[name="product_ids[]"]:checked');
        let productIds = Array.from(productCheckboxes).map(cb => cb.value);
        
        if(!contactId) {
            Swal.fire('خطا', 'لطفاً شخص را انتخاب کنید', 'error');
            return;
        }
        
        if(!dealStatus) {
            Swal.fire('خطا', 'لطفاً وضعیت معامله را انتخاب کنید', 'error');
            return;
        }
        // Pipeline stage is only required if status is NOT lost
        if(!pipelineStage && dealStatus !== 'lost') {
            Swal.fire('خطا', 'لطفاً مرحله کاریز معامله را انتخاب کنید', 'error');
            return;
        }

        const stageMeansSuccess = (pipelineStage === 'deal_success' || pipelineStage === 'اتمام');
        const stageMeansFailed = (pipelineStage === 'deal_failed' || pipelineStage === 'معامله ناموفق');
        if (stageMeansSuccess) {
            form.deal_status.value = 'won';
            dealStatus = 'won';
        } else if (stageMeansFailed) {
            form.deal_status.value = 'lost';
            dealStatus = 'lost';
        }
        
        // If status is 'lost' and pipeline_stage is empty, automatically set it to 'معامله ناموفق'
        if (dealStatus === 'lost' && !pipelineStage) {
            pipelineStage = 'معامله ناموفق';
            if (form.pipeline_stage) {
                form.pipeline_stage.value = 'معامله ناموفق';
            }
        }

        // Enforce required deal info fields
        const requiredInfoFields = [
            { field: form.title, label: 'خدمات قابل ارائه' },
            { field: form.service_cost, label: 'هزینه خدمات' },
            { field: form.price_list_code, label: 'کد لیست قیمت' },
            { field: form.payable_amount, label: 'مبلغ قابل پرداخت' }
        ];
        for (const item of requiredInfoFields) {
            const val = item.field?.value ?? '';
            if (String(val).trim() === '') {
                Swal.fire('خطا', `${item.label} الزامی است`, 'error');
                if (item.field?.focus) item.field.focus();
                return;
            }
        }
        if (isNewDeal) {
            const dealSource = form.acquaintance_source?.value || '';
            if (!String(dealSource).trim()) {
                Swal.fire('خطا', 'شیوه آشنایی معامله الزامی است', 'error');
                form.acquaintance_source?.focus?.();
                return;
            }
        }

        const selectedTitle = form.title?.value?.trim() || '';
        const selectedTitleOption = form.title?.selectedOptions?.[0];
        const isGeneratedTitle =
            (selectedTitleOption && selectedTitleOption.dataset && selectedTitleOption.dataset.generated === '1') ||
            /^Ù…Ø¹Ø§Ù…Ù„Ù‡\s+\S+\s+\d{10,12}$/u.test(selectedTitle);
        if (isNewDeal && isGeneratedTitle) {
            Swal.fire('خطا', '\u0644\u0637\u0641\u0627\u064b \u062e\u062f\u0645\u0627\u062a \u0642\u0627\u0628\u0644 \u0627\u0631\u0627\u0626\u0647 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f', 'error');
            if (form.title?.focus) form.title.focus();
            return;
        }

        if (productIds.length === 0) {
            const linkedProductIdsRaw = String(selectedTitleOption?.dataset?.productIds || '').trim();
            if (linkedProductIdsRaw !== '') {
                productIds = linkedProductIdsRaw.split(',').map(v => v.trim()).filter(Boolean);
            } else if (selectedTitleOption?.dataset?.productId) {
                productIds = [selectedTitleOption.dataset.productId];
            }
        }

        const isPaymentHistoryMutation = this.state.savingPaymentHistoryMutation === true;

        // Collect payment fields ROW-BY-ROW so every parallel array stays aligned to the same
        // payment row. (Previously these were form-wide querySelectorAll lists indexed by i,
        // which got misaligned when rows had different visible fields — causing e.g. cheque
        // fields to be read from the wrong row / saved empty, and card-reader rows to wrongly
        // demand cheque fields.)
        // History mutations (delete, and similar cache-only operations) intentionally skip form
        // rows; they submit the already-mutated payment cache.
        const paymentRowEls = isPaymentHistoryMutation
            ? []
            : Array.from(form.querySelectorAll('#payment-rows > [data-existing-payment]'))
                .filter(row => row.offsetParent !== null);
        const __col = (sel) => paymentRowEls.map(r => r.querySelector(sel));
        const paymentDates = __col('input[name="payment_date[]"]');
        const paymentAmounts = __col('input[name="payment_amount[]"]');
        const paymentMethods = __col('select[name="payment_method[]"]');
        const paymentDescriptions = __col('textarea[name="payment_description[]"]');
        const paymentCardLast4s = __col('input[name="payment_card_last4[]"]');
        const paymentBanks = __col('select[name="destination_bank[]"]');
        const paymentBankOthers = __col('input[name="destination_bank_other[]"]');
        const paymentAccountOwners = __col('input[name="account_owner_name[]"]');
        const paymentDepositorNames = __col('input[name="depositor_name[]"]');
        const paymentDestinationCards = __col('input[name="destination_card_number[]"]');
        const paymentDestinationBankTitles = __col('input[name="destination_bank_title[]"]');
        const paymentDestinationCardFulls = __col('input[name="destination_card_full[]"]');
        const paymentDestinationAccounts = __col('input[name="destination_account_number[]"]');
        const paymentDestinationShebas = __col('input[name="destination_sheba[]"]');
        const paymentDestinationOwners = __col('input[name="destination_account_owner[]"]');
        const paymentCardReaderDevices = __col('select[name="card_reader_device[]"]');
        const paymentCurrencies = __col('select[name="payment_currency[]"]');
        const paymentCurrencyOthers = __col('input[name="payment_currency_other[]"]');
        const paymentCurrencyAmounts = __col('input[name="payment_currency_amount[]"]');
        const paymentCheckBanks = __col('input[name="check_bank_name[]"]');
        const paymentCheckDates = __col('input[name="check_date[]"]');
        const paymentCheckNumbers = __col('input[name="check_number[]"]');
        const paymentCheckOwnerNames = __col('input[name="check_owner_name[]"]');
        const paymentRegisteredAts = __col('input[name="payment_registered_at[]"]');
        const paymentDepositDates = __col('input[name="deposit_date[]"]');
        const paymentCheckDueDates = __col('input[name="check_due_date[]"]');
        const paymentSayadiDates = __col('input[name="sayadi_date[]"]');
        const paymentCheckDeliveryDates = __col('input[name="check_delivery_date[]"]');
        const paymentCheckRecipientNames = __col('input[name="check_recipient_name[]"]');
        const paymentReceiptImages = __col('input[name="receipt_image_path[]"]');
        const paymentCheckImages = __col('input[name="check_image_path[]"]');
        const paymentCheckSayadiImages = __col('input[name="check_sayadi_image_path[]"]');
        const paymentSequences = __col('input[name="payment_sequence[]"]');
        const paymentRefundAmounts = __col('input[name="payment_refund_amount[]"]');
        const paymentRefundDescriptions = __col('textarea[name="payment_refund_description[]"]');
        const paymentRefundCounts = __col('input[name="payment_refund_count[]"]');
        const paymentRegisteredByDidarIds = __col('input[name="payment_registered_by_didar_id[]"]');
        const paymentRegisteredByNames = __col('input[name="payment_registered_by_name[]"]');
        const paymentOwnerUserIds = __col('input[name="payment_owner_user_id[]"]');
        const paymentOwnerDidarIds = __col('input[name="payment_owner_didar_id[]"]');
        const paymentOwnerNameSnapshots = __col('input[name="payment_owner_name_snapshot[]"]');
        const paymentOwnerLockedAts = __col('input[name="payment_owner_locked_at[]"]');
        const paymentOwnerSources = __col('input[name="payment_owner_source[]"]');
        const successRequiredAmount = this.getDealSuccessRequiredPaymentAmount(form);
        const mustHavePayments = (dealStatus === 'won' || stageMeansSuccess) && successRequiredAmount > 0;
        const anyPaymentFilled = [
            ...paymentDates.map(i => i?.value),
            ...paymentAmounts.map(i => i?.value),
            ...paymentMethods.map(i => i?.value),
            ...paymentDescriptions.map(i => i?.value)
        ].some(v => String(v || '').trim() !== '');
        
        // Validate failure reason for lost deals
        if (dealStatus === 'lost') {
            const failureReason = form.failure_reason.value;
            if (!failureReason) {
                Swal.fire('خطا', 'لطفاً دلیل ناموفق بودن معامله را انتخاب کنید', 'error');
                return;
            }
            // برای همه دلایل، توضیحات اجباری است
            const otherDesc = form.failure_other_description.value;
            if (!otherDesc || !otherDesc.trim()) {
                Swal.fire('خطا', 'لطفاً توضیحات دلیل ناموفق بودن را وارد کنید', 'error');
                const descField = document.getElementById('deal-failure-other-description');
                if (descField) descField.focus();
                return;
            }
        }
        
        const paymentRows = paymentRowEls;
        const validatePaymentRow = (index, showErrors = true) => {
            const row = paymentRows[index];
            return this.getPaymentRowValidation(row, index, showErrors).complete;
        };
        const paymentRowStates = paymentRows.map((row, index) => this.getPaymentRowValidation(row, index, false));
        const activeDealTab = this.state.activeDealTab || '';
        const isPaymentTabSave = activeDealTab === 'payments';
        const hasCompletePaymentRows = paymentRowStates.some(state => state.complete);
        const hasIncompletePaymentRows = paymentRowStates.some(state => state.hasAnyValue && !state.complete);

        if (mustHavePayments) {
            if (paymentDates.length === 0 && !(this.state.lastPaymentsCache || []).length) {
                Swal.fire('خطا', 'لطفاً حداقل یک پرداخت را تکمیل کنید', 'error');
                return;
            }
            for (let i = 0; i < paymentRows.length; i++) {
                const state = this.getPaymentRowValidation(paymentRows[i], i, false);
                if (state.hasAnyValue && !validatePaymentRow(i)) return;
            }
        } else if (isPaymentTabSave && !isPaymentHistoryMutation && !hasCompletePaymentRows) {
            Swal.fire('خطا', 'لطفاً موارد الزامی فرم پرداخت را کامل کنید.', 'error');
            return;
        } else if (hasIncompletePaymentRows) {
            for (let i = 0; i < paymentRows.length; i++) {
                const state = this.getPaymentRowValidation(paymentRows[i], i, false);
                if (state.hasAnyValue && !validatePaymentRow(i)) return;
            }
        } else if (anyPaymentFilled) {
            for (let i = 0; i < paymentRows.length; i++) {
                if (!validatePaymentRow(i)) return;
            }
        } else if (dealStatus === 'won' && successRequiredAmount > 0 && !this.hasCompletePaymentsForSuccess()) {
            Swal.fire('خطا', 'برای ثبت معامله موفق، جزئیات پرداخت باید کامل باشد.', 'error');
            return;
        }
        
	        // Get discount occasion (use custom if other)
	        const hasDiscount = String(form.has_discount?.value || '0') === '1';
	        const discountType = form.discount_type?.value || '';
	        let discountOccasion = form.discount_occasion?.value || '';
	        if (discountOccasion === 'other') {
	            discountOccasion = form.discount_occasion_custom?.value || '';
	        }
	        let discountAmount = parseMoneyValue(form.discount_amount?.value || '');
	        const refundAmount = parseMoneyValue(form.refund_amount?.value || '');
	        const refundDescription = (form.refund_description?.value || '').trim();

	        if (hasDiscount && discountType === 'refund') {
	            if (refundAmount <= 0) {
	                Swal.fire('خطا', 'مبلغ مرجوعی الزامی است', 'error');
	                const el = document.getElementById('deal-refund-amount');
	                if (el) el.focus();
	                return;
	            }
	            if (!refundDescription) {
	                Swal.fire('خطا', 'توضیحات مرجوعی الزامی است', 'error');
	                const el = document.getElementById('deal-refund-description');
	                if (el) el.focus();
	                return;
	            }
	            // Refund is not a discount amount/occasion
	            discountOccasion = '';
	            discountAmount = 0;
	        } else if (!hasDiscount) {
	            discountOccasion = '';
	            discountAmount = 0;
	        }
        
	        // Collect visible payment form rows, then merge them with the cached payment history.
	        const formPayments = [];
	        
	        for (let i = 0; i < paymentDates.length; i++) {
	            const persianDate = paymentDates[i]?.value?.trim() || '';
	            const amountRaw = paymentAmounts[i]?.value?.trim() || '';
	            const method = paymentMethods[i]?.value || '';
	            const description = paymentDescriptions[i]?.value?.trim() || '';
            const cardLast4 = paymentCardLast4s[i]?.value?.trim() || '';
            const bankVal = paymentBanks[i]?.value?.trim() || '';
            const bankOtherVal = paymentBankOthers[i]?.value?.trim() || '';
            const destinationCardVal = paymentDestinationCards[i]?.value?.trim() || '';
            const destinationCardDigits = destinationCardVal.replace(/\D/g, '').slice(-4);
            const destinationBankTitleVal = paymentDestinationBankTitles[i]?.value?.trim() || '';
            const destinationCardFullVal = paymentDestinationCardFulls[i]?.value?.trim() || '';
            const destinationAccountVal = paymentDestinationAccounts[i]?.value?.trim() || '';
            const destinationShebaVal = paymentDestinationShebas[i]?.value?.trim() || '';
            const destinationOwnerVal = paymentDestinationOwners[i]?.value?.trim() || '';
            const cardReaderDeviceVal = paymentCardReaderDevices[i]?.value?.trim() || '';
            const currencyVal = paymentCurrencies[i]?.value?.trim() || '';
            const currencyOtherVal = paymentCurrencyOthers[i]?.value?.trim() || '';
            const currencyAmountVal = parseMoneyValue(paymentCurrencyAmounts[i]?.value || '');
            const checkBankVal = paymentCheckBanks[i]?.value?.trim() || '';
            const checkDateVal = paymentCheckDates[i]?.value?.trim() || '';
            const checkNumberVal = toEnglishDigits(String(paymentCheckNumbers[i]?.value || '')).replace(/\D/g, '');
            const checkOwnerVal = paymentCheckOwnerNames[i]?.value?.trim() || '';
            const accountOwnerVal = paymentAccountOwners[i]?.value?.trim() || '';
            const depositorNameVal = paymentDepositorNames[i]?.value?.trim() || '';
            const registeredAtVal = paymentRegisteredAts[i]?.value?.trim() || '';
            const depositDatePersian = paymentDepositDates[i]?.value?.trim() || '';
            const checkDueDatePersian = paymentCheckDueDates[i]?.value?.trim() || '';
            const sayadiDateVal = paymentSayadiDates[i]?.value?.trim() || '';
            const checkDeliveryDatePersian = paymentCheckDeliveryDates[i]?.value?.trim() || '';
            const checkRecipientVal = paymentCheckRecipientNames[i]?.value?.trim() || '';
            const receiptImageVal = paymentReceiptImages[i]?.value?.trim() || '';
            const checkImageVal = paymentCheckImages[i]?.value?.trim() || '';
            const checkSayadiImageVal = paymentCheckSayadiImages[i]?.value?.trim() || '';
            const paymentSequenceVal = parseInt(paymentSequences[i]?.value || '0', 10) || 0;
            const amount = parseMoneyValue(amountRaw);
            const refundAmountVal = Math.max(0, parseMoneyValue(paymentRefundAmounts[i]?.value || ''));
            const refundDescriptionVal = paymentRefundDescriptions[i]?.value?.trim() || '';
            const refundCountVal = parseInt(paymentRefundCounts[i]?.value || '0', 10) || 0;
            const registeredByDidarIdVal = paymentRegisteredByDidarIds[i]?.value?.trim() || '';
            const registeredByNameVal = paymentRegisteredByNames[i]?.value?.trim() || '';
            const paymentOwnerUserIdVal = paymentOwnerUserIds[i]?.value?.trim() || '';
            const paymentOwnerDidarIdVal = paymentOwnerDidarIds[i]?.value?.trim() || '';
            const paymentOwnerNameSnapshotVal = paymentOwnerNameSnapshots[i]?.value?.trim() || '';
            const paymentOwnerLockedAtVal = paymentOwnerLockedAts[i]?.value?.trim() || '';
            const paymentOwnerSourceVal = paymentOwnerSources[i]?.value?.trim() || '';
            if (refundAmountVal > amount) {
                Swal.fire('خطا', `مجموع مبلغ عودت داده شده نمی‌تواند از اصل مبلغ پرداخت بیشتر باشد (ردیف ${i + 1})`, 'error');
                paymentRefundAmounts[i]?.focus?.();
                return;
            }
            if ((refundAmountVal > 0 || refundCountVal > 0) && !refundDescriptionVal) {
                Swal.fire('خطا', `توضیحات عودت وجه الزامی است (ردیف ${i + 1})`, 'error');
                paymentRefundDescriptions[i]?.focus?.();
                return;
            }
            const toGregOrEmpty = (p) => {
                if (!p) return '';
                try { const g = persianToGregorian(p); return g ? g.split('T')[0] : ''; }
                catch (e) { return ''; }
            };
            // Card methods: enforce mandatory fields for NEW payment rows only (existing rows
            // may predate these rules — matches the server's $isEntryNew gating).
            const __rowEl = (paymentMethods[i] || paymentAmounts[i])?.closest('[data-existing-payment]');
            const __isExistingRow = __rowEl?.dataset?.existingPayment === '1';
            if (amount > 0 && !__isExistingRow && (this.isCardToCardMethod(method) || this.isShebaMethod(method) || this.isCardReaderMethod(method))) {
                if (!/^\d{4}$/.test(String(cardLast4).replace(/\D/g, ''))) {
                    Swal.fire('خطا', `وارد کردن ۴ رقم آخر کارت مبدا الزامی است (ردیف ${i + 1})`, 'error');
                    return;
                }
            }
            if (amount > 0 && !__isExistingRow && (this.isCardToCardMethod(method) || this.isShebaMethod(method)) && !bankVal) {
                Swal.fire('خطا', `انتخاب بانک مقصد الزامی است (ردیف ${i + 1})`, 'error');
                return;
            }
            if (amount > 0 && !__isExistingRow && this.isCardReaderMethod(method) && !cardReaderDeviceVal) {
                Swal.fire('خطا', `انتخاب دستگاه کارتخوان الزامی است (ردیف ${i + 1})`, 'error');
                return;
            }
            // C6: "سایر" destination bank requires destination number + account holder.
            if (amount > 0 && (this.isCardToCardMethod(method) || this.isShebaMethod(method)) && bankVal === '__other__') {
                if (!bankOtherVal || !accountOwnerVal) {
                    Swal.fire('خطا', `برای انتخاب «سایر» در بانک مقصد، شماره بانک مقصد و نام صاحب حساب الزامی است (ردیف ${i + 1})`, 'error');
                    return;
                }
            }
            // C7: cheque requires source bank, owner, number, Sayadi date. The cheque due date
            // (تاریخ سررسید) is captured by the top date field (persianDate) for cheques.
            if (amount > 0 && this.isCheckMethod(method)) {
                if (!checkBankVal || !checkOwnerVal || !/^\d{6}$/.test(checkNumberVal) || !sayadiDateVal || !persianDate || (!__isExistingRow && !checkImageVal) || (!__isExistingRow && !checkSayadiImageVal)) {
                    Swal.fire('خطا', `برای چک، نام بانک مبدا، نام صاحب چک، شماره چک ۶ رقمی، تاریخ صیادی، تاریخ سررسید چک، تصویر چک و تصویر ثبت چک صیادی الزامی است (ردیف ${i + 1})`, 'error');
                    return;
                }
            }
	            
	            // Only add payment if date and amount are provided
	            if (persianDate && amount > 0) {
	                let gregorianDate = '';
	                // Convert Persian date to Gregorian
	                if (persianDate) {
	                    try {
	                        const gDateStr = persianToGregorian(persianDate);
	                        if (gDateStr) {
	                            // Extract just the date part (YYYY-MM-DD)
	                            gregorianDate = gDateStr.split('T')[0];
	                        }
	                    } catch(e) {
	                        console.error('Error converting date:', e, 'Date:', persianDate);
	                    }
	                }
	                
	                formPayments.push({
	                    date: gregorianDate || '',
	                    date_persian: persianDate || '',
	                    amount,
                    method: method || '',
                    description: description || '',
                    card_last4: cardLast4 || '',
                    destination_bank: bankVal || '',
                    destination_bank_other: bankOtherVal || '',
                    account_owner_name: accountOwnerVal || '',
                    depositor_name: depositorNameVal || '',
                    destination_card_number: destinationCardDigits || '',
                    destination_card_number_other: '',
                    destination_bank_title: destinationBankTitleVal || '',
                    destination_card_full: destinationCardFullVal || '',
                    destination_account_number: destinationAccountVal || '',
                    destination_sheba: destinationShebaVal || '',
                    destination_account_owner: destinationOwnerVal || '',
                    card_reader_device: cardReaderDeviceVal || '',
                    currency: currencyVal || '',
                    currency_other: currencyOtherVal || '',
                    currency_amount: currencyAmountVal || '',
                    check_bank_name: checkBankVal || '',
                    check_date: checkDateVal || '',
                    check_number: checkNumberVal || '',
                    check_owner_name: checkOwnerVal || '',
                    registered_at: registeredAtVal || '',
                    deposit_date_persian: depositDatePersian || '',
                    deposit_date: toGregOrEmpty(depositDatePersian),
                    check_due_date_persian: this.isCheckMethod(method) ? (persianDate || '') : (checkDueDatePersian || ''),
                    check_due_date: this.isCheckMethod(method) ? (gregorianDate || toGregOrEmpty(persianDate)) : toGregOrEmpty(checkDueDatePersian),
                    sayadi_date: sayadiDateVal || '',
                    check_delivery_date_persian: checkDeliveryDatePersian || '',
                    check_delivery_date: toGregOrEmpty(checkDeliveryDatePersian),
                    check_recipient_name: checkRecipientVal || '',
                    receipt_image_path: this.isSudranGatewayMethod(method) ? '' : (receiptImageVal || ''),
                    check_image_path: checkImageVal || '',
                    check_sayadi_image_path: checkSayadiImageVal || '',
                    refund_amount: refundAmountVal || 0,
                    refund_description: refundDescriptionVal || '',
                    refund_count: refundCountVal || (refundAmountVal > 0 ? 1 : 0),
                    registered_by_didar_id: registeredByDidarIdVal || '',
                    registered_by_name: registeredByNameVal || '',
                    payment_owner_user_id: paymentOwnerUserIdVal || '',
                    payment_owner_didar_id: paymentOwnerDidarIdVal || registeredByDidarIdVal || '',
                    payment_owner_name_snapshot: paymentOwnerNameSnapshotVal || registeredByNameVal || '',
                    payment_owner_locked_at: paymentOwnerLockedAtVal || registeredAtVal || '',
                    payment_owner_source: paymentOwnerSourceVal || 'SESSION',
                    payment_sequence: paymentSequenceVal || ''
                });
            }
        }

        let payments = Array.isArray(this.state.lastPaymentsCache)
            ? JSON.parse(JSON.stringify(this.state.lastPaymentsCache))
            : [];
        if (this.state.editingPaymentIndex !== null && formPayments.length > 0) {
            payments[this.state.editingPaymentIndex] = formPayments[0];
        } else {
            payments = payments.concat(formPayments);
        }
        payments = this.normalizePaymentSequences(payments);

        // Calculate total payment amount
        const totalPaymentAmount = payments.reduce((sum, p) => sum + (this.isDeletedPayment(p) ? 0 : this.getPaymentNetAmount(p)), 0);
        if ((dealStatus === 'won' || stageMeansSuccess) && successRequiredAmount > 0 && totalPaymentAmount + 0.0001 < successRequiredAmount) {
            this.state.savingPaymentHistoryMutation = false;
            this.updatePaymentsSubmitState();
            Swal.fire('خطا', 'برای موفق شدن معامله، مجموع پرداخت‌ها باید حداقل برابر مبلغ قابل پرداخت باشد', 'error');
            return;
        }
        
        // Prepare failure reason fields
        let failureReasonCode = '';
        let failureReasonText = '';
        let failureReasonDescription = '';
        if (dealStatus === 'lost') {
            const selectedReason = form.failure_reason.value;
            failureReasonCode = selectedReason || '';
            failureReasonDescription = (form.failure_other_description.value || '').trim();
            const reasonText = {
                'no_financial_ability': 'عدم توان مالی',
                'no_purchase_intent': 'فعلا قصد خرید ندارد',
                'competitor_purchase': 'خرید از رقبا',
                'wrong_number': 'شماره اشتباه',
                'irrelevant_lead': 'لید نامرتبط',
                'other': 'سایر'
            };
            failureReasonText = reasonText[selectedReason] || '';
            if (!failureReasonCode) {
                Swal.fire('خطا', 'لطفاً دلیل ناموفق شدن را انتخاب کنید', 'error');
                return;
            }
            if (!failureReasonDescription) {
                Swal.fire('خطا', 'توضیحات دلیل ناموفق شدن الزامی است', 'error');
                return;
            }
        }
        
	        const data = {
	            deal_id: dealId || '',
	            contact_id: contactId,
	            deal_status: dealStatus, // pending, won, lost
	            pipeline_stage: pipelineStage,
	            product_ids: productIds.join(','),
	            title: form.title.value, // عنوان معامله = خدمات قابل ارائه
	            service_cost: parseMoneyValue(form.service_cost.value) || 0,
	            price_list_code: form.price_list_code.value,
	            has_discount: form.has_discount.value,
	            discount_type: discountType,
	            discount_occasion: discountOccasion,
	            discount_amount: discountAmount,
	            refund_amount: refundAmount,
	            refund_description: refundDescription,
	            payable_amount: parseMoneyValue(form.payable_amount.value) || 0,
	            payment_conditions: form.payment_conditions.value,
                acquaintance_source: form.acquaintance_source?.value || '',
                acquaintance_detail: form.acquaintance_detail?.value || '',
                content_topic: form.content_topic?.value || '',
	            payments: JSON.stringify(payments), // Convert to JSON string for FormData
	            payment_amount: totalPaymentAmount, // Total for backward compatibility
                replace_payments: this.state.activeDealTab === 'payments' ? '1' : '0',
                admin_unlock_paid_deal_fields: this.state.dealInfoAdminUnlocked ? '1' : '0',
	            failure_reason: failureReasonCode,
	            failure_reason_text: failureReasonText,
	            failure_other_description: failureReasonDescription
	        };

        // Include requested services from activities tab (hidden field JSON)
        const reqServicesHidden = document.getElementById('deal-activity-requested-services');
        if (reqServicesHidden && reqServicesHidden.value) {
            data.requested_services = reqServicesHidden.value;
        }

        // Include مالی fields (financial_level, asset_estimation, income_estimation, has_previous_purchase)
        const finLevel = document.getElementById('deal-activity-financial-level')?.value || '';
        const assetEst = document.getElementById('deal-activity-asset')?.value || '';
        const incomeEst = document.getElementById('deal-activity-income')?.value || '';
        const hasPurchase = document.getElementById('deal-activity-has-purchase')?.value || '';
        if (finLevel) data.financial_level = finLevel;
        if (assetEst) data.asset_estimation = assetEst;
        if (incomeEst) data.income_estimation = incomeEst;
        if (hasPurchase !== '') data.has_previous_purchase = hasPurchase;

        const titleVal = form.title.value?.trim();
        data.requested_services = titleVal ? JSON.stringify([titleVal]) : '[]';
        if (reqServicesHidden) reqServicesHidden.value = data.requested_services;
        
        console.log('Sending payments to backend:', payments);

        // COMPREHENSIVE LOGGING BEFORE SENDING DEAL SAVE REQUEST
        console.log('[DEAL_SAVE_REQUEST] Sending deal save request to backend', {
            timestamp: new Date().toISOString(),
            data: data,
            contactId: data.contact_id,
            dealId: data.deal_id,
            title: data.title,
            windowCurrentContactId: window.currentContactId,
            paymentsCount: Array.isArray(data.payments) ? data.payments.length : 0,
            formTitle: form.title?.value,
            formContactId: form.contact_id?.value,
            url: window.location.href,
            sessionId: this.getSessionId ? this.getSessionId() : 'unknown'
        });

        const wasPaymentHistoryMutation = this.state.savingPaymentHistoryMutation === true;
        const wasPaymentTabSave = this.state.activeDealTab === 'payments';
        const wasEditingPayment = this.state.editingPaymentIndex !== null;
        Swal.showLoading();
        let res;
        try {
            res = await this.req('save_deal', data);
        } catch (err) {
            console.error('saveDeal failed', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
            return;
        }
        this.closeBlockingSwal();

        // LOGGING DEAL SAVE RESPONSE
        console.log('[DEAL_SAVE_RESPONSE] Deal save response received', {
            timestamp: new Date().toISOString(),
            status: res.status,
            message: res.message,
            deal_id: res.deal_id,
            contact_id: data.contact_id,
            original_deal_id: data.deal_id,
            success: res.status === 'success'
        });

        if(res.status === 'success') {
            this.state.savingPaymentHistoryMutation = false;
            this.state.editingPaymentIndex = null;
            if (wasPaymentHistoryMutation || wasPaymentTabSave) {
                if (this.state.currentDeal) {
                    this.state.currentDeal = {
                        ...this.state.currentDeal,
                        payments,
                        payment_amount: totalPaymentAmount
                    };
                }
                this.state.lastPaymentsCache = JSON.parse(JSON.stringify(payments));
                const paymentRowsContainer = document.getElementById('payment-rows');
                if (paymentRowsContainer) {
                    this.cleanupPersianDatepickers(paymentRowsContainer);
                    paymentRowsContainer.innerHTML = '';
                }
                this.setPaymentFormTitle('ثبت پرداخت جدید');
                this.renderExistingPayments();
                this.showDealFormTab('payments');
                this.setPaymentFormVisible(false);
                const paymentToastTitle = wasPaymentHistoryMutation
                    ? 'پرداخت حذف شد'
                    : (wasEditingPayment ? 'پرداخت ویرایش شد' : 'پرداخت ثبت شد');
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title: paymentToastTitle, showConfirmButton:false, timer:2000});
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }
                return;
            }

            Swal.fire({toast:true, position:'bottom-end', icon:'success', title: dealId ? 'معامله به‌روزرسانی شد' : 'معامله ثبت شد', showConfirmButton:false, timer:2000});
            
            // Update deal-id if it was a new deal
            const currentDealId = document.getElementById('deal-id').value;
            if (!currentDealId && res.deal_id) {
                document.getElementById('deal-id').value = res.deal_id;
            }
            
            // Close modal and reload
            this.closeModal('modalDeal');
            if (window.currentContactId) {
                await this.openPersonProfile(window.currentContactId);
            }
            if (document.getElementById('deals-container')) {
                await this.loadDeals();
            }
        } else {
            this.state.savingPaymentHistoryMutation = false;
            const mismatchMsg = 'شناسه معامله با لید انتخاب‌شده مطابقت ندارد';
            if ((res.message || '').includes(mismatchMsg)) {
                console.warn('[DEAL_SAVE_BLOCKED] Deal/contact mismatch detected; clearing deal context', {
                    timestamp: new Date().toISOString(),
                    contact_id: data.contact_id,
                    deal_id: data.deal_id,
                    message: res.message
                });
                const dealIdInput = document.getElementById('deal-id');
                if (dealIdInput) dealIdInput.value = '';
                const dealActDeal = document.getElementById('deal-activity-deal-id');
                if (dealActDeal) dealActDeal.value = '';
                const activityDealId = document.getElementById('activity-deal-id');
                if (activityDealId) activityDealId.value = '';
                this.state.currentDeal = null;
            }
            console.error('[DEAL_SAVE_ERROR] Deal save failed', {
                timestamp: new Date().toISOString(),
                error: res.message,
                status: res.status,
                contact_id: data.contact_id,
                deal_id: data.deal_id,
                title: data.title,
                fullResponse: res
            });
            Swal.fire('خطا', res.message, 'error');
        }
    },

    saveDealFromTab(tab) {
        const form = document.getElementById('deal-form');
        if (!form) return;
        this.state.activeDealTab = tab;
        return this.saveDeal({ preventDefault: () => {}, target: form });
    },
    
    // MARK DEAL AS SUCCESS
    async markDealAsSuccess() {
        const dealId = document.getElementById('deal-id').value;
        const contactId = document.getElementById('deal-contact-id').value;
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        
        if (!dealId) {
            Swal.fire('خطا', 'لطفاً ابتدا معامله را ذخیره کنید', 'error');
            return;
        }
        
        Swal.fire({
            title: 'تایید معامله موفق',
            text: 'آیا از موفق بودن این معامله اطمینان دارید؟',
            icon: 'question',
            showCancelButton: true,
            confirmButtonText: 'بله، معامله موفق است',
            cancelButtonText: 'انصراف',
            confirmButtonColor: '#10b981'
        }).then(async (result) => {
            if (result.isConfirmed) {
                Swal.showLoading();
                let res;
                try {
                    res = await this.req('set_deal_status', {
                        deal_id: dealId,
                        status: '1', // Won
                        contact_id: contactId
                    });
                } catch (err) {
                    console.error('markDealWon failed', err);
                    this.closeBlockingSwal();
                    Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
                    return;
                }
                this.closeBlockingSwal();
                
                if (res.status === 'success') {
                    Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'معامله به عنوان موفق ثبت شد', showConfirmButton:false, timer:2000});
                    this.closeModal('modalDeal');
                    if (window.currentContactId) {
                        this.openPersonProfile(window.currentContactId);
                    }
                    if (document.getElementById('deals-container')) {
                        this.loadDeals();
                    }
                } else {
                    Swal.fire('خطا', res.message, 'error');
                }
            }
        });
    },
    
    // MARK DEAL AS FAILED
    markDealAsFailed() {
        const dealId = document.getElementById('deal-id').value;
        const contactId = document.getElementById('deal-contact-id').value;
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        
        if (!dealId) {
            Swal.fire('خطا', 'لطفاً ابتدا معامله را ذخیره کنید', 'error');
            return;
        }
        
        document.getElementById('failed-deal-id').value = dealId;
        document.getElementById('failed-contact-id').value = contactId;
        document.getElementById('failure-reason').value = '';
        document.getElementById('failure-other-description').value = '';
        document.getElementById('failure-other-reason').classList.add('hidden');
        this.openModal('modalDealFailed');
    },
    
	    
    // CONFIRM DEAL FAILED
    async confirmDealFailed(e) {
        e.preventDefault();
        
        const dealId = document.getElementById('failed-deal-id').value;
        const contactId = document.getElementById('failed-contact-id').value;
        const reason = document.getElementById('failure-reason').value;
        const otherDesc = document.getElementById('failure-other-description').value;
        
	        // Map reason to Persian text
	        const reasonText = {
            'no_financial_ability': 'عدم توان مالی',
            'no_purchase_intent': 'فعلا قصد خرید ندارد',
            'competitor_purchase': 'خرید از رقبا',
            'wrong_number': 'شماره اشتباه',
            'irrelevant_lead': 'لید نامرتبط',
            'other': 'سایر'
        };
        
        if (!reason) {
            Swal.fire('خطا', 'لطفاً دلیل ناموفق شدن را انتخاب کنید', 'error');
            return;
        }
        // For consistency with deal form, always require description
        if (!otherDesc.trim()) {
            Swal.fire('خطا', 'لطفاً توضیحات را وارد کنید', 'error');
            return;
        }

        Swal.showLoading();
        let res;
        try {
            res = await this.req('set_deal_status', {
                deal_id: dealId,
                status: '2', // Lost
                contact_id: contactId,
                failure_reason: reason,
                failure_reason_text: reasonText[reason] || '',
                failure_other_description: otherDesc
            });
        } catch (err) {
            console.error('markDealLost failed', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
            return;
        }
        this.closeBlockingSwal();
        
        if (res.status === 'success') {
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'معامله به عنوان ناموفق ثبت شد', showConfirmButton:false, timer:2000});
            this.closeModal('modalDealFailed');
            this.closeModal('modalDeal');
            if (window.currentContactId) {
                this.openPersonProfile(window.currentContactId);
            }
            if (document.getElementById('deals-container')) {
                this.loadDeals();
            }
        } else {
            Swal.fire('خطا', res.message, 'error');
        }
    },
    
    // DELETE DEAL (admin only)
    async deleteDeal(dealId) {
        if (!this.canActionClient('deals.delete')) {
            Swal.fire('خطا', 'دسترسی به حذف معامله ندارید', 'error');
            return;
        }
        
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }
        
        const result = await Swal.fire({
            title: 'آیا مطمئن هستید؟',
            text: 'این معامله به طور دائم حذف خواهد شد',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            cancelButtonColor: '#3085d6',
            confirmButtonText: 'بله، حذف کن',
            cancelButtonText: 'لغو',
            reverseButtons: true
        });
        
        if (!result.isConfirmed) {
            return;
        }
        
        Swal.fire({
            title: 'در حال حذف...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('delete_deal', { deal_id: dealId });
            Swal.close();
            
            if (res.status === 'success') {
                Swal.fire('موفق', res.message || 'معامله با موفقیت حذف شد', 'success');
                
                // Refresh deals list
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }
                
                // Refresh person profile if open
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در حذف معامله', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در حذف معامله: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // REACTIVATE DEAL (admin only)
    async reactivateDeal(dealId) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند معاملات بسته شده را فعال کند', 'error');
            return;
        }

        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        // نمایش modal برای انتخاب مرحله کاریز جدید
        const { value: formData } = await Swal.fire({
            title: 'فعال‌سازی مجدد معامله',
            html: `
                <div class="text-right mb-4">
                    <p class="text-sm text-gray-600 mb-3">مرحله کاریز جدید را انتخاب کنید:</p>
                    <select id="reactivate-stage" class="swal2-input w-full text-right">
                        ${this.buildReactivationStageSelectHtml()}
                    </select>
                </div>
                <div class="text-right">
                    <label class="block text-sm text-gray-600 mb-2">دلیل فعال‌سازی مجدد (اختیاری):</label>
                    <textarea id="reactivate-reason" class="swal2-textarea w-full text-right" placeholder="توضیح دهید چرا این معامله را فعال می‌کنید..." rows="3"></textarea>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'فعال‌سازی',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#10b981',
            cancelButtonColor: '#6b7280',
            preConfirm: () => {
                const stage = document.getElementById('reactivate-stage').value;
                const reason = document.getElementById('reactivate-reason').value;

                if (!stage) {
                    Swal.showValidationMessage('مرحله کاریز جدید را انتخاب کنید');
                    return false;
                }

                return { stage, reason };
            }
        });

        if (!formData) return;

        // نمایش loading
        Swal.fire({
            title: 'در حال فعال‌سازی...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('deal/reactivate', {
                deal_id: dealId,
                new_pipeline_stage: formData.stage,
                reactivation_reason: formData.reason
            });
            Swal.close();

            if (res.status === 'success') {
                Swal.fire({
                    title: 'موفق',
                    text: res.message || 'معامله با موفقیت فعال شد',
                    icon: 'success',
                    confirmButtonText: 'باشه'
                });

                // بروزرسانی لیست معاملات
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }

                // بروزرسانی پروفایل شخص اگر باز است
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در فعال‌سازی معامله', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در فعال‌سازی معامله: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // DELETE DEAL FROM MODAL (admin only)
    async deleteDealFromModal() {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        const dealId = document.getElementById('deal-id').value;
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        const result = await Swal.fire({
            title: 'آیا مطمئن هستید؟',
            text: 'این معامله به طور دائم حذف خواهد شد و قابل بازیابی نیست',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            cancelButtonColor: '#6b7280',
            confirmButtonText: 'بله، حذف کن',
            cancelButtonText: 'لغو',
            reverseButtons: true
        });

        if (!result.isConfirmed) {
            return;
        }

        Swal.fire({
            title: 'در حال حذف...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('delete_deal', { deal_id: dealId });
            Swal.close();

            if (res.status === 'success') {
                Swal.fire({
                    title: 'موفق',
                    text: res.message || 'معامله با موفقیت حذف شد',
                    icon: 'success',
                    confirmButtonText: 'باشه'
                });

                // بستن modal و بروزرسانی لیست‌ها
                this.closeModal('modalDeal');
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در حذف معامله', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در حذف معامله: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // REACTIVATE DEAL FROM MODAL (admin only)
    async reactivateDealFromModal() {
        const dealId = document.getElementById('deal-id').value;
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        // نمایش modal انتخاب مرحله کاریز
        const { value: formData } = await Swal.fire({
            title: 'فعال‌سازی مجدد معامله',
            html: `
                <div class="text-right mb-4">
                    <p class="text-sm text-gray-600 mb-3">مرحله کاریز جدید را انتخاب کنید:</p>
                    <select id="reactivate-stage" class="swal2-input w-full text-right">
                        ${this.buildReactivationStageSelectHtml()}
                    </select>
                </div>
                <div class="text-right">
                    <label class="block text-sm text-gray-600 mb-2">دلیل فعال‌سازی مجدد (اختیاری):</label>
                    <textarea id="reactivate-reason" class="swal2-textarea w-full text-right" placeholder="توضیح دهید چرا این معامله را فعال می‌کنید..." rows="3"></textarea>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'فعال‌سازی',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#10b981',
            cancelButtonColor: '#6b7280',
            preConfirm: () => {
                const stage = document.getElementById('reactivate-stage').value;
                const reason = document.getElementById('reactivate-reason').value;

                if (!stage) {
                    Swal.showValidationMessage('مرحله کاریز جدید را انتخاب کنید');
                    return false;
                }

                return { stage, reason };
            }
        });

        if (!formData) return;

        // نمایش loading
        Swal.fire({
            title: 'در حال فعال‌سازی...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('deal/reactivate', {
                deal_id: dealId,
                new_pipeline_stage: formData.stage,
                reactivation_reason: formData.reason
            });
            Swal.close();

            if (res.status === 'success') {
                Swal.fire({
                    title: 'موفق',
                    text: res.message || 'معامله با موفقیت فعال شد',
                    icon: 'success',
                    confirmButtonText: 'باشه'
                });

                // بروزرسانی وضعیت در modal و لیست‌ها
                this.closeModal('modalDeal');
                if (document.getElementById('deals-container')) {
                    this.loadDeals();
                }
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در فعال‌سازی معامله', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در فعال‌سازی معامله: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // REACTIVATE DEAL FROM PROFILE (admin only)
    async reactivateDealFromProfile(dealId) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند معاملات بسته شده را فعال کند', 'error');
            return;
        }

        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        // نمایش modal انتخاب مرحله کاریز
        const { value: formData } = await Swal.fire({
            title: 'فعال‌سازی مجدد معامله',
            html: `
                <div class="text-right mb-4">
                    <p class="text-sm text-gray-600 mb-3">مرحله کاریز جدید را انتخاب کنید:</p>
                    <select id="reactivate-stage" class="swal2-input w-full text-right">
                        ${this.buildReactivationStageSelectHtml()}
                    </select>
                </div>
                <div class="text-right">
                    <label class="block text-sm text-gray-600 mb-2">دلیل فعال‌سازی مجدد (اختیاری):</label>
                    <textarea id="reactivate-reason" class="swal2-textarea w-full text-right" placeholder="توضیح دهید چرا این معامله را فعال می‌کنید..." rows="3"></textarea>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'فعال‌سازی',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#10b981',
            cancelButtonColor: '#6b7280',
            preConfirm: () => {
                const stage = document.getElementById('reactivate-stage').value;
                const reason = document.getElementById('reactivate-reason').value;

                if (!stage) {
                    Swal.showValidationMessage('مرحله کاریز جدید را انتخاب کنید');
                    return false;
                }

                return { stage, reason };
            }
        });

        if (!formData) return;

        // نمایش loading
        Swal.fire({
            title: 'در حال فعال‌سازی...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('deal/reactivate', {
                deal_id: dealId,
                new_pipeline_stage: formData.stage,
                reactivation_reason: formData.reason
            });
            Swal.close();

            if (res.status === 'success') {
                Swal.fire({
                    title: 'موفق',
                    text: res.message || 'معامله با موفقیت فعال شد',
                    icon: 'success',
                    confirmButtonText: 'باشه'
                });

                // بروزرسانی پروفایل شخص
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در فعال‌سازی معامله', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در فعال‌سازی معامله: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    async forceSuccessExitKarizFromModal() {
        const dealId = document.getElementById('deal-id')?.value || '';
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        const currentDeal = this.state.currentDeal || {};
        const payableAmount = Number(currentDeal.payable_amount || 0);
        const paymentAmount = Number(currentDeal.payment_amount || 0);
        const outstandingAmount = Math.max(payableAmount - paymentAmount, 0);
        if (currentDeal.status !== 'Won') {
            Swal.fire('خطا', 'این عملیات فقط برای معاملات موفق مجاز است', 'error');
            return;
        }
        if (outstandingAmount > 0.0001) {
            Swal.fire('خطا', 'برای معامله‌ای که مجموع پرداخت‌های آن کمتر از مبلغ قابل پرداخت است، این عملیات مجاز نیست', 'error');
            return;
        }

        const result = await Swal.fire({
            title: 'تایید موفق و خروج از کاریز',
            text: 'این عملیات وضعیت معامله را موفق، مرحله کاریز را اتمام و مخاطب را از کاریز خارج می‌کند.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'اجرا',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#10b981'
        });
        if (!result.isConfirmed) {
            return;
        }

        Swal.fire({
            title: 'در حال اعمال...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await this.req('deal/force_success_exit_kariz', { deal_id: dealId });
            Swal.close();

            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطا در اعمال عملیات', 'error');
                return;
            }

            Swal.fire('موفق', res.message || 'عملیات با موفقیت انجام شد', 'success');
            await this.openDealModal(dealId);
            if (document.getElementById('deals-container')) {
                this.loadDeals();
            }
            if (window.currentContactId) {
                this.openPersonProfile(window.currentContactId);
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در اعمال عملیات: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // DELETE ACTIVITY (admin only)
    async deleteActivity(activityId) {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند فعالیت‌ها را حذف کند', 'error');
            return;
        }
        
        if (!activityId) {
            Swal.fire('خطا', 'شناسه فعالیت مشخص نشده است', 'error');
            return;
        }
        
        const result = await Swal.fire({
            title: 'آیا مطمئن هستید؟',
            text: 'این فعالیت به طور دائم حذف خواهد شد',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            cancelButtonColor: '#3085d6',
            confirmButtonText: 'بله، حذف کن',
            cancelButtonText: 'لغو',
            reverseButtons: true
        });
        
        if (!result.isConfirmed) {
            return;
        }
        
        Swal.fire({
            title: 'در حال حذف...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('delete_activity', { activity_id: activityId });
            Swal.close();
            
            if (res.status === 'success') {
                Swal.fire('موفق', res.message || 'فعالیت با موفقیت حذف شد', 'success');
                
                // Refresh person profile if open
                if (window.currentContactId) {
                    this.openPersonProfile(window.currentContactId);
                }
                
                // Refresh activities modal if open
                const activitiesModal = document.getElementById('modalDidarActivities');
                if (activitiesModal && !activitiesModal.classList.contains('hidden')) {
                    this.viewDidarActivities(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در حذف فعالیت', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در حذف فعالیت: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    updateDealModalSummary(person = null, deal = null) {
        const fill = (id, value) => {
            const el = document.getElementById(id);
            if (el) el.textContent = value || '-';
        };
        const stage = deal?.pipeline_stage || document.getElementById('deal-pipeline-stage')?.value || '';
        const statusLabel = (person && typeof person.sale_status !== 'undefined')
            ? (String(person.sale_status) === '1' ? 'موفق' : 'ناموفق')
            : '-';

        fill('deal-summary-name', person ? `${person.first_name || ''} ${person.last_name || ''}`.trim() || (deal?.contact_name || 'بدون نام') : (deal?.contact_name || 'بدون نام'));
        fill('deal-summary-owner', person?.owner_name || deal?.owner_name || this.state.currentLead?.owner_name || APP_USER.name || '---');
        fill('deal-summary-prev-owner', person?.previous_owner_name || '-');
        fill('deal-summary-sale-status', statusLabel);
        fill('deal-summary-mobile', person?.mobile_phone || deal?.contact_mobile || '-');
        fill('deal-summary-mobile2', person?.secondary_mobile_phone || '-');
        fill('deal-summary-mobile3', person?.mobile_phone_3 || '-');
        fill('deal-summary-mobile4', person?.mobile_phone_4 || '-');
        fill('deal-summary-city', person?.city || '-');
        fill('deal-summary-job', person?.job_title || '-');
        fill('deal-summary-didar-id', deal?.didar_deal_id || deal?.id || person?.didar_contact_id || '-');
        fill('deal-summary-didar-id-box', deal?.didar_deal_id || deal?.id || person?.didar_contact_id || '-');
        const createdByWrap = document.getElementById('deal-summary-created-by-wrap');
        const createdViaWrap = document.getElementById('deal-summary-created-via-wrap');
        const createdByValue = String(deal?.created_by_name || '').trim();
        const createdViaValue = this.translateCreatedVia(deal?.created_via || '');
        fill('deal-summary-created-by', createdByValue || '-');
        fill('deal-summary-created-via', createdViaValue || '-');
        if (createdByWrap) {
            createdByWrap.classList.toggle('hidden', !(this.isAdmin() && createdByValue));
        }
        if (createdViaWrap) {
            createdViaWrap.classList.toggle('hidden', !(this.isAdmin() && createdViaValue));
        }

        const stageBadge = document.getElementById('deal-summary-stage');
        if (stageBadge) {
            stageBadge.textContent = stage || 'مرحله تعیین نشده';
        }

        this.syncParkedDealUi(deal);
        this.updateDealFirstPaymentTiming();
        this.updateDealCurrentFields(stage, deal?.status);
    },

    parseDealTimingDate(value, fallbackTime = '00:00') {
        const raw = this.toEnglishDigits(String(value || '').trim());
        if (!raw) return null;
        const normalized = raw.replace(/-/g, '/');
        const jalaliMatch = normalized.match(/^(1[34]\d{2}|15\d{2})\/(\d{1,2})\/(\d{1,2})(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?/);
        if (jalaliMatch) {
            const datePart = `${jalaliMatch[1]}/${String(jalaliMatch[2]).padStart(2, '0')}/${String(jalaliMatch[3]).padStart(2, '0')}`;
            const timePart = jalaliMatch[4] || fallbackTime;
            const converted = persianToGregorian(datePart, timePart);
            const parsed = converted ? parseAppDateValue(converted) : null;
            return parsed && !isNaN(parsed.getTime()) ? parsed : null;
        }
        const parsed = parseAppDateValue(raw);
        return parsed && !isNaN(parsed.getTime()) ? parsed : null;
    },

    getDealRegisterTimingDate(deal = null) {
        const source = deal || this.state.currentDeal || {};
        return this.parseDealTimingDate(
            source.register_time ||
            source.register_time_jalali ||
            source.deal_register_time ||
            source.deal_register_date ||
            source.DealRegisterTime ||
            source.DealRegisterDate ||
            source.RegisterTime ||
            source.RegisterDate ||
            source.created_at ||
            ''
        );
    },

    getPaymentTimingDate(payment = {}) {
        return this.parseDealTimingDate(
            payment.date ||
            payment.payment_date ||
            payment.date_persian ||
            payment.payment_date_persian ||
            payment.registered_at ||
            payment.created_at ||
            ''
        );
    },

    getFirstPaymentTiming(payments = []) {
        return (Array.isArray(payments) ? payments : [])
            .filter(payment => !this.isDeletedPayment(payment))
            .map(payment => ({ payment, date: this.getPaymentTimingDate(payment) }))
            .filter(item => item.date && !isNaN(item.date.getTime()))
            .sort((a, b) => a.date.getTime() - b.date.getTime())[0] || null;
    },

    formatDealFirstPaymentDelay(registerDate, firstPaymentDate) {
        if (!registerDate || !firstPaymentDate) return '-';
        const dayMs = 24 * 60 * 60 * 1000;
        const startOfDay = date => new Date(date.getFullYear(), date.getMonth(), date.getDate());
        const diffDays = Math.max(0, Math.round((startOfDay(firstPaymentDate) - startOfDay(registerDate)) / dayMs));
        return diffDays === 0 ? 'همان روز' : `${this.formatNumber(diffDays)} روز`;
    },

    updateDealFirstPaymentTiming() {
        const card = document.getElementById('deal-first-payment-delay-card');
        const valueEl = document.getElementById('deal-first-payment-delay-value');
        const detailEl = document.getElementById('deal-first-payment-delay-detail');
        if (!card || !valueEl || !detailEl) return;

        card.classList.remove('border-emerald-500/40', 'bg-emerald-950/20', 'border-amber-500/40', 'bg-amber-950/20');
        valueEl.className = 'text-sm text-gray-100 font-bold';

        const registerDate = this.getDealRegisterTimingDate();
        const payments = this.state.lastPaymentsCache || [];
        if (!payments.length) {
            valueEl.textContent = 'بدون پرداخت';
            valueEl.classList.add('text-gray-300');
            detailEl.textContent = registerDate ? `ثبت معامله: ${toPersianDate(registerDate)}` : 'تاریخ ثبت معامله نامشخص است';
            return;
        }

        const firstPayment = this.getFirstPaymentTiming(payments);
        if (!registerDate || !firstPayment) {
            valueEl.textContent = 'نامشخص';
            valueEl.classList.add('text-amber-300');
            detailEl.textContent = !registerDate ? 'تاریخ ثبت معامله قابل محاسبه نیست' : 'تاریخ اولین پرداخت قابل محاسبه نیست';
            card.classList.add('border-amber-500/40', 'bg-amber-950/20');
            return;
        }

        valueEl.textContent = this.formatDealFirstPaymentDelay(registerDate, firstPayment.date);
        valueEl.classList.add('text-emerald-300');
        detailEl.textContent = `ثبت معامله: ${toPersianDate(registerDate)} | اولین پرداخت: ${toPersianDate(firstPayment.date)}`;
        card.classList.add('border-emerald-500/40', 'bg-emerald-950/20');
    },

    syncParkedDealUi(deal = null) {
        const isParked = String(deal?.pipeline_bucket || 'active') === 'parked';
        const parkCount = parseInt(deal?.park_count ?? 0, 10);
        const parkedBadge = document.getElementById('deal-parked-badge');
        const parkHistoryBadge = document.getElementById('deal-park-history-badge');
        const parkCountLabel = document.getElementById('deal-park-count-label');
        const moveToParkedBtn = document.getElementById('move-to-parked-btn');
        const moveOutOfParkedBtn = document.getElementById('move-out-of-parked-btn');
        const stageSelect = document.getElementById('deal-pipeline-stage');

        if (parkedBadge) {
            parkedBadge.classList.toggle('hidden', !isParked);
        }

        // Admin-only park history badge (visible when deal has been parked at least once)
        if (parkHistoryBadge) {
            const showHistory = this.isAdmin() && parkCount > 0;
            parkHistoryBadge.classList.toggle('hidden', !showHistory);
            if (parkCountLabel && showHistory) {
                parkCountLabel.textContent = `×${this.formatNumber(parkCount)}`;
            }
        }

        if (moveToParkedBtn) {
            const canShow = this.isAdmin() && !!(deal?.didar_deal_id || deal?.id) && !isParked && deal?.status === 'Pending';
            moveToParkedBtn.style.display = canShow ? 'inline-block' : 'none';
        }
        if (moveOutOfParkedBtn) {
            const canShow = this.isAdmin() && !!(deal?.didar_deal_id || deal?.id) && isParked;
            moveOutOfParkedBtn.style.display = canShow ? 'inline-block' : 'none';
        }

        // Stage select: always enabled — agents can change stage of parked deals (auto-unparks on save)
        if (stageSelect) {
            stageSelect.disabled = false;
            stageSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
        }

        // Show/hide the "changing stage will unpark this deal" note
        let unParkNote = document.getElementById('parked-stage-change-note');
        if (isParked && stageSelect) {
            if (!unParkNote) {
                unParkNote = document.createElement('div');
                unParkNote.id = 'parked-stage-change-note';
                unParkNote.className = 'mt-1 text-[10px] text-amber-400/80 flex items-center gap-1';
                unParkNote.innerHTML = '<i class="fa-solid fa-circle-info text-[9px]"></i>با تغییر مرحله، معامله از پارک خارج می‌شود';
                stageSelect.parentNode?.appendChild(unParkNote);
            }
            unParkNote.classList.remove('hidden');
        } else if (unParkNote) {
            unParkNote.classList.add('hidden');
        }
    },

    async moveCurrentDealToParked() {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند معامله را پارک کند', 'error');
            return;
        }
        const dealId = this.state.currentDeal?.didar_deal_id || document.getElementById('deal-id')?.value || '';
        if (!dealId) {
            Swal.fire('خطا', 'ابتدا معامله را ذخیره کنید', 'error');
            return;
        }

        const { value: reason } = await Swal.fire({
            title: 'انتقال معامله به پارک‌شده',
            input: 'textarea',
            inputLabel: 'توضیح (اختیاری)',
            inputPlaceholder: 'در صورت نیاز دلیل انتقال را بنویسید...',
            showCancelButton: true,
            confirmButtonText: 'انتقال',
            cancelButtonText: 'لغو'
        });
        if (reason === undefined) return;

        const res = await this.req('deal/move_to_parked', {
            deal_id: dealId,
            parked_reason: reason || ''
        });
        if (res.status === 'success') {
            Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: res.message || 'معامله پارک شد', showConfirmButton: false, timer: 2000 });
            await this.openDealModal(dealId);
            if (document.getElementById('deals-container')) this.loadDeals();
            if (document.getElementById('dashboard-content')) this.loadDashboard();
        } else {
            Swal.fire('خطا', res.message || 'خطا در انتقال معامله', 'error');
        }
    },

    async moveCurrentDealOutOfParked() {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند معامله را به کاریز برگرداند', 'error');
            return;
        }
        const dealId = this.state.currentDeal?.didar_deal_id || document.getElementById('deal-id')?.value || '';
        const currentStage = this.state.currentDeal?.parked_from_stage || this.state.currentDeal?.pipeline_stage || '';
        if (!dealId) {
            Swal.fire('خطا', 'شناسه معامله مشخص نشده است', 'error');
            return;
        }

        const { value: stage } = await Swal.fire({
            title: 'بازگشت معامله به کاریز',
            html: `
                <div class="text-right mb-3">
                    <p class="text-sm text-gray-600 mb-3">مرحله مقصد را انتخاب کنید:</p>
                    <select id="parked-restore-stage" class="swal2-input w-full text-right">
                        ${this.buildReactivationStageSelectHtml()}
                    </select>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'بازگشت',
            cancelButtonText: 'لغو',
            didOpen: () => {
                const select = document.getElementById('parked-restore-stage');
                if (select && currentStage) select.value = currentStage;
            },
            preConfirm: () => {
                const value = document.getElementById('parked-restore-stage')?.value || '';
                if (!value) {
                    Swal.showValidationMessage('مرحله مقصد را انتخاب کنید');
                    return false;
                }
                return value;
            }
        });
        if (!stage) return;

        const res = await this.req('deal/move_out_of_parked', {
            deal_id: dealId,
            target_stage: stage
        });
        if (res.status === 'success') {
            Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: res.message || 'معامله به کاریز بازگشت', showConfirmButton: false, timer: 2000 });
            await this.openDealModal(dealId);
            if (document.getElementById('deals-container')) this.loadDeals();
            if (document.getElementById('dashboard-content')) this.loadDashboard();
        } else {
            Swal.fire('خطا', res.message || 'خطا در بازگرداندن معامله', 'error');
        }
    },

    updateDealCurrentFields(stageValue = null, statusValue = null) {
        const statusMap = {
            pending: 'جاری',
            won: 'موفق',
            lost: 'ناموفق',
            Pending: 'جاری',
            Won: 'موفق',
            Lost: 'ناموفق'
        };
        const statusSelect = document.getElementById('deal-status');
        const stageSelect = document.getElementById('deal-pipeline-stage');
        const statusText = statusValue || statusSelect?.value || '';
        const stageText = stageValue || stageSelect?.value || '';
        const statusBox = document.getElementById('deal-current-status');
        const stageBox = document.getElementById('deal-current-stage');
        if (statusBox) {
            statusBox.textContent = statusMap[statusText] || '-';
        }
        if (stageBox) {
            stageBox.textContent = stageText || '-';
        }
    },

    showDealFormTab(tab = 'info') {
        this.state.activeDealFormTab = tab;
        const tabs = ['info', 'activities', 'payments'];
        tabs.forEach(t => {
            const section = document.getElementById(`deal-form-tab-${t}`);
            const btn = document.getElementById(`deal-tab-btn-${t}`);
            if (section) section.classList.toggle('hidden', t !== tab);
            if (btn) {
                if (t === tab) {
                    btn.classList.add('is-active');
                    btn.setAttribute('aria-selected', 'true');
                } else {
                    btn.classList.remove('is-active');
                    btn.setAttribute('aria-selected', 'false');
                }
            }
        });

        // Entering the payments tab via its tab button shows only the history +
        // "افزودن پرداخت" button; the new-payment form stays collapsed until requested.
        // Exception: if the user already started typing a payment (or is mid-edit) and
        // just bounced to another tab, keep their unsaved draft on screen instead of
        // collapsing it — switching tabs must not discard in-progress input.
        if (tab === 'payments') {
            this.setPaymentFormVisible(this.hasUnsavedPaymentDraft());
        }
        this.syncDealCancelButton();

        // Buttons are inside each tab; no global save toggle needed
    },

    updateDealActivitiesBadge(count = 0) {
        const badge = document.getElementById('deal-activities-badge');
        if (badge) badge.textContent = count > 99 ? '99+' : count;
    },

    hasCompletePaymentsForSuccess() {
        const collected = this.collectCurrentPayments();
        const cached = this.state.lastPaymentsCache || [];
        const stored = Array.isArray(this.state.currentDeal?.payments) ? this.state.currentDeal.payments : [];
        const list = [...collected, ...cached, ...stored];
	        const valid = list.filter(p => {
	            if (this.isDeletedPayment(p)) return false;
	            const amountVal = parseMoneyValue(p.amount || 0);
	            const hasDate = !!(p.date_persian || p.date);
	            const hasMethod = !!(p.method && String(p.method).trim() !== '');
	            return hasDate && hasMethod && amountVal > 0;
	        });
        const result = valid.length > 0;
        console.log('hasCompletePaymentsForSuccess', { collected, cached, stored, validCount: valid.length, result });
        return result;
    },

    getDealSuccessRequiredPaymentAmount(form = null) {
        const payableField = form?.payable_amount || document.getElementById('deal-payable-amount');
        if (payableField) {
            return Math.max(0, parseMoneyValue(payableField.value || 0));
        }

        if (this.state.currentDeal && Object.prototype.hasOwnProperty.call(this.state.currentDeal, 'payable_amount') && this.state.currentDeal.payable_amount !== null) {
            return Math.max(0, parseMoneyValue(this.state.currentDeal.payable_amount || 0));
        }

        return Math.max(0, parseMoneyValue(form?.service_cost?.value || this.state.currentDeal?.service_cost || 0));
    },

    hasEnoughPaymentsForSuccess() {
        const requiredAmount = this.getDealSuccessRequiredPaymentAmount();
        if (requiredAmount <= 0) {
            return true;
        }

        const collected = this.collectCurrentPayments();
        const cached = this.state.lastPaymentsCache || [];
        const stored = Array.isArray(this.state.currentDeal?.payments) ? this.state.currentDeal.payments : [];
        const list = [...collected, ...cached, ...stored];
        const totalPaid = list.reduce((sum, p) => sum + (this.isDeletedPayment(p) ? 0 : this.getPaymentNetAmount(p)), 0);
        const result = totalPaid + 0.0001 >= requiredAmount;
        console.log('hasEnoughPaymentsForSuccess', { requiredAmount, totalPaid, result });
        return result;
    },

        populateDealActivityServices(selected = []) {
        // Keep selected services in state
        this.state.dealActivityRequestedServices = Array.isArray(selected) ? [...new Set(selected.map(String))] : [];

        const select = document.getElementById('deal-activity-service-select');
        if (select) {
            select.innerHTML = '<option value="">-- انتخاب خدمت --</option>';

            const lookupOptions = this.getLookupOptions('allowed_services');
            if (lookupOptions.length) {
                lookupOptions.forEach(service => {
                    const o = document.createElement('option');
                    o.value = service.value;
                    o.textContent = service.label || service.value;
                    select.appendChild(o);
                });
            } else {
                // fallback: read from deal-title select (static services)
                const titleSelect = document.getElementById('deal-title');
                if (titleSelect) {
                    Array.from(titleSelect.options || []).forEach(opt => {
                        if (!opt.value) return;
                        const o = document.createElement('option');
                        o.value = opt.value;
                        o.textContent = opt.textContent;
                        select.appendChild(o);
                    });
                }
            }

            // Try to refresh from API in background (optional)
            this.req('get_allowed_services').then(res => {
                if (res.status === 'success' && res.services && res.services.length > 0) {
                    select.innerHTML = '<option value="">-- انتخاب خدمت --</option>';
                    res.services.forEach(service => {
                        const o = document.createElement('option');
                        o.value = service;
                        o.textContent = service;
                        select.appendChild(o);
                    });
                }
            }).catch(() => {});
        }
        this.renderDealActivityServices();
    },

    renderDealActivityServices() {
        const chips = document.getElementById('deal-activity-service-chips');
        const hidden = document.getElementById('deal-activity-requested-services');
        if (!chips || !hidden) return;
        const items = Array.isArray(this.state.dealActivityRequestedServices) ? this.state.dealActivityRequestedServices : [];
        chips.innerHTML = '';
        items.forEach(service => {
            const chip = document.createElement('span');
            chip.className = 'inline-flex items-center gap-1 px-2 py-1 rounded-full bg-gray-800 text-gray-100 text-xs border border-gray-700';
            chip.textContent = service;
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'text-red-400 hover:text-red-200 ml-1';
            btn.innerHTML = '&times;';
            btn.onclick = () => this.removeDealActivityService(service);
            chip.appendChild(btn);
            chips.appendChild(chip);
        });
        hidden.value = JSON.stringify(items);
    },

    addDealActivityService() {
        if (this.state.dealHasActivity) {
            Swal.fire('خطا', 'پس از ثبت اولین فعالیت امکان ویرایش خدمات نیست', 'error');
            return;
        }
        const select = document.getElementById('deal-activity-service-select');
        if (!select) return;
        const value = select.value;
        if (!value) return;
        this.state.dealActivityRequestedServices = Array.isArray(this.state.dealActivityRequestedServices)
            ? this.state.dealActivityRequestedServices
            : [];
        if (!this.state.dealActivityRequestedServices.includes(value)) {
            this.state.dealActivityRequestedServices.push(value);
            this.renderDealActivityServices();
        }
        select.value = '';
    },

    removeDealActivityService(service) {
        if (this.state.dealHasActivity) {
            Swal.fire('خطا', 'پس از ثبت اولین فعالیت امکان حذف خدمات نیست', 'error');
            return;
        }
        if (!Array.isArray(this.state.dealActivityRequestedServices)) return;
        this.state.dealActivityRequestedServices = this.state.dealActivityRequestedServices.filter(s => s !== service);
        this.renderDealActivityServices();
    },

    toggleAcqLessThanMonth() {
        const less = document.getElementById('deal-activity-acq-less');
        const years = document.getElementById('deal-activity-acq-years');
        const months = document.getElementById('deal-activity-acq-months');
        if (!less || !years || !months) return;
        if (less.checked) {
            years.disabled = true;
            months.disabled = true;
            years.value = '0';
            months.value = '0';
            years.required = false;
            months.required = false;
        } else {
            years.disabled = false;
            months.disabled = false;
            years.required = true;
            months.required = true;
        }
    },

    // Show/hide initial info fields (city/job/financial/etc.) to avoid re-entry in deals
    toggleInitialInfoVisibility(show = true) {
        // initial info fields removed from deal modal; keep no-op for compatibility
        return;
    },

    lockDealActivityExtraFields(values = {}) {
        const setReadonly = (el, condition) => {
            if (!el) return;
            if (condition && String(condition).trim() !== '') {
                if ('readOnly' in el) el.readOnly = true;
                if ('disabled' in el && el.tagName === 'SELECT') el.disabled = true;
                el.classList.add('bg-gray-900', 'cursor-not-allowed');
                el.dataset.locked = 'true';
            } else {
                if ('readOnly' in el) el.readOnly = false;
                if ('disabled' in el && el.tagName === 'SELECT') el.disabled = false;
                el.classList.remove('bg-gray-900', 'cursor-not-allowed');
                el.dataset.locked = '';
            }
        };

        setReadonly(document.getElementById('deal-activity-city'), values.city);
        setReadonly(document.getElementById('deal-activity-job'), values.job_title);
        setReadonly(document.getElementById('deal-activity-job-desc'), values.job_description);
        setReadonly(document.getElementById('deal-activity-extra-info'), values.extra_info);

        const fin = document.getElementById('deal-activity-financial-level');
        const asset = document.getElementById('deal-activity-asset');
        const income = document.getElementById('deal-activity-income');
        setReadonly(fin, values.financial_level);
        setReadonly(asset, values.asset_estimation);
        setReadonly(income, values.income_estimation);

        // Requested services should stay editable
        const svcSelect = document.getElementById('deal-activity-service-select');
        const svcAdd = document.getElementById('deal-activity-service-add');
        if (svcSelect) svcSelect.disabled = false;
        if (svcAdd) svcAdd.disabled = false;

        // خرید قبلی اگر تعیین شده باشد قفل شود
        const purchaseSelect = document.getElementById('deal-activity-has-purchase');
        const purchaseLocked = values.has_previous_purchase === 0 || values.has_previous_purchase === 1 || values.has_previous_purchase === '0' || values.has_previous_purchase === '1';
        if (purchaseSelect) {
            if (purchaseLocked) {
                purchaseSelect.value = String(values.has_previous_purchase);
                purchaseSelect.disabled = true;
                purchaseSelect.dataset.locked = 'true';
                purchaseSelect.classList.add('bg-gray-900', 'cursor-not-allowed');
            } else {
                // For new deals or when no previous purchase value exists, keep field enabled
                purchaseSelect.disabled = false;
                purchaseSelect.dataset.locked = '';
                purchaseSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
            }
        }

        // Acquaintance duration
        const acqLess = document.getElementById('deal-activity-acq-less');
        const acqYears = document.getElementById('deal-activity-acq-years');
        const acqMonths = document.getElementById('deal-activity-acq-months');
        if (acqLess && acqYears && acqMonths) {
            const locked = values.acquaintance_duration && String(values.acquaintance_duration).trim() !== '';
            acqLess.disabled = locked;
            acqYears.disabled = locked || acqLess.checked;
            acqMonths.disabled = locked || acqLess.checked;
            acqLess.dataset.locked = locked ? 'true' : '';
            acqYears.dataset.locked = locked ? 'true' : '';
            acqMonths.dataset.locked = locked ? 'true' : '';
            if (locked) {
                acqYears.classList.add('cursor-not-allowed', 'bg-gray-900');
                acqMonths.classList.add('cursor-not-allowed', 'bg-gray-900');
            } else {
                acqYears.classList.remove('cursor-not-allowed', 'bg-gray-900');
                acqMonths.classList.remove('cursor-not-allowed', 'bg-gray-900');
            }
        }
    },

    populateDealActivityStages() {
        const src = document.getElementById('deal-pipeline-stage');
        const dest = document.getElementById('deal-activity-stage');
        if (!src || !dest) return;
        const current = dest.value;
        dest.innerHTML = '';
        const addOpt = (val, text) => {
            const o = document.createElement('option');
            o.value = val;
            o.textContent = text;
            dest.appendChild(o);
        };
        addOpt('', '-- انتخاب مرحله کاریز فروش --');
        Array.from(src.options || []).forEach(opt => {
            if (!opt.value) return;
            if (opt.value === 'بلاتکلیف' || opt.value === 'معامله جدید') return;
            addOpt(opt.value, opt.textContent);
        });
        if (!Array.from(dest.options).some(o => o.value === 'deal_failed')) {
            addOpt('deal_failed', 'معامله ناموفق');
        }
        if (!Array.from(dest.options).some(o => o.value === 'deal_success')) {
            addOpt('deal_success', 'معامله موفق');
        }

        dest.onchange = () => {
            this.state.activityStagePrev = dest.value;
            this.toggleDealActivityFailure();
            this.handleActivityStageChange(dest.value);
        };
        this.state.activityStagePrev = current || '';
        dest.value = current;
        this.toggleDealActivityFailure();
    },

    toggleDealActivityFailure() {
        const stage = document.getElementById('activity-virtual-stage')?.value || document.getElementById('deal-activity-stage')?.value || '';
        const failureBox = document.getElementById('failure-reason-extra');
        const reason = document.getElementById('activity-failure-reason');
        const otherBox = document.getElementById('activity-failure-other-reason');
        const otherDesc = document.getElementById('activity-failure-other-description');
        const stageMeta = this.getLookupMeta('activity_stages', stage);
        const showFailureFromMeta = stageMeta.show_failure_reason === true || stageMeta.showFailureReason === true;
        const required = stage === 'deal_failed' || stage === 'معامله ناموفق' || showFailureFromMeta;

        if (failureBox) failureBox.classList.toggle('hidden', !required);
        if (reason) {
            reason.required = required;
            if (!required) reason.value = '';
        }
        if (otherBox) otherBox.classList.toggle('hidden', !required);
        if (otherDesc) {
            otherDesc.required = required;
            if (!required) {
                otherDesc.value = '';
            } else {
                otherDesc.placeholder = 'لطفاً توضیحات دلیل عدم موفقیت را وارد کنید';
            }
        }

        this.toggleActivityFailureOtherReason();
    },

    toggleDealActivityFailureOther() {
        const reason = document.getElementById('deal-activity-failure-reason')?.value || '';
        const otherBox = document.getElementById('deal-activity-failure-other');
        const otherDesc = document.getElementById('deal-activity-failure-other-description');
        const show = reason === 'other';
        if (otherBox) otherBox.classList.toggle('hidden', !show);
        if (otherDesc) {
            otherDesc.required = show;
            if (!show) otherDesc.value = '';
        }
    },

    async saveDealActivity(e) {
        e.preventDefault();
        if (this.state.dealHasActivity) {
            Swal.fire('خطا', 'برای این معامله قبلاً فعالیت ثبت شده است', 'error');
            return;
        }
        const contactId = document.getElementById('deal-activity-contact-id')?.value || window.currentContactId || '';
        const dealId = document.getElementById('deal-activity-deal-id')?.value || document.getElementById('deal-id')?.value || '';
        const stage = document.getElementById('activity-virtual-stage')?.value || document.getElementById('deal-activity-stage')?.value || '';
        const note = document.getElementById('deal-activity-result-note')?.value || '';
        // Requested services from state/hidden input
        let services = Array.isArray(this.state.dealActivityRequestedServices) ? this.state.dealActivityRequestedServices : [];
        if (!services.length) {
            const hiddenServices = document.getElementById('deal-activity-requested-services')?.value;
            try {
                const parsed = JSON.parse(hiddenServices || '[]');
                if (Array.isArray(parsed)) services = parsed;
            } catch (err) {
                console.warn('Could not parse deal activity services', err);
            }
        }
        const financialLevel = document.getElementById('deal-activity-financial-level')?.value || '';
        const asset = document.getElementById('deal-activity-asset')?.value || '';
        const income = document.getElementById('deal-activity-income')?.value || '';
        const isDone = document.getElementById('deal-activity-is-done')?.checked ? '1' : '0';
        const failureReason = document.getElementById('deal-activity-failure-reason')?.value || '';
        const failureOther = document.getElementById('deal-activity-failure-other-description')?.value || '';
        const activityType = document.getElementById('deal-activity-type-select')?.value || '';
        const direction = document.getElementById('deal-activity-direction')?.value || 'outgoing';
        const hasPurchaseOther = document.getElementById('deal-activity-has-purchase')?.value || '';
        const prevPurchaseNumber = document.getElementById('deal-activity-previous-number')?.value || '';
        const city = document.getElementById('deal-activity-city')?.value || '';
        const jobTitle = document.getElementById('deal-activity-job')?.value || '';
        const jobDescription = document.getElementById('deal-activity-job-desc')?.value || '';
        const extraInfo = document.getElementById('deal-activity-extra-info')?.value || '';
        const acqLess = document.getElementById('deal-activity-acq-less')?.checked;
        const acqYears = document.getElementById('deal-activity-acq-years')?.value || '0';
        const acqMonths = document.getElementById('deal-activity-acq-months')?.value || '0';
        const acquaintanceDuration = acqLess ? 'کمتر از 1 ماه' : `${acqYears} سال ${acqMonths} ماه`;
        const hasPreviousPurchaseVal = document.getElementById('deal-activity-has-purchase')?.value ?? '';

        if (!contactId) {
            Swal.fire('خطا', 'شناسه لید خالی است', 'error');
            return;
        }
        if (!dealId) {
            console.warn('saveDealActivity blocked: empty dealId', {contactId});
            Swal.fire('خطا', 'ابتدا معامله را ذخیره کنید، سپس فعالیت ثبت کنید', 'error');
            this.showDealFormTab('info');
            return;
        }
        if (!stage) {
            Swal.fire('خطا', 'مرحله کاریز را انتخاب کنید', 'error');
            return;
        }
        if (!note.trim()) {
            Swal.fire('خطا', 'نتیجه فعالیت را وارد کنید', 'error');
            return;
        }
        if (!services.length) {
            Swal.fire('خطا', 'خدمات درخواستی را انتخاب کنید', 'error');
            return;
        }
        const isStageFailed = stage === 'deal_failed' || stage === 'معامله ناموفق';
        const isStageSuccess = stage === 'deal_success';

        if (isStageFailed && !failureReason) {
            Swal.fire('خطا', 'دلیل ناموفق را انتخاب کنید', 'error');
            return;
        }
        if (isStageFailed && failureReason === 'other' && !failureOther.trim()) {
            Swal.fire('خطا', 'توضیح دلیل ناموفق را وارد کنید', 'error');
            return;
        }
        if (isStageSuccess && !this.hasEnoughPaymentsForSuccess()) {
            Swal.fire('خطا', 'برای ثبت معامله موفق، مجموع پرداخت‌ها باید حداقل برابر مبلغ قابل پرداخت باشد.', 'error');
            console.warn('saveDealActivity blocked: no complete payments for success', { stage, dealId });
            return;
        }
        if (!activityType) {
            Swal.fire('خطا', 'نوع فعالیت را انتخاب کنید', 'error');
            return;
        }

        const isLocked = (el) => el?.dataset?.locked === 'true';
        const cityEl = document.getElementById('deal-activity-city');
        const jobEl = document.getElementById('deal-activity-job');
        const prevPurchaseEl = document.getElementById('deal-activity-previous-number');
        const acqYearsEl = document.getElementById('deal-activity-acq-years');
        const acqMonthsEl = document.getElementById('deal-activity-acq-months');

        if (!city.trim() && !isLocked(cityEl)) {
            Swal.fire('خطا', 'شهر را وارد کنید', 'error');
            return;
        }
        if (!jobTitle.trim() && !isLocked(jobEl)) {
            Swal.fire('خطا', 'شغل را وارد کنید', 'error');
            return;
        }

        const acqLocked = isLocked(acqYearsEl) || isLocked(acqMonthsEl);
        if (!acqLocked && !acqLess && acqYears === '0' && acqMonths === '0') {
            Swal.fire('خطا', 'مدت زمان آشنایی را وارد کنید', 'error');
            return;
        }

        let prevPurchaseClean = (prevPurchaseNumber || '').trim();
        if (prevPurchaseClean) {
            prevPurchaseClean = prevPurchaseClean.replace(/\D/g, '');
            if (!/^[1-9]\d{9}$/.test(prevPurchaseClean)) {
                Swal.fire('خطا', 'شماره خرید قبلی باید ۱۰ رقم و بدون صفر شروع باشد', 'error');
                return;
            }
        }
        const cityValue = city.trim();
        const jobValue = jobTitle.trim();
        const jobDescValue = jobDescription.trim();
        const extraInfoValue = extraInfo.trim();

        const payload = {
            contact_id: contactId,
            deal_id: dealId,
            pipeline_stage: isStageFailed ? '' : (isStageSuccess ? 'اتمام' : stage),
            result_note: note,
            requested_services: JSON.stringify(services),
            financial_level: financialLevel,
            asset_estimation: asset,
            income_estimation: income,
            is_done: isDone,
            activity_type_id: activityType,
            direction,
            has_previous_purchase_other_number: hasPurchaseOther,
            previous_purchase_number: prevPurchaseClean,
            city: cityValue,
            job_title: jobValue,
            job_description: jobDescValue,
            acquaintance_duration: acquaintanceDuration,
            extra_info: extraInfoValue,
            failure_reason: failureReason,
            failure_other_description: failureOther,
            has_previous_purchase: hasPreviousPurchaseVal === '' ? null : hasPreviousPurchaseVal
        };

        try {
            Swal.showLoading();
            const res = await this.req('save_activity', payload);
            if (isStageFailed) {
                const reasonText = {
                    'no_financial_ability': 'عدم توان مالی',
                    'no_purchase_intent': 'فعلا قصد خرید ندارد',
                    'competitor_purchase': 'خرید از رقبا',
                    'wrong_number': 'شماره اشتباه',
                    'irrelevant_lead': 'لید نامرتبط',
                    'other': 'سایر'
                };
                const statusRes = await this.req('set_deal_status', {
                    deal_id: dealId,
                    status: '0',
                    contact_id: contactId,
                    pipeline_stage: 'معامله ناموفق',
                    failure_reason: failureReason,
                    failure_reason_text: reasonText[failureReason] || '',
                    failure_other_description: failureOther
                });
                if (statusRes?.status !== 'success') {
                    console.warn('set_deal_status failed for lost', statusRes);
                    this.closeBlockingSwal();
                    Swal.fire('خطا', statusRes?.message || 'وضعیت معامله به ناموفق تغییر نکرد', 'error');
                    return;
                }
            } else if (isStageSuccess) {
                const statusRes = await this.req('set_deal_status', { deal_id: dealId, status: '1', contact_id: contactId, pipeline_stage: 'اتمام' });
                if (statusRes?.status !== 'success') {
                    console.warn('set_deal_status failed for won', statusRes);
                    this.closeBlockingSwal();
                    Swal.fire('خطا', statusRes?.message || 'وضعیت معامله به موفق تغییر نکرد', 'error');
                    return;
                }
            }
            this.closeBlockingSwal();
            if (res.status === 'success') {
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'فعالیت ثبت شد', showConfirmButton:false, timer:2000});
                this.state.dealHasActivity = true;
                this.toggleDealActivityForm(true);
                // refresh profile/deal list
                if (window.currentContactId) {
                    await this.openPersonProfile(window.currentContactId);
                }
                if (document.getElementById('deals-container')) {
                    await this.loadDeals();
                }
                await this.openDealModal(dealId);
                this.showDealFormTab('activities');
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص در ثبت فعالیت', 'error');
            }
        } catch (err) {
            console.error('saveDealActivity error', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'ثبت فعالیت انجام نشد', 'error');
        }
    },

    // Save only extra info (without creating activity) for missing fields
    async saveDealInfoOnly() {
        const contactId = document.getElementById('deal-activity-contact-id')?.value || window.currentContactId || '';
        const dealId = document.getElementById('deal-activity-deal-id')?.value || document.getElementById('deal-id')?.value || '';
        if (!contactId) {
            Swal.fire('خطا', 'شناسه لید خالی است', 'error');
            return;
        }
        const payload = {
            contact_id: contactId,
            deal_id: dealId
        };

        const addIfEditable = (id, key) => {
            const el = document.getElementById(id);
            if (!el || el.dataset.locked === 'true') return;
            const val = (el.value || '').trim();
            if (val) payload[key] = val;
        };

        addIfEditable('deal-activity-city', 'city');
        addIfEditable('deal-activity-job', 'job_title');
        addIfEditable('deal-activity-job-desc', 'job_description');
        addIfEditable('deal-activity-extra-info', 'extra_info');
        addIfEditable('deal-activity-financial-level', 'financial_level');
        addIfEditable('deal-activity-asset', 'asset_estimation');
        addIfEditable('deal-activity-income', 'income_estimation');

        // Acquaintance duration
        const acqLess = document.getElementById('deal-activity-acq-less');
        const acqYears = document.getElementById('deal-activity-acq-years');
        const acqMonths = document.getElementById('deal-activity-acq-months');
        if (acqYears && acqMonths && !(acqYears.dataset.locked === 'true' || acqMonths.dataset.locked === 'true')) {
            let acqVal = '';
            if (acqLess?.checked) {
                acqVal = 'کمتر از 1 ماه';
            } else {
                acqVal = `${acqYears.value || '0'} سال ${acqMonths.value || '0'} ماه`;
            }
            if (acqVal.trim()) payload.acquaintance_duration = acqVal;
        }

        // Requested services
        if (Array.isArray(this.state.dealActivityRequestedServices) && this.state.dealActivityRequestedServices.length > 0) {
            payload.requested_services = JSON.stringify(this.state.dealActivityRequestedServices);
        }

        // Purchase status if not locked
        const purchaseSelect = document.getElementById('deal-activity-has-purchase');
        if (purchaseSelect && purchaseSelect.dataset.locked !== 'true') {
            const val = purchaseSelect.value;
            if (val !== '') {
                payload.has_previous_purchase = val;
            }
        }

        if (Object.keys(payload).length <= 2) {
            Swal.fire('خطا', 'فیلد قابل ویرایش یا مقداری برای ذخیره وجود ندارد', 'error');
            return;
        }

        try {
            Swal.showLoading();
            const res = await this.req('save_deal_info', payload);
            this.closeBlockingSwal();
            if (res.status === 'success') {
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:res.message || 'ذخیره شد', showConfirmButton:false, timer:2000});
                if (contactId) await this.openPersonProfile(contactId);
                if (dealId) await this.openDealModal(dealId);
            } else {
                Swal.fire('خطا', res.message || 'ذخیره انجام نشد', 'error');
            }
        } catch (err) {
            console.error('saveDealInfoOnly error', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'ذخیره اطلاعات انجام نشد', 'error');
        }
    },

    startActivityForDeal(presetStage = '') {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        // محاسبه contactId از چند منبع تا همیشه مقدار داشته باشد
        const contactId =
            document.getElementById('deal-contact-id')?.value ||
            this.state.currentDeal?.local_person_id ||
            this.state.currentDeal?.contact_didar_id ||
            this.state.currentLead?.id ||
            this.state.currentLead?.didar_contact_id ||
            '';
        if (!contactId) {
            Swal.fire('خطا', 'شناسه شخص برای این معامله مشخص نیست', 'error');
            return;
        }
        const dealId = document.getElementById('deal-id')?.value || this.state.currentDeal?.didar_deal_id || this.state.currentDeal?.id || '';
        const activityContactInput = document.getElementById('activity-contact-id');
        if (activityContactInput) {
            activityContactInput.value = contactId;
        }
        window.currentContactId = contactId; // برای openModal که از window.currentContactId استفاده می‌کند

        // اطمینان از اینکه لیست معاملات در اکتیویتی پر است
        let deals = Array.isArray(this.state.currentDeals) ? [...this.state.currentDeals] : [];
        if (dealId && !deals.find(d => String(d.didar_deal_id || d.id || d.deal_id) === String(dealId))) {
            if (this.state.currentDeal) {
                deals.push(this.state.currentDeal);
            }
        }
        // اگر هیچ معامله‌ای در لیست نبود، یک معامله موقت برای انتخاب فعالیت بسازیم
        if (!dealId && deals.length === 0 && (this.state.currentDeal || contactId)) {
            const pipelineStageFallback = document.getElementById('deal-pipeline-stage')?.value || '';
            const tempDeal = {
                didar_deal_id: '',
                id: '',
                deal_id: '',
                contact_didar_id: contactId,
                title: document.getElementById('deal-title')?.value || 'معامله',
                pipeline_stage: pipelineStageFallback
            };
            deals.push(tempDeal);
            this.state.currentDeal = tempDeal;
        }

        const pipelineStage = presetStage || document.getElementById('deal-pipeline-stage')?.value || this.state.currentDeal?.pipeline_stage || '';
        this.populateActivityDealSelect(deals, dealId, pipelineStage);

        const stageSelect = document.getElementById('activity-virtual-stage');
        if (stageSelect && pipelineStage) {
            stageSelect.value = pipelineStage;
        }
        if (pipelineStage) {
            const stageDisplay = document.getElementById('activity-current-stage-display');
            const currentStageInput = document.getElementById('activity-current-stage');
            if (stageDisplay) stageDisplay.innerHTML = `<span class="text-sm text-gray-200">${pipelineStage}</span>`;
            if (currentStageInput) currentStageInput.value = pipelineStage;
        }

        // ابتدا مودال را باز کن تا عناصر فرم حتماً در دسترس باشند، سپس استیج را دوباره ست کن
        this.openModal('modalActivity', { source: 'user', entityId: String(dealId || contactId || '') });
        const activityDealIdInput = document.getElementById('activity-deal-id');
        if (activityDealIdInput && dealId) {
            activityDealIdInput.value = dealId;
        }
        if (stageSelect && pipelineStage) {
            stageSelect.value = pipelineStage;
        }
        const stageDisplay = document.getElementById('activity-current-stage-display');
        const currentStageInput = document.getElementById('activity-current-stage');
        if (stageDisplay) stageDisplay.innerHTML = pipelineStage
            ? `<span class="text-sm text-gray-200">${pipelineStage}</span>`
            : '<span class="text-sm text-gray-500">مرحله تعیین نشده</span>';
        if (currentStageInput) currentStageInput.value = pipelineStage || 'keep_current';

        // برای جلوگیری از پوشانده شدن مودال فعالیت، ابتدا مودال معامله را ببند
        this.closeModal('modalDeal');
    },

    // Toggles between the two payments views: the history list (پرداخت‌های گذشته + افزودن پرداخت)
    // and the new-payment form (ثبت پرداخت جدید). They are mutually exclusive — the form is
    // never shown beneath the history.
    setPaymentFormVisible(visible) {
        const details = document.getElementById('deal-payment-details');
        if (details) details.style.display = visible ? 'block' : 'none';
        const history = document.getElementById('deal-existing-payments-section');
        if (history) history.style.display = visible ? 'none' : 'block';
        this.updatePaymentsSubmitState();
        this.syncDealCancelButton();
    },

    // Decides which "add payment" / "لغو" buttons are shown depending on where we are:
    //  - History view, NO payments  → only the big footer "افزودن پرداخت" CTA.
    //  - History view, HAS payments → only the small header "افزودن پرداخت" beside the list.
    //  - New-payment form view / other tabs → footer becomes a plain "لغو".
    syncDealCancelButton() {
        const onPaymentsTab = this.state.activeDealFormTab === 'payments';
        const onActivitiesTab = this.state.activeDealFormTab === 'activities';
        const details = document.getElementById('deal-payment-details');
        const formVisible = !!details && details.style.display !== 'none';
        const historyView = onPaymentsTab && !formVisible;
        const hasPayments = (this.state.lastPaymentsCache || []).length > 0;
        const canCreate = this.canActionClient('payments.create');
        const legacyLocked = this.isLegacyDidarDeal();

        // Header "افزودن پرداخت" (beside پرداخت‌های گذشته): only when there is history to add onto.
        const headerBtn = document.getElementById('deal-add-payment-btn');
        if (headerBtn) {
            headerBtn.style.display = (!legacyLocked && historyView && hasPayments && canCreate) ? '' : 'none';
        }

        const footer = document.getElementById('deal-cancel-btn');
        if (!footer) return;
        if (onActivitiesTab) {
            footer.style.display = 'none';
            return;
        }
        if (historyView) {
            if (!legacyLocked && !hasPayments && canCreate) {
                footer.dataset.mode = 'add-payment';
                footer.innerHTML = '<i class="fa-solid fa-plus ml-1"></i> افزودن پرداخت';
                footer.classList.add('btn-primary');
                footer.classList.remove('text-gray-400');
                footer.style.display = '';
            } else {
                // History with payments (or no create permission): no footer button — the
                // header X closes the modal, and "لغو" has no meaning while just viewing.
                footer.style.display = 'none';
            }
        } else {
            footer.dataset.mode = 'cancel';
            footer.innerHTML = '<i class="fa-solid fa-times ml-1"></i> لغو';
            footer.classList.remove('btn-primary');
            footer.classList.add('text-gray-400');
            footer.style.display = '';
        }
    },

    onDealCancelButton() {
        const btn = document.getElementById('deal-cancel-btn');
        if (btn && btn.dataset.mode === 'add-payment') {
            this.openNewPaymentForm();
            return;
        }
        this.closeModalFromUser('modalDeal');
    },

    // Triggered by the "افزودن پرداخت" button next to the payments history:
    // reveals the new-payment form and ensures one empty row is ready.
    openNewPaymentForm() {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.canActionClient('payments.create')) {
            Swal.fire('خطا', 'دسترسی به ثبت پرداخت ندارید', 'error');
            return;
        }
        this.state.editingPaymentIndex = null;
        this.setPaymentFormTitle('ثبت پرداخت جدید');
        this.setPaymentFormVisible(true);
        const container = document.getElementById('payment-rows');
        if (container) {
            this.cleanupPersianDatepickers(container);
            container.innerHTML = '';
            this.addPaymentRow(null, false);
        }
        document.getElementById('deal-payment-details')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    },

    setPaymentFormTitle(title) {
        const el = document.getElementById('deal-payment-form-title');
        if (el) el.textContent = title;
    },

    // The "ثبت پرداخت ها" action button: open the payments tab with the form already expanded.
    scrollToDealPayments() {
        this.showDealFormTab('payments');
        this.state.editingPaymentIndex = null;
        this.setPaymentFormTitle('ثبت پرداخت جدید');
        this.setPaymentFormVisible(true);
        const container = document.getElementById('payment-rows');
        if (container && this.canActionClient('payments.create')) {
            this.cleanupPersianDatepickers(container);
            container.innerHTML = '';
            this.addPaymentRow(null, false);
        }
        const section = document.getElementById('deal-payment-details');
        if (section) {
            section.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    },

    openSupportFromDeal() {
        if (!this.state.currentDeal) {
            Swal.fire('خطا', 'ابتدا معامله را انتخاب یا ذخیره کنید', 'error');
            return;
        }
        this.openCourseModal(this.state.currentDeal, 'پشتیبانی');
    },

    openCourseFromDeal() {
        if (!this.state.currentDeal) {
            Swal.fire('خطا', 'ابتدا معامله را انتخاب یا ذخیره کنید', 'error');
            return;
        }
        this.openCourseModal(this.state.currentDeal, 'برگزاری دوره');
    },
    
    // OPEN DEAL MODAL
    async openDealModal(dealId, options = {}) {
        const opts = options || {};
        const requestedInitialTab = ['info', 'payments', 'activities'].includes(String(opts.initialTab || ''))
            ? String(opts.initialTab)
            : 'info';
        const dealToken = this.createContextToken('dealModal', { dealId: String(dealId || '') || 'new' });
        const dropIfStaleDeal = () => this.dropIfStaleContext('dealModal', dealToken, { dealId: String(dealId || '') || 'new' });
        this.resetDealModalState({ keepContact: true });
        const isEditDeal = !!dealId;
        if (!isEditDeal && !this.canActionClient('deals.create')) {
            Swal.fire('خطا', 'دسترسی به ایجاد معامله ندارید', 'error');
            return;
        }
        let personForSummary = null;
        
        // Populate lead info display in deal modal
        const dealInfoDisplay = document.getElementById('deal-lead-info-display');
        const dealNameText = document.getElementById('deal-lead-name-text');
        if (this.state.currentLead && dealInfoDisplay && dealNameText) {
            const fullName = `${this.state.currentLead.first_name || ''} ${this.state.currentLead.last_name || ''}`.trim() || 'بدون نام';
            dealNameText.textContent = `${fullName} (${this.state.currentLead.mobile_phone || '-'})`;
            dealInfoDisplay.classList.remove('hidden');
        } else if (dealInfoDisplay) {
            dealInfoDisplay.classList.add('hidden');
        }

        // ensure products list
        if (!this.state.lookupsLoaded) {
            await this.loadLookups();
            if (dropIfStaleDeal()) return;
        }
        if (!this.state.products || this.state.products.length === 0) {
            await this.loadProducts();
            if (dropIfStaleDeal()) return;
        }
        this.initDealServiceLookupAutofillTracking();
        this.resetDealServiceLookupAutofillState();
        this.applyRbacActionVisibility();
        const dealTitleSelectEl = document.getElementById('deal-title');
        if (dealTitleSelectEl) {
            dealTitleSelectEl.onchange = () => {
                this.applyDealServiceLookupPricing();
                this.syncDealProductsFromSelectedService();
            };
        }
        const dealHasDiscountEl = document.getElementById('deal-has-discount');
        const dealDiscountTypeEl = document.getElementById('deal-discount-type');
        const dealDiscountAmountEl = document.getElementById('deal-discount-amount');
        if (dealHasDiscountEl) {
            dealHasDiscountEl.onchange = () => this.toggleDiscountFields();
        }
        if (dealDiscountTypeEl) {
            dealDiscountTypeEl.onchange = () => this.toggleDiscountOccasion();
        }
        if (dealDiscountAmountEl) {
            dealDiscountAmountEl.oninput = () => this.applyDealServiceLookupPricing();
        }
        this.setDealServiceCostLookupLock(true);
        const stageSelectEl = document.getElementById('deal-pipeline-stage');
        if (stageSelectEl) {
            stageSelectEl.onchange = () => {
                const badge = document.getElementById('deal-summary-stage');
                if (badge) badge.textContent = stageSelectEl.value || 'مرحله تعیین نشده';
                this.updateDealCurrentFields();
            };
        }
        // برای معامله جدید، تب اطلاعات معامله نمایش داده شود تا ابتدا معامله ذخیره گردد
        this.showDealFormTab('info');
        
        // ✅ Ensure configs loaded then apply (Tooltips, Logic, Defaults, Roles)
        await this.ensureFieldConfigsLoaded();
        if (dropIfStaleDeal()) return;
        this.applyFieldConfigurations('deal', '#deal-form');

        // اگر اطلاعات اولیه در پروفایل لید موجود است، آن بخش را مخفی کن
        const person = personForSummary || this.state.currentLead;
        const infoHasValues =
            !!(person?.city || person?.job_title || person?.job_description || person?.extra_info ||
                person?.financial_level || person?.asset_estimation || person?.income_estimation ||
                person?.acquaintance_duration || (person?.has_previous_purchase !== undefined && person?.has_previous_purchase !== null));
        this.toggleInitialInfoVisibility(!infoHasValues);
        await this.loadActivityTypesForModal();
        if (dropIfStaleDeal()) return;

	        if (!dealId) {
	            // New deal - Reset form first
	            this.state.dealHasActivity = false;
	            this.setPaymentsLockState(false);
	            document.getElementById('deal-modal-title').textContent = 'ثبت معامله جدید';
	            document.getElementById('deal-form').reset();
            document.getElementById('deal-id').value = '';
            document.getElementById('deal-contact-id').value = '';
            document.getElementById('deal-user-name').value = '';
            document.getElementById('deal-user-mobile').value = '';
            const invoiceCodeField = document.getElementById('deal-invoice-code');
            if (invoiceCodeField) invoiceCodeField.value = '';
            // New deals should start from the placeholders; the user picks the deal's own
            // marketing source instead of inheriting the lead defaults.
            this.updateDealMarketingUi(null, true, person);
	            this.state.dealActivityRequestedServices = [];
	            this.populateDealActivityServices();
	            this.renderDealActivityServices();

	            // Ensure action buttons are visible (previous deal might have hidden them)
	            const saveInfoBtn = document.getElementById('deal-info-submit-btn');
		            const savePaymentsBtn = document.querySelector("button[onclick*=\"saveDealFromTab('payments')\"]");
	            const addPaymentBtn = document.getElementById('deal-add-payment-btn');
	            const activityBtns = document.querySelectorAll('button[onclick*="startActivityForDeal"]');
	            if (saveInfoBtn) saveInfoBtn.classList.remove('hidden');
	            if (savePaymentsBtn) savePaymentsBtn.classList.remove('hidden');
	            if (addPaymentBtn) addPaymentBtn.classList.remove('hidden');
	            activityBtns.forEach(btn => btn.classList.remove('hidden'));
	            this.enableDealFormFields();
	            this.dealIsWon = false;

            // Ensure purchase field is enabled after form reset
            const purchaseSelectAfterReset = document.getElementById('deal-activity-has-purchase');
            if (purchaseSelectAfterReset) {
                purchaseSelectAfterReset.disabled = false;
                purchaseSelectAfterReset.dataset.locked = '';
                purchaseSelectAfterReset.classList.remove('bg-gray-900', 'cursor-not-allowed');
            }
	            this.state.currentDeal = null;
                this.syncParkedDealUi(null);
	            const pipelineStageSelect = document.getElementById('deal-pipeline-stage');
	            if (pipelineStageSelect) {
	                this.syncDealPipelineStageSelectOptions('');
	                pipelineStageSelect.value = '';
	                pipelineStageSelect.disabled = false;
	                pipelineStageSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
	            }
            this.renderProductOptions();
            
		            // Enable deal status select for new deals
		            let dealStatusSelect = document.getElementById('deal-status');
		        if (dealStatusSelect) {
		            dealStatusSelect.disabled = false;
		            dealStatusSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
		        }
            
            // Load contact info from currentContactId or currentLead
            const contactId = window.currentContactId;
            if (!contactId) {
                // No contact selected - show error
                Swal.fire('خطا', 'لطفاً ابتدا یک شخص را انتخاب کنید', 'error');
                return;
            }

            if (!this.state.currentLead || this.state.currentLead.didar_contact_id !== contactId) {
                this.state.currentDeals = [];
            }
            
            document.getElementById('deal-contact-id').value = contactId;
            const dealActContact = document.getElementById('deal-activity-contact-id');
            if (dealActContact) dealActContact.value = contactId;
            const dealActDeal = document.getElementById('deal-activity-deal-id');
            if (dealActDeal) dealActDeal.value = '';
            
            // Try to get from currentLead first (if profile is already loaded)
            if (this.state.currentLead && this.state.currentLead.didar_contact_id === contactId) {
                const person = this.state.currentLead;
                personForSummary = person;
                const fullName = `${person.first_name || ''} ${person.last_name || ''}`.trim() || 'بدون نام';
                document.getElementById('deal-user-name').value = fullName;
                document.getElementById('deal-user-mobile').value = person.mobile_phone || '';
            } else {
                // Load person info from API
                try {
                    const res = await this.req('get_person_profile', {contact_id: contactId}, {
                        requestKey: `dealModal:new:profile:${String(contactId || '')}`,
                        silentAbort: true
                    });
                    if (res?.aborted) return;
                    if (dropIfStaleDeal()) return;
                    if (res.status === 'success' && res.person) {
                        const person = res.person;
                        personForSummary = person;
                        const fullName = `${person.first_name || ''} ${person.last_name || ''}`.trim() || 'بدون نام';
                        document.getElementById('deal-user-name').value = fullName;
                        document.getElementById('deal-user-mobile').value = person.mobile_phone || '';
                        // Update currentLead for future use
                        this.state.currentLead = person;
                        this.state.currentDeals = res.deals || [];
                    } else {
                        console.error('Failed to load person profile:', res);
                        Swal.fire('خطا', res.message || 'خطا در بارگذاری اطلاعات شخص', 'error');
                        return;
                    }
                } catch (error) {
                    console.error('Error loading person profile:', error);
                    Swal.fire('خطا', 'خطا در بارگذاری اطلاعات شخص', 'error');
                    return;
                }
            }

            // Generate deal title for new deals
            try {
                const titleRes = await this.req('generate_deal_title', {contact_id: contactId}, {
                    requestKey: `dealModal:new:title:${String(contactId || '')}`,
                    silentAbort: true
                });
                if (titleRes?.aborted) return;
                if (dropIfStaleDeal()) return;
                if (titleRes.status === 'success' && titleRes.title) {
                    const titleSelect = document.getElementById('deal-title');
                    if (titleSelect) {
                        // Add the generated title as an option if it doesn't exist
                        const existingOption = Array.from(titleSelect.options).find(option => option.value === titleRes.title);
                        if (!existingOption) {
                            const newOption = document.createElement('option');
                            newOption.value = titleRes.title;
                            newOption.textContent = titleRes.title;
                            newOption.dataset.generated = '1';
                            titleSelect.appendChild(newOption);
                        } else {
                            existingOption.dataset.generated = '1';
                        }
                        titleSelect.value = titleRes.title;
                        const modalTitleEl = document.getElementById('deal-modal-title');
                        if (modalTitleEl) {
                            modalTitleEl.textContent = `ثبت معامله جدید: ${titleRes.title}`;
                        }
                    }
                }
            } catch (error) {
                console.error('Failed to generate deal title:', error);
                // Continue without setting title - user can select manually
            }

            this.paymentRowCounter = 0;
            // برای معامله جدید یک ردیف پرداخت خالی به‌صورت پیش‌فرض باز می‌شود تا بخش خالی نباشد؛
            // کاربر برای پرداخت‌های بعدی روی «افزودن پرداخت» می‌زند.
            this.loadPayments([], false, true);
            this.toggleDiscountFields();
            // Set default status placeholders for new deals
            dealStatusSelect = document.getElementById('deal-status');
            if (dealStatusSelect) {
                dealStatusSelect.disabled = false; // Enable for new deals
                dealStatusSelect.value = '';
                this.handleDealStatusChange();
            }
            // Reset dealIsWon for new deals
            this.dealIsWon = false;
            
            // Ensure all fields are enabled for new deals
            this.enableDealFormFields();
            this.applyDealServiceLookupPricing();

            // Summary + activity dropdown for new deal
            const stageValue = pipelineStageSelect?.value || '';
            // Prefill extra info fields from person (if available)
            const cityInput = document.getElementById('deal-activity-city');
            const jobInput = document.getElementById('deal-activity-job');
            const jobDescInput = document.getElementById('deal-activity-job-desc');
            const extraInfoInput = document.getElementById('deal-activity-extra-info');
            const acqYears = document.getElementById('deal-activity-acq-years');
            const acqMonths = document.getElementById('deal-activity-acq-months');
            const acqLess = document.getElementById('deal-activity-acq-less');
            const acqVal = personForSummary?.acquaintance_duration || '';
            if (cityInput) cityInput.value = personForSummary?.city || '';
            if (jobInput) jobInput.value = personForSummary?.job_title || '';
            if (jobDescInput) jobDescInput.value = personForSummary?.job_description || '';
            if (extraInfoInput) extraInfoInput.value = personForSummary?.extra_info || '';
            const purchaseSelect = document.getElementById('deal-activity-has-purchase');
            if (purchaseSelect) {
                // Always reset lock/disabled state for a brand-new deal
                purchaseSelect.disabled = false;
                purchaseSelect.dataset.locked = '';
                purchaseSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');

                // For new deals, always start with empty value (no default selection)
                purchaseSelect.value = '';
            }
            // Hide previous-number field by default for new deals
            const prevPurchaseField = document.getElementById('deal-previous-purchase-number-field');
            if (prevPurchaseField) prevPurchaseField.classList.add('hidden');
            this.handlePurchaseOtherNumberChange();
            if (acqYears && acqMonths && acqLess) {
                if (acqVal && acqVal.includes('کمتر')) {
                    acqLess.checked = true;
                    this.toggleAcqLessThanMonth();
                } else {
                    const match = acqVal.match(/(\d+)\s*سال.*?(\d+)\s*ماه/);
                    acqYears.value = match ? match[1] : '0';
                    acqMonths.value = match ? match[2] : '0';
                    acqLess.checked = false;
                    this.toggleAcqLessThanMonth();
                }
            }
            this.lockDealActivityExtraFields({
                city: cityInput?.value || '',
                job_title: jobInput?.value || '',
                job_description: jobDescInput?.value || '',
                extra_info: extraInfoInput?.value || '',
                financial_level: document.getElementById('deal-activity-financial-level')?.value || '',
                asset_estimation: document.getElementById('deal-activity-asset')?.value || '',
                income_estimation: document.getElementById('deal-activity-income')?.value || '',
                requested_services: this.state.dealActivityRequestedServices,
                acquaintance_duration: acqVal,
                has_previous_purchase: undefined // Always keep purchase field unlocked for new deals
            });
            this.updateDealModalSummary(personForSummary || this.state.currentLead, {
                pipeline_stage: stageValue,
                contact_name: document.getElementById('deal-user-name').value || '',
                contact_mobile: document.getElementById('deal-user-mobile').value || '',
                owner_name: (personForSummary || this.state.currentLead)?.owner_name || ''
            });
            const dealActStage = document.getElementById('deal-activity-stage');
            const dealActCurrent = document.getElementById('deal-activity-current-stage');
            if (dealActStage) dealActStage.value = stageValue;
            if (dealActCurrent) dealActCurrent.textContent = stageValue || '-';
            this.populateDealActivityStages();
            this.populateActivityDealSelect(this.state.currentDeals || []);
            this.populateDealActivityServices();
            this.showDealFormTab(requestedInitialTab);
            // فعالیت‌ها در تب فعالیت
            this.renderDealActivitiesInline(contactId);
            this.toggleDealActivityForm(false);
            
            this.openModal('modalDeal', { source: 'user', entityId: '' });
            this.applyRbacActionVisibility();
            this.applyLegacyDidarDealReadonly(null);
            return;
        }
        
        // Load deal
        const res = await this.req('get_deal', {deal_id: dealId}, {
            requestKey: `dealModal:deal:${String(dealId || '')}`,
            silentAbort: true
        });
        if (res?.aborted) return;
        if (dropIfStaleDeal()) return;
	        if (res.status === 'success' && res.deal) {
            this.setPaymentsLockState(true);
            const deal = res.deal;
            const loadedDealDidarId = deal.didar_deal_id || deal.id || '';
            this.updateDealMarketingUi(deal, false);
            const dealTitleText = deal.title || deal.pipeline_stage_title || deal.pipeline_stage || 'معامله';
            document.getElementById('deal-modal-title').textContent = `ویرایش معامله: ${dealTitleText}`;
            
            // Set deal status in form
            const dealStatusSelect = document.getElementById('deal-status');
            const isWonDeal = deal.status === 'Won';
            const pipelineStageSelect = document.getElementById('deal-pipeline-stage');
            const hasRegisteredPayments = this.dealHasRegisteredPayments(deal);
            this.state.dealInfoAdminUnlocked = false;
            
            if (dealStatusSelect) {
                dealStatusSelect.value = String(deal.status || '').toLowerCase();
                this.handleDealStatusChange();
            }
            if (pipelineStageSelect) {
                this.syncDealPipelineStageSelectOptions(deal.pipeline_stage || '');
                pipelineStageSelect.value = deal.pipeline_stage || '';
            }
            
            // Display current values in read-only fields
            const statusText = deal.status === 'Won' ? 'موفق' : (deal.status === 'Lost' ? 'ناموفق' : 'جاری');
            const currentStatusDisplay = document.getElementById('deal-current-status');
            const currentStageDisplay = document.getElementById('deal-current-stage');
            
            if (currentStatusDisplay) currentStatusDisplay.textContent = statusText;
            if (currentStageDisplay) currentStageDisplay.textContent = deal.pipeline_stage || '-';

            this.updateDealCurrentFields(deal.pipeline_stage || '', deal.status);

            // Mark products
            if (Array.isArray(deal.products)) {
                const selectedIds = deal.products.map(p => p.product_id);
                this.renderProductOptions(selectedIds);
            } else {
                this.renderProductOptions();
            }
            
            // Save deal info for course modal buttons
            this.state.currentDeal = deal;
            this.syncParkedDealUi(deal);
            this.state.currentDeals = [deal];
            this.populateDealActivityStages();
            let requestedServices = [];
            try {
                if (deal.requested_services) {
                    const parsed = JSON.parse(deal.requested_services);
                    if (Array.isArray(parsed)) requestedServices = parsed;
                }
            } catch (e) {
                // ignore parse errors
            }
            // Fallback: if عنوان حاوی | خدمات است، از آن استخراج کن
            if ((!requestedServices || requestedServices.length === 0) && deal.title && deal.title.includes('|')) {
                const parts = deal.title.split('|');
                const svc = (parts[parts.length - 1] || '').trim();
                if (svc) requestedServices = [svc];
            }
            this.populateDealActivityServices(requestedServices);

            const dealContactIdInput = document.getElementById('deal-contact-id');
            const dealActContact = document.getElementById('deal-activity-contact-id');
            const dealActDeal = document.getElementById('deal-activity-deal-id');
            const dealActStage = document.getElementById('deal-activity-stage');
            const dealActCurrent = document.getElementById('deal-activity-current-stage');
            const normalizedContactId = deal.contact_didar_id || deal.contact_id || deal.contactId || deal.local_person_id || '';
            if (dealContactIdInput) dealContactIdInput.value = normalizedContactId;
            if (dealActContact) dealActContact.value = normalizedContactId;
            if (dealActDeal) dealActDeal.value = deal.didar_deal_id || deal.id || '';
            if (dealActStage) dealActStage.value = deal.pipeline_stage || '';
            if (dealActCurrent) dealActCurrent.textContent = deal.pipeline_stage || '-';

            const setVal = (id, val) => {
                const el = document.getElementById(id);
                if (el) el.value = val ?? '';
            };
            const extra = deal.extra_fields || {};
            setVal('deal-activity-financial-level', deal.financial_level || extra.financial_level || '');
            setVal('deal-activity-asset', deal.asset_estimation || extra.asset_estimation || '');
            setVal('deal-activity-income', deal.income_estimation || extra.income_estimation || '');
            const purchaseSelect = document.getElementById('deal-activity-has-purchase');
            // Capture purchase value (from deal -> extra fields -> person) but don't lock yet
            const purchaseVal = (deal.has_previous_purchase !== undefined && deal.has_previous_purchase !== null)
                ? deal.has_previous_purchase
                : (extra.has_previous_purchase !== undefined && extra.has_previous_purchase !== null
                    ? extra.has_previous_purchase
                    : personForSummary?.has_previous_purchase);
            const hasPurchaseVal = purchaseVal === 0 || purchaseVal === 1 || purchaseVal === '0' || purchaseVal === '1';
            if (purchaseSelect) {
                purchaseSelect.value = hasPurchaseVal ? String(purchaseVal) : '';
                purchaseSelect.disabled = false;
                purchaseSelect.dataset.locked = '';
                purchaseSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
            }

            this.showDealFormTab('activities');
            this.renderDealActivitiesInline(normalizedContactId);
            const hasAct = await this.checkDealHasActivity(dealId);
            if (dropIfStaleDeal()) return;
            this.state.dealHasActivity = hasAct;
            this.toggleDealActivityForm(hasAct);
            if (purchaseSelect) {
                const shouldLockPurchase =
                    (purchaseVal === 1 || purchaseVal === '1') ||
                    (hasPurchaseVal && hasAct);
                if (shouldLockPurchase) {
                    purchaseSelect.disabled = true;
                    purchaseSelect.dataset.locked = 'true';
                    purchaseSelect.classList.add('bg-gray-900', 'cursor-not-allowed');
                } else {
                    purchaseSelect.disabled = false;
                    purchaseSelect.dataset.locked = '';
                    purchaseSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
                    purchaseSelect.value = '';
                }
            }
            
            // Check if deal is Won or Lost
            const isWonOrLost = deal.status === 'Won' || deal.status === 'Lost';
            
            // For won or lost deals, disable form fields and actions
            const saveInfoBtn = document.getElementById('deal-info-submit-btn');
            const savePaymentsBtn = document.querySelector('button[onclick*="saveDealFromTab(\'payments\')"]');
            const addPaymentBtn = document.getElementById('deal-add-payment-btn');
            const activityBtns = document.querySelectorAll('button[onclick*="startActivityForDeal"]');
            
            if (isWonOrLost) {
                const isAdminUser = this.isAdmin();
                const canAdminUnlockPaid = isAdminUser && hasRegisteredPayments;
                // Hide action buttons
                if (saveInfoBtn) saveInfoBtn.classList.toggle('hidden', !canAdminUnlockPaid);
                if (savePaymentsBtn) savePaymentsBtn.classList.toggle('hidden', !isAdminUser);
                if (addPaymentBtn) addPaymentBtn.classList.toggle('hidden', !isAdminUser);
                activityBtns.forEach(btn => btn.classList.add('hidden'));
                this.setDealInfoLockState(true, { allowAdminUnlock: canAdminUnlockPaid });
                
                // Store readonly state for existing payments (only for won deals)
                if (isWonDeal) {
                    this.dealIsWon = true;
                } else {
                    this.dealIsWon = false;
                }
            } else {
                // Enable deal status select for pending deals
                if (dealStatusSelect) {
                    dealStatusSelect.disabled = false;
                    dealStatusSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
                }
                if (pipelineStageSelect) {
                    pipelineStageSelect.disabled = false;
                    pipelineStageSelect.classList.remove('bg-gray-900', 'cursor-not-allowed');
                }

                // Show action buttons
                if (saveInfoBtn) saveInfoBtn.classList.remove('hidden');
                if (savePaymentsBtn) savePaymentsBtn.classList.remove('hidden');
                if (addPaymentBtn) addPaymentBtn.classList.remove('hidden');
                activityBtns.forEach(btn => btn.classList.remove('hidden'));
                
                // Re-enable all other fields
                this.enableDealFormFields();
                this.setDealInfoLockState(hasRegisteredPayments, { allowAdminUnlock: hasRegisteredPayments });

                this.dealIsWon = false;
            }
            
	            // Load failure reason if deal is lost
	            if (deal.status === 'Lost') {
	                const failureSection = document.getElementById('deal-failure-reason-section');
	                const failureReasonSelect = document.getElementById('deal-failure-reason');
	                const failureOtherDiv = document.getElementById('deal-failure-other-reason');
	                const failureOtherDesc = document.getElementById('deal-failure-other-description');

	                if (failureSection) failureSection.classList.remove('hidden');
	                if (failureReasonSelect) {
	                    failureReasonSelect.value = deal.failure_reason_code || '';
	                    failureReasonSelect.disabled = true; // Closed deal => read-only
	                }
	                if (failureOtherDiv) failureOtherDiv.classList.remove('hidden');
	                if (failureOtherDesc) {
	                    failureOtherDesc.value = deal.failure_reason_description || '';
	                    failureOtherDesc.readOnly = true;
	                }
	            } else {
	                const failureSection = document.getElementById('deal-failure-reason-section');
	                const failureReasonSelect = document.getElementById('deal-failure-reason');
	                const failureOtherDiv = document.getElementById('deal-failure-other-reason');
	                const failureOtherDesc = document.getElementById('deal-failure-other-description');
	                if (failureSection) failureSection.classList.add('hidden');
	                if (failureReasonSelect) {
	                    failureReasonSelect.value = '';
	                    failureReasonSelect.disabled = false;
	                }
	                if (failureOtherDiv) failureOtherDiv.classList.add('hidden');
	                if (failureOtherDesc) {
	                    failureOtherDesc.value = '';
	                    failureOtherDesc.readOnly = false;
	                }
	            }
            
            document.getElementById('deal-id').value = deal.didar_deal_id || deal.id;
            document.getElementById('deal-contact-id').value = normalizedContactId;
            
            // Load contact info - try from deal first, then from API if missing
            if (deal.contact_name && deal.contact_mobile) {
                document.getElementById('deal-user-name').value = deal.contact_name;
                document.getElementById('deal-user-mobile').value = deal.contact_mobile;
                personForSummary = personForSummary || {
                    first_name: deal.contact_name,
                    mobile_phone: deal.contact_mobile,
                    owner_name: deal.owner_name
                };
            }
            
            if (!personForSummary && (deal.local_person_id || deal.contact_didar_id || deal.contact_id || deal.contactId)) {
                // Load person info from API if not in deal data
                try {
                    const profileContactId = normalizedContactId;
                    const personRes = await this.req('get_person_profile', {contact_id: profileContactId}, {
                        requestKey: `dealModal:dealProfile:${String(profileContactId || '')}`,
                        silentAbort: true
                    });
                    if (personRes?.aborted) return;
                    if (dropIfStaleDeal()) return;
                    if (personRes.status === 'success' && personRes.person) {
                        const person = personRes.person;
                        personForSummary = person;
                        this.state.currentLead = person;
                        this.state.currentDeals = personRes.deals || [deal];
                        const fullName = `${person.first_name || ''} ${person.last_name || ''}`.trim() || 'بدون نام';
                        document.getElementById('deal-user-name').value = fullName;
                        document.getElementById('deal-user-mobile').value = person.mobile_phone || '';
                    } else {
                        document.getElementById('deal-user-name').value = deal.contact_name || '';
                        document.getElementById('deal-user-mobile').value = deal.contact_mobile || '';
                    }
                } catch (error) {
                    console.error('Error loading person profile for deal:', error);
                    document.getElementById('deal-user-name').value = deal.contact_name || '';
                    document.getElementById('deal-user-mobile').value = deal.contact_mobile || '';
                }
            } else if (!personForSummary) {
                document.getElementById('deal-user-name').value = deal.contact_name || '';
                document.getElementById('deal-user-mobile').value = deal.contact_mobile || '';
            }

            // Update summary & activity dropdown with latest data
            const cityInput = document.getElementById('deal-activity-city');
            const jobInput = document.getElementById('deal-activity-job');
            const jobDescInput = document.getElementById('deal-activity-job-desc');
            const extraInfoInput = document.getElementById('deal-activity-extra-info');
            const acqYears = document.getElementById('deal-activity-acq-years');
            const acqMonths = document.getElementById('deal-activity-acq-months');
            const acqLess = document.getElementById('deal-activity-acq-less');
            
            // Prioritize currentLead (latest data) over deal snapshot
            const currentPerson = (this.state.currentLead && (
                                    this.state.currentLead.id == deal.local_person_id || 
                                    this.state.currentLead.didar_contact_id === deal.contact_didar_id || 
                                    this.state.currentLead.didar_contact_id === deal.contact_id
                                )) 
                                ? this.state.currentLead 
                                : null;
            const getVal = (prop) => currentPerson?.[prop] || deal[prop] || personForSummary?.[prop] || '';

            const acqVal = getVal('acquaintance_duration');
            if (cityInput) cityInput.value = getVal('city');
            if (jobInput) jobInput.value = getVal('job_title');
            if (jobDescInput) jobDescInput.value = getVal('job_description');
            if (extraInfoInput) extraInfoInput.value = getVal('extra_info');
            if (purchaseSelect) {
                purchaseSelect.value = hasPurchaseVal ? String(purchaseVal) : '';
            }
            if (acqYears && acqMonths && acqLess) {
                if (acqVal && acqVal.includes('کمتر')) {
                    acqLess.checked = true;
                    this.toggleAcqLessThanMonth();
                } else {
                    const match = acqVal.match(/(\d+)\s*سال.*?(\d+)\s*ماه/);
                    acqYears.value = match ? match[1] : '0';
                    acqMonths.value = match ? match[2] : '0';
                    acqLess.checked = false;
                    this.toggleAcqLessThanMonth();
                }
            }
            this.lockDealActivityExtraFields({
                city: cityInput?.value || '',
                job_title: jobInput?.value || '',
                job_description: jobDescInput?.value || '',
                extra_info: extraInfoInput?.value || '',
                financial_level: deal.financial_level || personForSummary?.financial_level || '',
                asset_estimation: deal.asset_estimation || personForSummary?.asset_estimation || '',
                income_estimation: deal.income_estimation || personForSummary?.income_estimation || '',
                requested_services: requestedServices,
                acquaintance_duration: acqVal,
                has_previous_purchase: (
                    (purchaseVal === 1 || purchaseVal === '1') ||
                    (hasPurchaseVal && hasAct)
                ) ? purchaseVal : undefined
            });
            this.updateDealModalSummary(personForSummary || this.state.currentLead, deal);
            
            // Update deal lead info display when opening existing deal
            const dealInfoDisplay = document.getElementById('deal-lead-info-display');
            const dealNameText = document.getElementById('deal-lead-name-text');
            if (dealInfoDisplay && dealNameText) {
                // Use person from deal or current lead - prioritize personForSummary
                const person = personForSummary || this.state.currentLead;
                const dealContactId = deal.contact_didar_id || deal.contact_id || '';
                const currentContactId = window.currentContactId || '';
                
                // If deal belongs to different contact than current, show warning
                if (dealContactId && currentContactId && dealContactId !== currentContactId) {
                    // This deal belongs to a different contact
                    const contactName = deal.contact_name || 'بدون نام';
                    dealNameText.textContent = `${contactName} (${deal.contact_mobile || '-'}) - ⚠️ این معامله متعلق به لید دیگری است`;
                    dealInfoDisplay.classList.remove('hidden');
                    dealInfoDisplay.classList.add('border-red-500'); // Add red border to indicate warning
                } else if (person && (
                    person.didar_contact_id === dealContactId || 
                    person.id == deal.local_person_id ||
                    (!dealContactId && person.didar_contact_id === currentContactId)
                )) {
                    const fullName = `${person.first_name || ''} ${person.last_name || ''}`.trim() || 'بدون نام';
                    dealNameText.textContent = `${fullName} (${person.mobile_phone || '-'})`;
                    dealInfoDisplay.classList.remove('hidden');
                    dealInfoDisplay.classList.remove('border-red-500');
                } else if (deal.contact_name) {
                    dealNameText.textContent = `${deal.contact_name} (${deal.contact_mobile || '-'})`;
                    dealInfoDisplay.classList.remove('hidden');
                    dealInfoDisplay.classList.remove('border-red-500');
                } else {
                    dealInfoDisplay.classList.add('hidden');
                }
            }
            
            this.populateActivityDealSelect(this.state.currentDeals || [deal], deal.didar_deal_id || deal.id);

            // ریست گزینه‌های خدمات بر اساس محصولات ثبت‌شده و سپس انتخاب مقدار معتبر
            const titleSelect = document.getElementById('deal-title');
            if (titleSelect) {
                this.renderDealTitleOptions(titleSelect.value || '');
            }
            let options = titleSelect ? Array.from(titleSelect.options || []) : [];

            // اگر requested_services موجود است، اولین مقدار را برای انتخاب استفاده کن
            let candidateService = '';
            const dealTitleRaw = String(deal.title || '').trim();
            if (dealTitleRaw && options.some(o => o.value === dealTitleRaw)) {
                candidateService = dealTitleRaw;
            } else if (dealTitleRaw && dealTitleRaw.includes('|')) {
                const parts = deal.title.split('|');
                candidateService = (parts[parts.length - 1] || '').trim();
            }
            if (!candidateService) {
                candidateService = (requestedServices && requestedServices[0]) || '';
            }

            if (titleSelect) {
                let hasOption = options.some(o => o.value === candidateService);
                if (!hasOption && candidateService) {
                    const legacyOption = document.createElement('option');
                    legacyOption.value = candidateService;
                    legacyOption.textContent = candidateService;
                    legacyOption.dataset.generated = '1';
                    titleSelect.appendChild(legacyOption);
                    hasOption = true;
                    options = Array.from(titleSelect.options || []);
                }
                titleSelect.value = hasOption ? candidateService : '';
            }
        const infoHasValues =
            !!(deal.city || deal.job_title || deal.job_description || deal.extra_info ||
                deal.financial_level || deal.asset_estimation || deal.income_estimation ||
                deal.acquaintance_duration || deal.has_previous_purchase !== undefined);
        this.toggleInitialInfoVisibility(!infoHasValues);
            document.getElementById('deal-service-cost').value = formatMoneyString(deal.service_cost || '');
                const invoiceCodeField = document.getElementById('deal-invoice-code');
                if (invoiceCodeField) invoiceCodeField.value = deal.invoice_code || '';
            document.getElementById('deal-price-list-code').value = deal.price_list_code || '';
            document.getElementById('deal-has-discount').value = deal.has_discount || 0;
            document.getElementById('deal-discount-type').value = deal.discount_type || '';
            document.getElementById('deal-discount-amount').value = formatMoneyString(deal.discount_amount || '');
            const refundAmountEl = document.getElementById('deal-refund-amount');
            const refundDescEl = document.getElementById('deal-refund-description');
            if (refundAmountEl) refundAmountEl.value = formatMoneyString(deal.refund_amount || '');
            if (refundDescEl) refundDescEl.value = deal.refund_description || '';
            document.getElementById('deal-payable-amount').value = formatMoneyString(deal.payable_amount || '');
            document.getElementById('deal-payment-conditions').value = deal.payment_conditions || '';
            
            // Handle discount occasion
            if (deal.discount_occasion) {
                const occasionMap = {
                    'black_friday': 'black_friday',
                    'teacher_birthday': 'teacher_birthday',
                    'nowruz': 'nowruz'
                };
                if (occasionMap[deal.discount_occasion]) {
                    document.getElementById('deal-discount-occasion').value = deal.discount_occasion;
                } else {
                    document.getElementById('deal-discount-occasion').value = 'other';
                    document.getElementById('deal-discount-occasion-custom').value = deal.discount_occasion;
                }
            }
            const serviceCostField = document.getElementById('deal-service-cost');
            const payableAmountField = document.getElementById('deal-payable-amount');
            const priceListCodeField = document.getElementById('deal-price-list-code');
            if (titleSelect) {
                titleSelect.dataset.lastSelectedService = candidateService || '';
            }
            if (serviceCostField) {
                this.clearDealFieldLookupAutofill(serviceCostField);
            }
            if (payableAmountField) {
                this.clearDealFieldLookupAutofill(payableAmountField);
            }
            if (priceListCodeField) {
                this.clearDealFieldLookupAutofill(priceListCodeField);
            }
            this.applyDealServiceLookupPricing();
            
            // Load payments
            this.paymentRowCounter = 0;
            let payments = [];
            console.log('Loading payments for deal:', deal);
            
            if (deal.payments && Array.isArray(deal.payments) && deal.payments.length > 0) {
                console.log('Using payments array:', deal.payments);
                payments = deal.payments.map(p => {
                    // Ensure date_persian exists
                    let datePersian = p.date_persian || '';
                    if (!datePersian && p.date) {
                        // Try to convert Gregorian to Persian
                        try {
                            const gDate = new Date(p.date);
                            if (!isNaN(gDate.getTime())) {
                                const pDate = new persianDate(gDate);
                                datePersian = pDate.format('YYYY/MM/DD');
                            } else {
                                // If date is already in Persian format, use it as is
                                datePersian = p.date;
                            }
                        } catch(e) {
                            datePersian = p.date || '';
                        }
                    }
                    
                    // Normalize payment keys to standard format
                    // Support both card_last4 and card_last_4, but use card_last4 as standard
                    const cardLast4 = p.card_last4 || p.card_last_4 || '';
                    const destinationBank = p.destination_bank || p.bank_destination || '';
                    const method = p.method || p.payment_method || '';
                    
                    const paymentObj = {
                        ...p,
                        date_persian: datePersian,
                        date: p.date || '',
                        method: method,
                        card_last4: cardLast4,  // Always use card_last4 (standard)
                        destination_bank: destinationBank,  // Always use destination_bank (standard)
                        destination_bank_other: p.destination_bank_other || '',
                        destination_card_number: String(p.destination_card_number || '').replace(/\D/g, '').slice(-4),
                        destination_card_number_other: p.destination_card_number_other || '',
                        depositor_name: p.depositor_name || p.payer_name || p.payment_depositor_name || '',
                        destination_bank_title: p.destination_bank_title || '',
                        destination_card_full: p.destination_card_full || '',
                        destination_account_number: p.destination_account_number || '',
                        destination_sheba: p.destination_sheba || '',
                        destination_account_owner: p.destination_account_owner || '',
                        card_reader_device: p.card_reader_device || '',
                        currency: p.currency || '',
                        currency_other: p.currency_other || '',
                        currency_amount: p.currency_amount || '',
                        check_bank_name: p.check_bank_name || '',
                        check_date: p.check_date || '',
                        check_number: p.check_number || '',
                        check_owner_name: p.check_owner_name || '',
                        check_sayadi_image_path: p.check_sayadi_image_path || ''
                    };
                    console.log('Processed payment:', paymentObj);
                    return paymentObj;
                });
            } else if (deal.payment_amount || deal.payment_method) {
                console.log('Using legacy payment fields');
                // Legacy single payment - convert date if needed
                let persianDate = '';
                if (deal.payment_date) {
                    try {
                        const gDate = new Date(deal.payment_date);
                        if (!isNaN(gDate.getTime())) {
                            const pDate = new persianDate(gDate);
                            persianDate = pDate.format('YYYY/MM/DD');
                        } else {
                            // If date is already in Persian format, use it as is
                            persianDate = deal.payment_date;
                        }
                    } catch(e) {
                        persianDate = deal.payment_date || '';
                    }
                }
                payments = [{
                    date: deal.payment_date || '',
                    date_persian: persianDate,
                    amount: deal.payment_amount || '',
                    method: deal.payment_method || '',
                    description: deal.payment_description || ''
                }];
                console.log('Legacy payment:', payments[0]);
            }
            
            console.log('Final payments array:', payments);
            // Pass null to loadPayments so it can decide readonly based on payment data.
            // addDefault=true: when an existing deal has no payments, still show one
            // empty starter row so the payments tab isn't blank.
            this.loadPayments(payments, null, true);

            this.setDealServiceCostLookupLock(false);
            this.toggleDiscountFields();
            this.toggleDiscountOccasion();
            this.toggleCustomOccasion();

            // نمایش/پنهان کردن دکمه فعال‌سازی بر اساس وضعیت معامله
            const reactivateBtn = document.getElementById('reactivate-deal-btn');
            if (reactivateBtn) {
                const isClosed = deal.status === 'Won' || deal.status === 'Lost';
                reactivateBtn.style.display = this.isAdmin() && isClosed ? 'inline-block' : 'none';
            }
            const forceSuccessBtn = document.getElementById('force-success-exit-kariz-btn');
            if (forceSuccessBtn) {
                const hasDealId = !!(deal.didar_deal_id || dealId || deal.id);
                const serviceCost = Number(deal.service_cost || deal.payable_amount || 0);
                const paymentAmount = Number(deal.payment_amount || 0);
                const outstandingAmount = Math.max(serviceCost - paymentAmount, 0);
                const canForceExitKariz = hasDealId && deal.status === 'Won' && outstandingAmount <= 0.0001;
                forceSuccessBtn.style.display = canForceExitKariz ? 'inline-block' : 'none';
            }

            this.showDealFormTab(requestedInitialTab);
            this.openModal('modalDeal', { source: opts.source || 'user', entityId: String(dealId || deal.didar_deal_id || deal.id || '') });
            this.applyRbacActionVisibility();
            this.applyLegacyDidarDealReadonly(deal);
        } else {
            Swal.fire('خطا', res.message || 'خطا در بارگذاری معامله', 'error');
        }
    },
    
    // PAYMENT MANAGEMENT
    paymentRowCounter: 0,

    isDeletedPayment(payment = {}) {
        const isTruthyFlag = (value) => {
            if (value === true || value === 1) return true;
            const normalized = String(value ?? '').trim().toLowerCase();
            return normalized === '1' || normalized === 'true' || normalized === 'yes';
        };
        return !!(payment && (isTruthyFlag(payment.deleted) || isTruthyFlag(payment.is_deleted)));
    },

    isLegacyDidarDeal(deal = null) {
        const source = deal || this.state.currentDeal || {};
        const value = source.is_legacy_didar ?? source.legacy_didar ?? 0;
        return value === true || value === 1 || String(value).trim() === '1';
    },

    getLegacyDidarReadonlyMessage() {
        return 'این معامله از معاملات گذشته CRM خارجی است و فقط قابل مشاهده است.';
    },

    blockLegacyDidarMutation() {
        Swal.fire('معامله فقط خواندنی است', this.getLegacyDidarReadonlyMessage(), 'info');
    },

    applyLegacyDidarDealReadonly(deal = null) {
        const locked = this.isLegacyDidarDeal(deal);
        const banner = document.getElementById('deal-legacy-didar-lock-banner');
        if (banner) banner.classList.toggle('hidden', !locked);
        const modal = document.getElementById('modalDeal');
        if (!modal) return;

        const mutatingButtonSelectors = [
            '#deal-info-submit-btn',
            '#deal-add-payment-btn',
            '#deal-payments-submit-btn',
            '#deal-paid-edit-toggle-btn',
            '#move-to-parked-btn',
            '#move-out-of-parked-btn',
            '#reactivate-deal-btn',
            '#force-success-exit-kariz-btn',
            'button[onclick*="deleteDealFromModal"]',
            'button[onclick*="startActivityForDeal"]',
            'button[onclick*="openPaymentRefundModal"]',
            'button[onclick*="editExistingPayment"]',
            'button[onclick*="deleteExistingPayment"]',
            'button[onclick*="togglePaymentRowEdit"]',
            'button[onclick*="removePaymentRow"]'
        ];
        modal.querySelectorAll(mutatingButtonSelectors.join(',')).forEach((button) => {
            if (locked) {
                button.dataset.legacyReadonlyPrevHidden = button.classList.contains('hidden') ? '1' : '0';
                button.dataset.legacyReadonlyPrevDisabled = button.disabled ? '1' : '0';
                button.classList.add('hidden');
                button.disabled = true;
            } else if (button.dataset.legacyReadonlyPrevHidden !== undefined) {
                button.classList.toggle('hidden', button.dataset.legacyReadonlyPrevHidden === '1');
                button.disabled = button.dataset.legacyReadonlyPrevDisabled === '1';
                delete button.dataset.legacyReadonlyPrevHidden;
                delete button.dataset.legacyReadonlyPrevDisabled;
            }
        });

        modal.querySelectorAll('#deal-form input, #deal-form select, #deal-form textarea').forEach((field) => {
            const type = String(field.type || '').toLowerCase();
            if (type === 'hidden') return;
            if (locked) {
                field.dataset.legacyReadonlyPrevDisabled = field.disabled ? '1' : '0';
                field.dataset.legacyReadonlyPrevReadonly = field.readOnly ? '1' : '0';
                if (field.tagName === 'SELECT') {
                    field.disabled = true;
                } else {
                    field.readOnly = true;
                    if (type === 'checkbox' || type === 'radio' || type === 'file') field.disabled = true;
                }
                field.classList.add('bg-gray-900', 'cursor-not-allowed', 'opacity-70');
            } else if (field.dataset.legacyReadonlyPrevDisabled !== undefined) {
                field.disabled = field.dataset.legacyReadonlyPrevDisabled === '1';
                field.readOnly = field.dataset.legacyReadonlyPrevReadonly === '1';
                delete field.dataset.legacyReadonlyPrevDisabled;
                delete field.dataset.legacyReadonlyPrevReadonly;
                field.classList.remove('bg-gray-900', 'cursor-not-allowed', 'opacity-70');
            }
        });

        this.updatePaymentsSubmitState();
        this.syncDealCancelButton();
    },

    setPaymentsLockState(isLocked) {
        this.state.paymentsLocked = !!isLocked;
        const addBtn = document.getElementById('deal-add-payment-btn');
        if (addBtn) {
            addBtn.disabled = false;
            addBtn.title = isLocked ? 'پرداخت‌های ثبت‌شده قابل ویرایش نیستند؛ برای ثبت پرداخت جدید کلیک کنید' : '';
            addBtn.classList.remove('opacity-50', 'pointer-events-none');
        }
    },

    collectCurrentPayments() {
        const rows = Array.from(document.querySelectorAll('#payment-rows > [data-existing-payment]'))
            .filter(row => row.offsetParent !== null);
        const get = (row, selector) => row.querySelector(selector)?.value || '';
        const list = rows.map((row) => {
            const method = get(row, 'select[name="payment_method[]"]');
            const datePersian = get(row, 'input[name="payment_date[]"]');
            return {
                date_persian: datePersian,
                amount: get(row, 'input[name="payment_amount[]"]'),
                method,
                description: get(row, 'textarea[name="payment_description[]"]'),
                card_last4: get(row, 'input[name="payment_card_last4[]"]'),
                destination_bank: get(row, 'select[name="destination_bank[]"]'),
                destination_bank_other: get(row, 'input[name="destination_bank_other[]"]'),
                account_owner_name: get(row, 'input[name="account_owner_name[]"]'),
                depositor_name: get(row, 'input[name="depositor_name[]"]'),
                destination_card_number: String(get(row, 'input[name="destination_card_number[]"]')).replace(/\D/g, '').slice(-4),
                destination_card_number_other: '',
                destination_bank_title: get(row, 'input[name="destination_bank_title[]"]'),
                destination_card_full: get(row, 'input[name="destination_card_full[]"]'),
                destination_account_number: get(row, 'input[name="destination_account_number[]"]'),
                destination_sheba: get(row, 'input[name="destination_sheba[]"]'),
                destination_account_owner: get(row, 'input[name="destination_account_owner[]"]'),
                card_reader_device: get(row, 'select[name="card_reader_device[]"]'),
                currency: get(row, 'select[name="payment_currency[]"]'),
                currency_other: get(row, 'input[name="payment_currency_other[]"]'),
                currency_amount: get(row, 'input[name="payment_currency_amount[]"]'),
                check_bank_name: get(row, 'input[name="check_bank_name[]"]'),
                check_date: get(row, 'input[name="check_date[]"]'),
                check_number: toEnglishDigits(String(get(row, 'input[name="check_number[]"]'))).replace(/\D/g, ''),
                check_owner_name: get(row, 'input[name="check_owner_name[]"]'),
                registered_at: get(row, 'input[name="payment_registered_at[]"]'),
                deposit_date_persian: get(row, 'input[name="deposit_date[]"]'),
                check_due_date_persian: this.isCheckMethod(method) ? datePersian : get(row, 'input[name="check_due_date[]"]'),
                sayadi_date: get(row, 'input[name="sayadi_date[]"]'),
                check_delivery_date_persian: get(row, 'input[name="check_delivery_date[]"]'),
                check_recipient_name: get(row, 'input[name="check_recipient_name[]"]'),
                receipt_image_path: get(row, 'input[name="receipt_image_path[]"]'),
                check_image_path: get(row, 'input[name="check_image_path[]"]'),
                check_sayadi_image_path: get(row, 'input[name="check_sayadi_image_path[]"]'),
                refund_amount: parseMoneyValue(get(row, 'input[name="payment_refund_amount[]"]') || ''),
                refund_description: get(row, 'textarea[name="payment_refund_description[]"]'),
                refund_count: parseInt(get(row, 'input[name="payment_refund_count[]"]') || '0', 10) || 0,
                registered_by_didar_id: get(row, 'input[name="payment_registered_by_didar_id[]"]'),
                registered_by_name: get(row, 'input[name="payment_registered_by_name[]"]'),
                payment_owner_user_id: get(row, 'input[name="payment_owner_user_id[]"]'),
                payment_owner_didar_id: get(row, 'input[name="payment_owner_didar_id[]"]'),
                payment_owner_name_snapshot: get(row, 'input[name="payment_owner_name_snapshot[]"]'),
                payment_owner_locked_at: get(row, 'input[name="payment_owner_locked_at[]"]'),
                payment_owner_source: get(row, 'input[name="payment_owner_source[]"]'),
                payment_sequence: parseInt(get(row, 'input[name="payment_sequence[]"]') || '0', 10) || ''
            };
        });
        return list.filter(p =>
            p.date_persian || p.amount || p.method || p.description ||
            p.card_last4 || p.destination_bank || p.destination_bank_other ||
            p.account_owner_name || p.depositor_name ||
            p.destination_card_number || p.destination_card_full || p.destination_account_number || p.destination_sheba || p.destination_account_owner ||
            p.card_reader_device || p.currency || p.currency_other ||
            p.currency_amount || p.check_bank_name || p.check_date || p.check_number || p.check_owner_name ||
            p.refund_amount || p.refund_description || p.refund_count
        );
    },

    // True when the new-payment form (#payment-rows) holds an in-progress, unsaved entry:
    // either the user typed into a "افزودن پرداخت" row or is mid-edit of a payment. Saved
    // payments never live here (they render in the history section via lastPaymentsCache),
    // so this only ever reflects unpersisted input. Unlike collectCurrentPayments() it does
    // NOT require the rows to be visible, so it works while the payments tab is hidden.
    // Auto-prefilled fields (registration date, sequence, default method) are ignored.
    hasUnsavedPaymentDraft() {
        const container = document.getElementById('payment-rows');
        if (!container) return false;
        const rows = Array.from(container.querySelectorAll('[data-existing-payment]'));
        return rows.some(row => {
            const val = sel => String(row.querySelector(sel)?.value || '').trim();
            return !!(
                val('input[name="payment_amount[]"]') ||
                val('input[name="payment_date[]"]') ||
                val('textarea[name="payment_description[]"]') ||
                val('input[name="payment_card_last4[]"]') ||
                val('input[name="destination_card_number[]"]') ||
                val('input[name="account_owner_name[]"]') ||
                val('input[name="depositor_name[]"]') ||
                val('input[name="check_number[]"]') ||
                val('input[name="check_bank_name[]"]') ||
                val('input[name="currency_amount[]"]') ||
                val('input[name="destination_account_number[]"]') ||
                val('input[name="destination_sheba[]"]') ||
                val('input[name="payment_refund_amount[]"]') ||
                val('textarea[name="payment_refund_description[]"]') ||
                val('input[name="payment_refund_count[]"]')
            );
        });
    },

    normalizePaymentMethodCode(methodValue) {
        return String(methodValue || '').trim().toLowerCase().replace(/[\s\-\/]+/g, '_');
    },

    isShebaMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code.includes('sheba') || code.includes('shaba') || code.includes('pol') || code.includes('pool') || code.includes('پل');
    },

    isCardToCardMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code === 'card_to_card' || code === 'card_to_card_lookup' || code === 'card_to_card_transfer';
    },

    isCardReaderMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code === 'card_reader' || code === 'pos' || code === 'pos_machine';
    },

    isCashMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code === 'cash' || code === 'نقد' || code === 'وجه_نقد';
    },

    isCheckMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code === 'check' || code === 'cheque' || code === 'چک';
    },

    isSudranGatewayMethod(methodValue) {
        const code = this.normalizePaymentMethodCode(methodValue);
        return code === 'sudran_payment_gateway' || code === 'sudran_gateway' || code === 'soodran_payment_gateway'
            || code === 'soodran_gateway' || code === 'sodran_payment_gateway'
            || code.includes('سودران');
    },

    getPaymentMethodOptionValue(predicate, fallback = '') {
        const options = this.getLookupOptions('payment_methods');
        const match = options.find(opt => predicate(opt.value) || predicate(opt.label));
        return match?.value || fallback;
    },

    paymentHasCheckFields(payment = {}) {
        return [
            payment.check_bank_name,
            payment.check_owner_name,
            payment.check_number,
            payment.check_date,
            payment.check_due_date,
            payment.check_due_date_persian,
            payment.sayadi_date,
            payment.check_delivery_date,
            payment.check_delivery_date_persian,
            payment.check_recipient_name,
            payment.check_image_path,
            payment.check_sayadi_image_path
        ].some(value => String(value || '').trim() !== '');
    },

    getEffectivePaymentMethod(payment = {}) {
        const method = payment?.method || payment?.payment_method || '';
        if (!this.isCheckMethod(method) && this.paymentHasCheckFields(payment)) {
            return this.getPaymentMethodOptionValue(value => this.isCheckMethod(value), 'چک');
        }
        return method;
    },

    getPaymentDateLabel(methodValue) {
        if (this.isCheckMethod(methodValue)) return 'تاریخ سررسید چک (شمسی)';
        if (this.isCardReaderMethod(methodValue) || this.isCardToCardMethod(methodValue) || this.isShebaMethod(methodValue) || this.isSudranGatewayMethod(methodValue)) return 'تاریخ واریز (شمسی)';
        return 'تاریخ پرداخت (شمسی)';
    },

    // The main payment date input doubles as the cheque DUE date on cheque rows (future allowed),
    // but is the deposit/payment date for every other method (must NOT be in the future).
    // Re-applies the picker's maxDate based on the row's current method; called on init and on
    // every method change so switching to/from cheque flips the future-date rule live.
    applyPaymentDateLimit(row) {
        if (!row) return;
        if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') return;
        const input = row.querySelector('.payment-date-jalali');
        if (!input || !input._neoJdp) return; // picker not attached yet
        const method = row.querySelector('select[name="payment_method[]"]')?.value || '';
        const allowFuture = this.isCheckMethod(method);
        const today = this.getTodayGregorianDateOnly();
        try { $(input).persianDatepicker({ maxDate: allowFuture ? null : today }); } catch (_) {}
        if (!allowFuture) {
            const norm = this.normalizeJalaliInputValue(input.value || '');
            const g = norm ? this.jalaliToGregorianDateOnly(norm, false) : '';
            if (g && g > today) input.value = toPersianDate(today) || input.value; // clamp an already-typed future deposit date
        }
    },

    getPaymentCardDateLabel(methodValue) {
        if (this.isCheckMethod(methodValue)) return 'تاریخ سررسید چک';
        if (this.isCardReaderMethod(methodValue) || this.isCardToCardMethod(methodValue) || this.isShebaMethod(methodValue) || this.isSudranGatewayMethod(methodValue)) return 'تاریخ واریز';
        return 'تاریخ پرداخت';
    },

    normalizeTimeValue(timeValue) {
        const raw = String(timeValue || '').trim();
        const match = raw.match(/^(\d{1,2}):(\d{2})/);
        if (!match) return '';
        const hour = Number(match[1]);
        const minute = Number(match[2]);
        if (!Number.isInteger(hour) || !Number.isInteger(minute) || hour < 0 || hour > 23 || minute < 0 || minute > 59) {
            return '';
        }
        return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
    },

    getSystemTimeValue() {
        const now = new Date();
        const hour = now.getHours();
        const minute = now.getMinutes();
        const roundedMinute = Math.round(minute / 5) * 5;
        const adjustedHour = roundedMinute >= 60 ? (hour + 1) % 24 : hour;
        const adjustedMinute = roundedMinute >= 60 ? 0 : roundedMinute;
        return `${String(adjustedHour).padStart(2, '0')}:${String(adjustedMinute).padStart(2, '0')}`;
    },

    ensurePaymentTimeDefault(timeInput) {
        if (!timeInput) return;
        const normalized = this.normalizeTimeValue(timeInput.value);
        if (normalized) {
            timeInput.value = normalized;
            return;
        }
        timeInput.value = this.getSystemTimeValue();
    },

    normalizePaymentUploadUrl(path, download = false) {
        const raw = String(path || '').trim();
        if (!raw || raw === '#') return '#';
        if (/^(https?:|blob:|data:)/i.test(raw)) return raw;
        const withoutPublic = raw.replace(/^public\//, '');
        const clean = withoutPublic.replace(/^\/+/, '');
        if (!clean.startsWith('uploads/payments/')) return '#';
        // Route through the PHP serve endpoint instead of a guessed /public/ static URL:
        // works regardless of the deployment's document root, sets the right Content-Type,
        // is auth-gated, and supports download mode. Fixes "مشاهده فیش" showing nothing.
        return '?api=payment_file&path=' + encodeURIComponent(clean) + (download ? '&download=1' : '');
    },
    
    async uploadPaymentFile(inputEl, kind) {
        const file = inputEl?.files?.[0];
        if (!file) return;
        if (file.size > 5 * 1024 * 1024) {
            Swal.fire('خطا', 'حجم فایل باید حداکثر ۵ مگابایت باشد', 'error');
            inputEl.value = '';
            return;
        }
        const wrap = inputEl.closest('div');
        const hidden = wrap?.querySelector('input[type="hidden"]');
        const link = wrap?.querySelector('a');
        const fd = new FormData();
        fd.append('file', file);
        fd.append('kind', kind || 'receipt');
        try {
            Swal.fire({ title: 'در حال بارگذاری...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
            const r = await fetch('?api=upload_payment_file', { method: 'POST', body: fd });
            const data = await r.json();
            Swal.close();
            if (r.ok && data.status === 'success' && data.path) {
                if (hidden) hidden.value = data.path;
                if (link) { link.href = this.normalizePaymentUploadUrl(data.path); link.classList.remove('hidden'); }
                this.updatePaymentsSubmitState();
            } else {
                Swal.fire('خطا', data.message || 'بارگذاری ناموفق بود', 'error');
                inputEl.value = '';
            }
        } catch (e) {
            Swal.close();
            console.error('uploadPaymentFile error', e);
            Swal.fire('خطا', 'خطا در بارگذاری فایل', 'error');
            inputEl.value = '';
        }
    },

    isOtherPaymentMethod(value, label = '') {
        const code = this.normalizePaymentMethodCode(value);
        if (code === 'other' || code === 'سایر' || code === 'دیگر') return true;
        const text = String(label || '').trim();
        return text === 'سایر' || text === 'دیگر';
    },

    getPaymentDestinationMeta(value) {
        const item = this.findLookupItem('payment_destination_banks', value, true);
        if (!item || !item.meta_json) return {};
        try {
            const meta = JSON.parse(item.meta_json);
            return meta && typeof meta === 'object' && !Array.isArray(meta) ? meta : {};
        } catch (_) {
            return {};
        }
    },

    getPaymentDestinationSnapshot(value) {
        const item = this.findLookupItem('payment_destination_banks', value, true);
        const meta = this.getPaymentDestinationMeta(value);
        const cardFull = String(meta.card_number || '').replace(/\D/g, '');
        return {
            title: item?.title || '',
            bankName: String(meta.bank_name || ''),
            accountType: String(meta.account_type || ''),
            cardFull,
            cardLast4: cardFull ? cardFull.slice(-4) : '',
            accountNumber: String(meta.account_number || '').replace(/\s+/g, ''),
            sheba: String(meta.sheba || '').replace(/\s+/g, '').toUpperCase(),
            accountOwner: String(meta.account_owner || '').trim(),
            tafsiliCode: String(meta.tafsili_code || '').trim()
        };
    },

    formatPaymentDestinationOption(opt) {
        const snapshot = this.getPaymentDestinationSnapshot(opt.value);
        const parts = [
            snapshot.bankName || opt.label,
            snapshot.accountType,
            snapshot.cardLast4 ? `کارت ${snapshot.cardLast4}` : ''
        ].filter(Boolean);
        return parts.length ? parts.join(' - ') : opt.label;
    },

    populatePaymentMethods(selectElement, selectedValue = '') {
        const options = this.getLookupOptions('payment_methods');
        selectElement.innerHTML = '<option value="">-- انتخاب نحوه پرداخت --</option>';
        options.forEach(opt => {
            // C1: hide the "سایر/Other" payment method, but keep it if an existing
            // record was already saved with it so the locked row still displays correctly.
            if (this.isOtherPaymentMethod(opt.value, opt.label) && opt.value !== selectedValue) return;
            if ((this.isSudranGatewayMethod(opt.value) || this.isSudranGatewayMethod(opt.label)) && !this.isAdmin()) return;
            const optElem = document.createElement('option');
            optElem.value = opt.value;
            const isSheba = this.isShebaMethod(opt.value) || this.isShebaMethod(opt.label);
            const isSudran = this.isSudranGatewayMethod(opt.value) || this.isSudranGatewayMethod(opt.label);
            optElem.textContent = isSudran ? 'درگاه پرداخت سودران' : (isSheba ? 'شِبا/پُل' : opt.label);
            if (opt.value === selectedValue) optElem.selected = true;
            selectElement.appendChild(optElem);
        });
        // Guard: if the stored method isn't in the loaded lookups yet (e.g. options not
        // loaded when an existing payment renders), still keep the saved value selected.
        // Otherwise the select would fall back to '' and togglePaymentFields would wipe
        // card_last4/depositor/time before a re-save, silently dropping saved data.
        if (selectedValue && !Array.from(selectElement.options).some(o => o.value === selectedValue)) {
            const keepElem = document.createElement('option');
            keepElem.value = selectedValue;
            const isSheba = this.isShebaMethod(selectedValue);
            keepElem.textContent = isSheba ? 'شِبا/پُل' : selectedValue;
            keepElem.selected = true;
            selectElement.appendChild(keepElem);
        }
    },

    populateDestinationBanks(selectElement, selectedValue = '') {
        const allOptions = this.getLookupOptions('payment_destination_banks')
            .filter(opt => !this.isOtherPaymentMethod(opt.value, opt.label));
        let options = [...allOptions];
        if (selectedValue && selectedValue !== '__other__' && !options.some(o => o.value === selectedValue)) {
            let extra = allOptions.find(o => o.value === selectedValue);
            if (!extra) {
                // The saved bank was hidden/disabled in the lookup manager. Resolve it from
                // the include-inactive archive so older deals keep showing their card.
                const archived = this.findLookupItem('payment_destination_banks', selectedValue, true);
                if (archived) {
                    extra = { value: selectedValue, label: archived.title ?? archived.label ?? selectedValue };
                }
            }
            if (extra) options = options.concat(extra);
        }
        selectElement.innerHTML = '<option value="">-- انتخاب بانک --</option>';
        options.forEach(opt => {
            const optElem = document.createElement('option');
            optElem.value = opt.value;
            optElem.textContent = this.formatPaymentDestinationOption(opt);
            if (opt.value === selectedValue) optElem.selected = true;
            selectElement.appendChild(optElem);
        });
        // C6: "سایر" option triggers manual destination number + account holder.
        const otherOpt = document.createElement('option');
        otherOpt.value = '__other__';
        otherOpt.textContent = 'سایر';
        if (selectedValue === '__other__') otherOpt.selected = true;
        selectElement.appendChild(otherOpt);
    },

    populateCurrencyTypes(selectElement, selectedValue = '') {
        const options = [
            { value: 'IRR', label: 'ریال' },
            { value: 'USD', label: 'دلار' },
            { value: 'EUR', label: 'یورو' },
            { value: 'GBP', label: 'پوند' },
            { value: '__other__', label: 'دیگر' }
        ];
        selectElement.innerHTML = '<option value="">-- انتخاب ارز --</option>';
        options.forEach(opt => {
            const optElem = document.createElement('option');
            optElem.value = opt.value;
            optElem.textContent = opt.label;
            if (opt.value === selectedValue) optElem.selected = true;
            selectElement.appendChild(optElem);
        });
    },

    getPaymentMethodLabel(methodValue) {
        const raw = String(methodValue || '').trim();
        if (!raw) return '-';
        if (this.isShebaMethod(raw)) return 'شِبا/پُل';
        const item = this.findLookupItem('payment_methods', raw, true);
        return item?.title || item?.label || raw;
    },

    getPaymentDisplayDate(payment = {}) {
        const direct = payment.date_persian || payment.payment_date_persian || '';
        if (direct) return direct;
        const raw = payment.date || payment.payment_date || '';
        return raw ? (toPersianDate(raw) || raw) : '-';
    },

    getPaymentRegisteredAt(payment = {}) {
        return String(
            payment.registered_at ||
            payment.payment_registered_at ||
            payment.payment_screen_at ||
            payment.payment_owner_locked_at ||
            payment.created_at ||
            ''
        ).trim();
    },

    getPaymentRegisteredDisplayDate(payment = {}) {
        const registeredAt = this.getPaymentRegisteredAt(payment);
        return registeredAt ? (toPersianDate(registeredAt) || registeredAt) : '';
    },

    getChronologicalPayments(payments = []) {
        return (Array.isArray(payments) ? payments : [])
            .map((payment, originalIndex) => ({ payment, originalIndex }))
            .sort((a, b) => {
                const aDate = String(this.getPaymentRegisteredAt(a.payment) || a.payment.date || a.payment.date_persian || '');
                const bDate = String(this.getPaymentRegisteredAt(b.payment) || b.payment.date || b.payment.date_persian || '');
                if (aDate === bDate) return a.originalIndex - b.originalIndex;
                return aDate.localeCompare(bDate);
            });
    },

    getPaymentSequence(payment = {}) {
        const raw = payment.payment_sequence ?? payment.sequence ?? '';
        const seq = parseInt(raw, 10);
        return Number.isFinite(seq) && seq > 0 ? seq : 0;
    },

    getPaymentRefundAmount(payment = {}) {
        return Math.max(0, parseMoneyValue(payment?.refund_amount || 0));
    },

    getPaymentRefundCount(payment = {}) {
        const count = parseInt(payment?.refund_count || '0', 10);
        if (Number.isFinite(count) && count > 0) return count;
        return this.getPaymentRefundAmount(payment) > 0 ? 1 : 0;
    },

    getPaymentNetAmount(payment = {}) {
        const amount = Math.max(0, parseMoneyValue(payment?.amount || 0));
        const refund = Math.min(amount, this.getPaymentRefundAmount(payment));
        return Math.max(amount - refund, 0);
    },

    getPaymentOwnerSnapshot(payment = {}, useCurrentUserFallback = false) {
        const registeredByDidarId = String(payment?.registered_by_didar_id || '').trim();
        const registeredByName = String(payment?.registered_by_name || '').trim();
        const currentUserId = useCurrentUserFallback && typeof window !== 'undefined' && window.CURRENT_USER_ID
            ? String(window.CURRENT_USER_ID)
            : '';
        const currentDidarId = useCurrentUserFallback ? String(APP_USER?.didar_id || '').trim() : '';
        const currentName = useCurrentUserFallback ? String(APP_USER?.name || '').trim() : '';
        return {
            user_id: String(payment?.payment_owner_user_id || '').trim() || currentUserId,
            didar_id: String(payment?.payment_owner_didar_id || '').trim() || registeredByDidarId || currentDidarId,
            name: String(payment?.payment_owner_name_snapshot || '').trim() || registeredByName || currentName,
            locked_at: String(payment?.payment_owner_locked_at || '').trim() || this.getPaymentRegisteredAt(payment),
            source: String(payment?.payment_owner_source || '').trim() || 'SESSION'
        };
    },

    normalizePaymentSequences(payments = []) {
        const list = Array.isArray(payments) ? JSON.parse(JSON.stringify(payments)) : [];
        let maxSequence = list.reduce((max, payment) => Math.max(max, this.getPaymentSequence(payment)), 0);
        this.getChronologicalPayments(list).forEach(({ payment }) => {
            const existingSequence = this.getPaymentSequence(payment);
            if (existingSequence > 0) {
                payment.payment_sequence = existingSequence;
                return;
            }
            maxSequence += 1;
            payment.payment_sequence = maxSequence;
        });
        return list;
    },

    buildPaymentCardDetails(payment = {}) {
        if (payment.deleted || payment.is_deleted) return [];
        const method = this.getEffectivePaymentMethod(payment);
        const isCardTransfer = this.isCardToCardMethod(method) || this.isShebaMethod(method);
        const isCardReader = this.isCardReaderMethod(method);
        const isCardMethod = isCardTransfer || isCardReader;
        const isCash = this.isCashMethod(method);
        const isCheck = this.isCheckMethod(method);
        const paymentOwner = this.getPaymentOwnerSnapshot(payment);
        const registeredDisplayDate = this.getPaymentRegisteredDisplayDate(payment);
        const fields = [
            ['تاریخ ثبت', registeredDisplayDate],
            ['کارشناس ثبت‌کننده پرداخت', paymentOwner.name || ''],
            [this.getPaymentCardDateLabel(method), this.getPaymentDisplayDate(payment)],
            [isCheck ? 'مبلغ چک' : 'مبلغ پرداخت', payment.amount ? `${formatMoneyString(payment.amount)} تومان` : ''],
            ['نحوه پرداخت', this.getPaymentMethodLabel(method)]
        ];
        const refundAmount = this.getPaymentRefundAmount(payment);
        if (refundAmount > 0) {
            const refundCount = this.getPaymentRefundCount(payment);
            fields.push(
                ['تعداد عودت وجه', `${this.formatNumber(refundCount)} مورد`],
                ['مبلغ عودت وجه', `${formatMoneyString(refundAmount)} تومان`],
                ['مبلغ خالص پرداخت', `${formatMoneyString(this.getPaymentNetAmount(payment))} تومان`]
            );
        }
        if (isCardMethod) {
            fields.push(
                ['۴ رقم آخر کارت مبدا', payment.card_last4 || ''],
                ['نام واریزکننده', payment.depositor_name || '']
            );
        }
        if (isCardTransfer) {
            fields.push(
                ['بانک مقصد', payment.destination_bank_title || payment.destination_bank || ''],
                ['شماره بانک/حساب مقصد', payment.destination_bank_other || ''],
                ['نام صاحب حساب', payment.account_owner_name || payment.destination_account_owner || '']
            );
        }
        if (isCardReader) {
            fields.push(['دستگاه کارتخوان', payment.card_reader_device || '']);
        }
        if (isCash) {
            fields.push(
                ['نوع ارز', payment.currency_other || payment.currency || ''],
                ['مبلغ ارز', payment.currency_amount ? formatMoneyString(payment.currency_amount) : '']
            );
        }
        if (isCheck) {
            fields.push(
                ['نام بانک مبدا', payment.check_bank_name || ''],
                ['نام صاحب چک', payment.check_owner_name || ''],
                ['تصویر چک', payment.check_image_path ? 'بارگذاری شده' : ''],
                ['تصویر ثبت چک صیادی', payment.check_sayadi_image_path ? 'بارگذاری شده' : ''],
                ['شماره چک', payment.check_number || ''],
                ['تاریخ صیادی', payment.sayadi_date || ''],
                ['تاریخ تحویل چک', payment.check_delivery_date_persian || payment.check_delivery_date || ''],
                ['نام تحویل‌گیرنده', payment.check_recipient_name || '']
            );
        }
        if (isCardMethod && payment.receipt_image_path) {
            fields.push(['فیش واریزی', 'بارگذاری شده']);
        }
        fields.push(['توضیح پرداخت', payment.description || '']);
        return fields.filter(([, value]) => String(value || '').trim() !== '' && String(value) !== '-');
    },

    renderExistingPayments() {
        const section = document.getElementById('deal-existing-payments-section');
        const container = document.getElementById('deal-existing-payments');
        if (!section || !container) return;
        const rows = this.getChronologicalPayments(this.state.lastPaymentsCache || [])
            .filter(({ payment }) => !this.isDeletedPayment(payment));
        // Keep the section (heading + "افزودن پرداخت" button) visible even with no payments;
        // only the list area shows an empty state. Form/history toggling is handled by
        // setPaymentFormVisible().
        if (rows.length === 0) {
            container.innerHTML = '<div class="text-center text-gray-500 text-sm py-4">پرداختی ثبت نشده است</div>';
            this.updateDealFirstPaymentTiming();
            this.syncDealCancelButton();
            return;
        }
        const legacyLocked = this.isLegacyDidarDeal();
        const canEdit = !legacyLocked && this.canActionClient('payments.edit');
        const canDelete = !legacyLocked && this.canActionClient('payments.delete');
        container.innerHTML = rows.map(({ payment, originalIndex }, orderedIndex) => {
            const details = this.buildPaymentCardDetails(payment);
            const imageDetailLabels = new Set(['تصویر چک', 'تصویر ثبت چک صیادی']);
            const orderedDetails = [
                ...details.filter(([label]) => !imageDetailLabels.has(label)),
                ...details.filter(([label]) => imageDetailLabels.has(label))
            ];
            const paymentSequence = orderedIndex + 1;
            const isDeletedPayment = this.isDeletedPayment(payment);
            const detailHtml = orderedDetails.map(([label, value]) => {
                const isAmount = label === 'مبلغ پرداخت' || label === 'مبلغ چک';
                const uploadPath = label === 'فیش واریزی'
                    ? payment.receipt_image_path
                    : (label === 'تصویر چک'
                        ? payment.check_image_path
                        : (label === 'تصویر ثبت چک صیادی' ? payment.check_sayadi_image_path : ''));
                const isUploadLink = !!uploadPath;
                const uploadUrl = this.normalizePaymentUploadUrl(uploadPath);
                const downloadUrl = this.normalizePaymentUploadUrl(uploadPath, true);
                const uploadLinkText = label === 'تصویر چک'
                    ? 'تصویر چک'
                    : (label === 'تصویر ثبت چک صیادی' ? 'ثبت چک صیادی' : 'فیش');
                const valueClass = isAmount
                    ? 'text-base font-extrabold leading-7'
                    : 'text-sm font-semibold leading-6';
                const valueStyle = isAmount ? 'color:#6ee7b7' : 'color:#f3f4f6';
                const valueHtml = isUploadLink && uploadUrl !== '#'
                    ? `<div class="flex w-full items-center justify-between gap-3 flex-wrap">
                            <span class="${valueClass}" style="${valueStyle}">بارگذاری شده</span>
                            <div class="flex items-center gap-2 flex-wrap">
                                <a href="${this.escapeHtml(uploadUrl)}" target="_blank" rel="noopener" onclick="event.stopPropagation()" class="inline-flex items-center justify-center gap-1.5 rounded-lg border text-xs font-bold transition-colors whitespace-nowrap" style="border-color:rgba(96,165,250,0.72);background:rgba(59,130,246,0.14);color:#bfdbfe;padding:7px 10px;min-height:34px">
                                    <i class="fa-solid fa-eye"></i> ${uploadLinkText}
                                </a>
                                <a href="${this.escapeHtml(downloadUrl)}" download onclick="event.stopPropagation()" class="inline-flex items-center justify-center gap-1.5 rounded-lg border text-xs font-bold transition-colors whitespace-nowrap" style="border-color:rgba(52,211,153,0.6);background:rgba(16,185,129,0.14);color:#a7f3d0;padding:7px 10px;min-height:34px">
                                    <i class="fa-solid fa-download"></i> دانلود
                                </a>
                            </div>
                       </div>`
                    : `<div class="${valueClass} break-words" style="${valueStyle};text-align:right">${this.escapeHtml(String(value))}</div>`;
                return `
                <div class="min-w-0 p-3 rounded-lg" style="background:rgba(15,23,42,0.74);border:1px solid rgba(148,163,184,0.14)">
                    <div class="text-[11px] font-bold" style="color:#9ca3af;margin-bottom:8px">${this.escapeHtml(label)}</div>
                    ${valueHtml}
                </div>`;
            }).join('');
            const commissionStatusCard = this.renderPaymentCommissionStatusCard(payment);
            return `
                <div class="relative overflow-hidden rounded-lg p-3 shadow-lg transition-colors" style="background:#172334;border:1px solid rgba(148,163,184,0.28);box-shadow:0 14px 32px rgba(2,6,23,0.22)">
                    <div class="absolute top-0 right-0 h-full w-1.5 bg-yellow-500/70"></div>
                    <div class="flex items-center justify-between gap-3 flex-wrap">
                        <div class="flex items-center gap-2 flex-wrap">
                            <span class="inline-flex items-center gap-1.5 text-xs font-bold whitespace-nowrap cursor-default select-none" style="min-height:34px;color:#fde68a">
                                <i class="fa-solid fa-receipt"></i> پرداخت ${this.toPersianOrdinal(paymentSequence)}
                            </span>
                        </div>
                        <div class="flex gap-2 shrink-0 flex-wrap ${isDeletedPayment ? 'hidden' : ''}">
                            ${this.isAdmin() && !legacyLocked ? `<button type="button" onclick="app.openPaymentRefundModal(${originalIndex})" class="rounded-lg border bg-white/5 hover:bg-white/12 text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="border-color:rgba(229,231,235,0.78);padding:6px 14px;min-height:34px;color:#f9fafb"><i class="fa-solid fa-rotate-left"></i> عودت وجه</button>` : ''}
                            ${canEdit ? `<button type="button" onclick="app.editExistingPayment(${originalIndex})" class="rounded-lg border bg-yellow-500/15 hover:bg-yellow-500/30 text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="border-color:rgba(234,179,8,0.72);padding:6px 14px;min-height:34px;color:#fde68a"><i class="fa-solid fa-pen"></i> ویرایش</button>` : ''}
                            ${canDelete ? `<button type="button" onclick="app.deleteExistingPayment(${originalIndex})" class="rounded-lg border bg-red-500/15 hover:bg-red-500/30 text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="border-color:rgba(239,68,68,0.72);padding:6px 14px;min-height:34px;color:#fecaca"><i class="fa-solid fa-trash"></i> حذف</button>` : ''}
                        </div>
                    </div>
                    <div class="access-divider mt-3 mb-4" style="border-bottom:1px solid rgba(148,163,184,0.18)"></div>
                    ${isDeletedPayment
                        ? '<div class="rounded-lg p-3 text-center text-sm font-bold" style="background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.34);color:#fca5a5">این پرداخت حذف شده است</div>'
                        : `<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">${commissionStatusCard}${detailHtml}</div>`}
                </div>
            `;
        }).join('');
        this.updateDealFirstPaymentTiming();
        this.syncDealCancelButton();
    },

    renderPaymentCommissionStatusCard(payment = {}) {
        const isSudranGateway = this.isSudranGatewayMethod(payment.method || payment.payment_method || '');
        const calculated = !isSudranGateway && Number(payment.commission_calculated || 0) === 1;
        const status = isSudranGateway ? 'NOT_ELIGIBLE' : String(payment.commission_status || 'PENDING').toUpperCase();
        const labels = {
            NOT_ELIGIBLE: 'شامل پورسانت نمی‌شود',
            PENDING: 'در انتظار محاسبه',
            CALCULATED: 'محاسبه شده',
            APPROVED: 'تایید شده',
            READY_FOR_PAYOUT: 'آماده پرداخت',
            PAID: 'پرداخت شده',
            REFUNDED: 'عودت شده',
            CLAWBACK_PENDING: 'در انتظار برگشت',
            CLAWED_BACK: 'برگشت خورده',
            VOID: 'باطل'
        };
        const dateLabel = payment.commission_calculated_at_jalali
            || (payment.commission_calculated_at ? (toPersianDate(payment.commission_calculated_at) || payment.commission_calculated_at) : '');
        const title = 'وضعیت پورسانت';
        const mainLabel = isSudranGateway ? labels.NOT_ELIGIBLE : (calculated ? (labels[status] || 'محاسبه شده') : (labels[status] || 'در انتظار محاسبه'));
        const rows = [];
        rows.push('<div><span>وضعیت</span><strong>' + this.escapeHtml(mainLabel) + '</strong></div>');
        if (!isSudranGateway && status !== 'NOT_ELIGIBLE') {
            rows.push('<div><span>تاریخ محاسبه</span><strong>' + this.escapeHtml(dateLabel || '—') + '</strong></div>');
        }
        if (isSudranGateway || status === 'NOT_ELIGIBLE') {
            return `<div class="min-w-0 p-3 rounded-lg" style="background:rgba(15,23,42,0.74);border:1px solid rgba(148,163,184,0.14)">
                <div class="text-[11px] font-bold" style="color:#9ca3af;margin-bottom:8px">${this.escapeHtml(title)}</div>
                <div class="text-sm font-semibold leading-6 break-words" style="color:#f3f4f6;text-align:right">${this.escapeHtml(mainLabel)}</div>
            </div>`;
        }
        return `<div class="min-w-0 p-3 rounded-lg deal-payment-commission-card" style="background:rgba(15,23,42,0.74);border:1px solid rgba(148,163,184,0.14)">
            <div class="text-[11px] font-bold mb-3" style="color:#9ca3af">${this.escapeHtml(title)}</div>
            <div class="deal-payment-commission-meta">${rows.join('')}</div>
        </div>`;
    },

    toPersianOrdinal(number) {
        const labels = ['اول', 'دوم', 'سوم', 'چهارم', 'پنجم', 'ششم', 'هفتم', 'هشتم', 'نهم', 'دهم'];
        return labels[number - 1] || `${this.formatNumber(number)}`;
    },

    editExistingPayment(index) {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.canActionClient('payments.edit')) {
            Swal.fire('خطا', 'دسترسی به ویرایش پرداخت ندارید', 'error');
            return;
        }
        const payment = (this.state.lastPaymentsCache || [])[index];
        if (!payment) return;
        if (payment.deleted || payment.is_deleted) {
            Swal.fire('خطا', 'پرداخت حذف‌شده قابل ویرایش نیست', 'error');
            return;
        }
        const container = document.getElementById('payment-rows');
        if (container) {
            this.cleanupPersianDatepickers(container);
            container.innerHTML = '';
        }
        this.state.editingPaymentIndex = index;
        this.setPaymentFormTitle('ویرایش پرداخت');
        this.setPaymentFormVisible(true);
        this.addPaymentRow(payment, false);
        this.updatePaymentsSubmitState();
        document.getElementById('deal-payment-details')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    },

    async openPaymentRefundModal(index) {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'ثبت عودت وجه فقط برای مدیر مجاز است', 'error');
            return;
        }
        const cache = Array.isArray(this.state.lastPaymentsCache) ? JSON.parse(JSON.stringify(this.state.lastPaymentsCache)) : [];
        const payment = cache[index];
        if (!payment || this.isDeletedPayment(payment)) return;
        const currentRefund = this.getPaymentRefundAmount(payment);
        const currentRefundCount = this.getPaymentRefundCount(payment);
        const paymentAmount = Math.max(0, parseMoneyValue(payment.amount || 0));
        const netAmount = Math.max(paymentAmount - currentRefund, 0);
        const result = await Swal.fire({
            title: '',
            width: 520,
            background: '#182231',
            color: '#e5e7eb',
            backdrop: 'rgba(2,6,23,0.78)',
            html: `
                <div dir="rtl" style="text-align:right;font-family:inherit">
                    <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid rgba(148,163,184,0.18)">
                        <div>
                            <div style="display:flex;align-items:center;gap:8px;font-size:20px;font-weight:900;color:#fecaca;line-height:1.5">
                                <i class="fa-solid fa-rotate-left" style="font-size:16px;color:#fecaca"></i>
                                <span>عودت وجه</span>
                            </div>
                            <div style="font-size:12px;color:#9ca3af;margin-top:2px">مبلغ عودت از پرداخت خالص معامله کم می‌شود</div>
                        </div>
                    </div>

                    <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px">
                        <div style="border:1px solid rgba(148,163,184,0.16);background:rgba(15,23,42,0.72);border-radius:10px;padding:10px">
                            <div style="font-size:11px;color:#9ca3af;margin-bottom:5px">مبلغ پرداخت</div>
                            <div style="font-size:15px;font-weight:900;color:#6ee7b7">${this.escapeHtml(formatMoneyString(paymentAmount))} تومان</div>
                        </div>
                        <div style="border:1px solid rgba(148,163,184,0.16);background:rgba(15,23,42,0.72);border-radius:10px;padding:10px">
                            <div style="font-size:11px;color:#9ca3af;margin-bottom:5px">خالص فعلی</div>
                            <div style="font-size:15px;font-weight:900;color:#f3f4f6">${this.escapeHtml(formatMoneyString(netAmount))} تومان</div>
                        </div>
                        <div style="border:1px solid rgba(148,163,184,0.16);background:rgba(15,23,42,0.72);border-radius:10px;padding:10px">
                            <div style="font-size:11px;color:#9ca3af;margin-bottom:5px">مجموع عودت</div>
                            <div style="font-size:15px;font-weight:900;color:#fecaca">${this.escapeHtml(formatMoneyString(currentRefund))} تومان</div>
                        </div>
                        <div style="border:1px solid rgba(148,163,184,0.16);background:rgba(15,23,42,0.72);border-radius:10px;padding:10px">
                            <div style="font-size:11px;color:#9ca3af;margin-bottom:5px">تعداد عودت</div>
                            <div style="font-size:15px;font-weight:900;color:#f3f4f6">${this.escapeHtml(this.formatNumber(currentRefundCount))} مورد</div>
                        </div>
                    </div>

                    <div style="display:grid;gap:12px">
                        <div>
                            <label style="display:block;font-size:12px;font-weight:800;color:#d1d5db;margin-bottom:7px">مبلغ عودت جدید</label>
                            <input id="swal-payment-refund-amount" inputmode="numeric" value="" placeholder="0" style="box-sizing:border-box;width:100%;height:48px;margin:0;border-radius:10px;border:1px solid rgba(248,113,113,0.42);background:#1f2937;color:#f9fafb;font-size:18px;font-weight:800;direction:ltr;text-align:right;padding:0 14px;outline:none">
                        </div>
                        <div>
                            <label style="display:block;font-size:12px;font-weight:800;color:#d1d5db;margin-bottom:7px">توضیحات</label>
                            <textarea id="swal-payment-refund-desc" placeholder="توضیحات عودت وجه الزامی است" style="box-sizing:border-box;width:100%;min-height:96px;margin:0;border-radius:10px;border:1px solid rgba(148,163,184,0.28);background:#1f2937;color:#f9fafb;font-size:15px;font-weight:700;line-height:1.8;text-align:right;padding:12px 14px;resize:vertical;outline:none"></textarea>
                        </div>
                    </div>
                </div>
            `,
            showCancelButton: true,
            confirmButtonText: 'تایید',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#ef4444',
            cancelButtonColor: '#374151',
            buttonsStyling: false,
            customClass: {
                popup: 'rounded-xl border shadow-2xl',
                htmlContainer: 'm-0',
                actions: 'gap-3 mt-5',
                confirmButton: 'rounded-lg bg-red-600 hover:bg-red-500 text-white font-bold px-6 py-2.5 min-w-[96px]',
                cancelButton: 'rounded-lg bg-gray-700 hover:bg-gray-600 text-white font-bold px-6 py-2.5 min-w-[96px]',
            },
            didRender: () => {
                const popup = Swal.getPopup();
                if (popup) {
                    popup.style.borderColor = 'rgba(148,163,184,0.48)';
                    popup.style.boxShadow = '0 42px 100px rgba(0,0,0,0.76), 0 0 0 1px rgba(255,255,255,0.06)';
                }
            },
            focusConfirm: false,
            didOpen: () => {
                const amountInput = document.getElementById('swal-payment-refund-amount');
                amountInput?.focus?.();
                amountInput?.select?.();
                amountInput?.addEventListener('input', () => {
                    amountInput.value = formatMoneyString(amountInput.value);
                });
            },
            preConfirm: () => {
                const amountInput = document.getElementById('swal-payment-refund-amount');
                const descInput = document.getElementById('swal-payment-refund-desc');
                const description = String(descInput?.value || '').trim();
                if (!description) {
                    Swal.showValidationMessage('توضیحات عودت وجه الزامی است');
                    return false;
                }
                return {
                    amount: Math.max(0, parseMoneyValue(amountInput?.value || '')),
                    description
                };
            }
        });
        if (!result.isConfirmed) return;
        const refundAmount = Number(result.value?.amount || 0);
        if (refundAmount <= 0) {
            Swal.fire('خطا', 'مبلغ عودت وجه را وارد کنید', 'error');
            return;
        }
        const nextRefundTotal = currentRefund + refundAmount;
        if (nextRefundTotal > paymentAmount) {
            Swal.fire('خطا', 'مبلغ عودت داده شده نمی‌تواند از اصل مبلغ پرداخت بیشتر باشد', 'error');
            return;
        }
        payment.refund_amount = nextRefundTotal;
        payment.refund_count = currentRefundCount + 1;
        const newDescription = String(result.value?.description || '').trim();
        payment.refund_description = [payment.refund_description || '', newDescription].filter(Boolean).join(' | ');
        cache[index] = payment;
        this.state.lastPaymentsCache = cache;
        this.state.editingPaymentIndex = null;
        this.state.savingPaymentHistoryMutation = true;
        this.renderExistingPayments();
        await this.saveDealFromTab('payments');
    },

    async deleteExistingPayment(index) {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.canActionClient('payments.delete')) {
            Swal.fire('خطا', 'دسترسی به حذف پرداخت ندارید', 'error');
            return;
        }
        const result = await Swal.fire({
            title: 'حذف پرداخت',
            html: `
                <div class="text-right leading-7">
                    <p class="mb-3">آیا از حذف این پرداخت مطمئن هستید؟</p>
                    <label for="swal-payment-delete-reason" class="block text-sm font-bold mb-2">دلیل حذف پرداخت <span class="text-red-500">*</span></label>
                    <textarea id="swal-payment-delete-reason" class="swal2-textarea" style="width:100%;min-height:110px;margin:0;text-align:right;direction:rtl" placeholder="دلیل حذف را وارد کنید"></textarea>
                </div>
            `,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'انصراف',
            confirmButtonColor: '#ef4444',
            focusConfirm: false,
            preConfirm: () => {
                const reason = String(document.getElementById('swal-payment-delete-reason')?.value || '').trim();
                if (!reason) {
                    Swal.showValidationMessage('لطفاً دلیل حذف پرداخت را وارد کنید');
                    return false;
                }
                return { reason };
            }
        });
        if (!result.isConfirmed) return;
        const cache = Array.isArray(this.state.lastPaymentsCache) ? [...this.state.lastPaymentsCache] : [];
        if (!cache[index]) return;
        this.state.savingPaymentHistoryMutation = true;
        const deleteReason = String(result.value?.reason || '').trim();
        const deletedPayment = {
            ...cache[index],
            deleted: true,
            deleted_at: new Date().toISOString(),
            delete_reason: deleteReason,
            deleted_reason: deleteReason,
            deletion_reason: deleteReason,
            amount: 0,
            currency_amount: 0,
            refund_amount: 0,
            refund_description: '',
            refund_count: 0
        };
        cache[index] = deletedPayment;
        this.state.lastPaymentsCache = cache;
        this.state.editingPaymentIndex = null;
        this.renderExistingPayments();
        const container = document.getElementById('payment-rows');
        if (container) {
            this.cleanupPersianDatepickers(container);
            container.innerHTML = '';
        }
        this.setPaymentFormTitle('ثبت پرداخت جدید');
        this.setPaymentFormVisible(false);
        this.updatePaymentsSubmitState();
        await this.saveDealFromTab('payments');
    },

    addPaymentRow(paymentData = null, readonly = null) {
        if (paymentData === null && this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (paymentData === null && !this.canActionClient('payments.create')) {
            Swal.fire('خطا', 'دسترسی به ثبت پرداخت ندارید', 'error');
            return;
        }
        const isAdminUser = this.isAdmin();
        if (readonly === null) {
            const hasData = paymentData !== null && (
                paymentData.date || paymentData.date_persian || paymentData.amount || paymentData.method || paymentData.description
            );
            readonly = ((this.state.paymentsLocked && hasData) || (this.dealIsWon && hasData)) && !isAdminUser;
        }
        const container = document.getElementById('payment-rows');
        if (!container) return;

        const rowId = `payment-row-${this.paymentRowCounter++}`;
        const row = document.createElement('div');
        row.id = rowId;
        row.className = 'bg-gray-800/50 p-4 rounded border border-gray-700 relative';

        let dateValue = paymentData?.date_persian || '';
        if (!dateValue && paymentData?.date) {
            try {
                const gDate = new Date(paymentData.date);
                dateValue = !isNaN(gDate.getTime()) ? (new persianDate(gDate)).format('YYYY/MM/DD') : paymentData.date;
            } catch (e) {
                dateValue = paymentData.date || '';
            }
        }

        const amountValue = paymentData?.amount || '';
        const amountDisplay = formatMoneyString(amountValue);
        const methodValue = this.getEffectivePaymentMethod(paymentData || {});
        const descriptionValue = paymentData?.description || '';
        const cardLast4Value = paymentData?.card_last4 || paymentData?.card_last_4 || '';
        const bankValue = paymentData?.destination_bank || paymentData?.bank_destination || '';
        const destinationBankOtherValue = paymentData?.destination_bank_other || '';
        const accountOwnerNameValue = paymentData?.account_owner_name || '';
        const depositorNameValue = paymentData?.depositor_name || paymentData?.payer_name || paymentData?.payment_depositor_name || '';
        const destinationCardValueRaw = (
            paymentData?.destination_card_number === '__other__'
                ? (paymentData?.destination_card_number_other || '')
                : (paymentData?.destination_card_number || paymentData?.destination_card_number_other || '')
        );
        const destinationCardValue = String(destinationCardValueRaw || '').replace(/\D/g, '').slice(-4);
        const destinationSnapshot = this.getPaymentDestinationSnapshot(bankValue || '');
        const destinationBankTitleValue = paymentData?.destination_bank_title || destinationSnapshot.title || '';
        const destinationCardFullValue = paymentData?.destination_card_full || destinationSnapshot.cardFull || '';
        const destinationAccountNumberValue = paymentData?.destination_account_number || destinationSnapshot.accountNumber || '';
        const destinationShebaValue = paymentData?.destination_sheba || destinationSnapshot.sheba || '';
        const destinationAccountOwnerValue = paymentData?.destination_account_owner || destinationSnapshot.accountOwner || '';
        const cardReaderDeviceValue = paymentData?.card_reader_device || '';
        const currencyValue = paymentData?.currency || '';
        const currencyOtherValue = paymentData?.currency_other || '';
        const currencyAmountValue = paymentData?.currency_amount || '';
        const currencyAmountDisplay = formatMoneyString(currencyAmountValue);
        const checkBankValue = paymentData?.check_bank_name || '';
        const checkDateValue = paymentData?.check_date || '';
        const checkNumberValue = toEnglishDigits(String(paymentData?.check_number || '')).replace(/\D/g, '').slice(0, 6);
        const checkOwnerNameValue = paymentData?.check_owner_name || '';
        // C3: registration date (immutable). Gregorian value round-trips in a hidden input;
        // the Jalali form is displayed disabled. New rows show today and are stamped server-side.
        const registeredAtValue = paymentData ? this.getPaymentRegisteredAt(paymentData) : '';
        let registeredAtDisplay = '';
        try {
            if (registeredAtValue) {
                const rDate = new Date(String(registeredAtValue).replace(' ', 'T'));
                registeredAtDisplay = !isNaN(rDate.getTime()) ? (new persianDate(rDate)).format('YYYY/MM/DD') : registeredAtValue;
            } else {
                registeredAtDisplay = (new persianDate()).format('YYYY/MM/DD');
            }
        } catch (e) {
            registeredAtDisplay = String(registeredAtValue || '');
        }
        // C4: deposit date (واریز) and cheque due date (سررسید چک)
        const depositDateValue = paymentData?.deposit_date_persian || paymentData?.deposit_date || '';
        const checkDueDateValue = paymentData?.check_due_date_persian || paymentData?.check_due_date || '';
        // C7/C8: Sayadi date, cheque delivery date and recipient name
        const sayadiDateValue = paymentData?.sayadi_date || '';
        const checkDeliveryDateValue = paymentData?.check_delivery_date_persian || paymentData?.check_delivery_date || '';
        const checkRecipientNameValue = paymentData?.check_recipient_name || '';
        // C2: uploaded receipt / cheque image paths (relative to web root).
        const receiptImagePath = paymentData?.receipt_image_path || '';
        const checkImagePath = paymentData?.check_image_path || '';
        const checkSayadiImagePath = paymentData?.check_sayadi_image_path || '';
        const receiptImageUrl = this.normalizePaymentUploadUrl(receiptImagePath);
        const checkImageUrl = this.normalizePaymentUploadUrl(checkImagePath);
        const checkSayadiImageUrl = this.normalizePaymentUploadUrl(checkSayadiImagePath);
        const paymentSequenceValue = this.getPaymentSequence(paymentData || {});
        const isNewPaymentRow = paymentData === null;
        const paymentOwnerSnapshot = this.getPaymentOwnerSnapshot(paymentData || {}, isNewPaymentRow);
        const paymentRegisteredByDidarIdValue = paymentData?.registered_by_didar_id || paymentOwnerSnapshot.didar_id || (isNewPaymentRow ? (APP_USER?.didar_id || '') : '');
        const paymentRegisteredByNameValue = paymentData?.registered_by_name || paymentOwnerSnapshot.name || (isNewPaymentRow ? (APP_USER?.name || '') : '');
        const paymentOwnerUserIdValue = paymentOwnerSnapshot.user_id || '';
        const paymentOwnerDidarIdValue = paymentOwnerSnapshot.didar_id || paymentRegisteredByDidarIdValue || '';
        const paymentOwnerNameSnapshotValue = paymentOwnerSnapshot.name || paymentRegisteredByNameValue || '';
        const paymentOwnerLockedAtValue = paymentOwnerSnapshot.locked_at || registeredAtValue || '';
        const paymentOwnerSourceValue = paymentOwnerSnapshot.source || 'SESSION';
        const paymentRefundAmountValue = Math.max(0, parseMoneyValue(paymentData?.refund_amount || 0));
        const paymentRefundDescriptionValue = paymentData?.refund_description || '';
        const paymentRefundCountValue = this.getPaymentRefundCount(paymentData || {});

        const isSavedPaymentRow = paymentData !== null;
        const isExistingPayment = isSavedPaymentRow && this.state.editingPaymentIndex === null;
        const canEditPaymentByRbac = this.canActionClient('payments.edit');
        const lockExistingForAdmin = isAdminUser && isExistingPayment;
        const effectiveReadonly = !!readonly || (isExistingPayment && !canEditPaymentByRbac) || lockExistingForAdmin;
        const readonlyAttr = effectiveReadonly ? 'readonly' : '';
        const readonlyClass = effectiveReadonly ? 'bg-gray-900 cursor-not-allowed pointer-events-none opacity-70' : '';
        const canDeletePayment = this.canUserDeleteFieldClient('payment_delete');
        const canDeletePaymentByRbac = this.canActionClient('payments.delete');
        const canShowAdminPaymentControls = isAdminUser && isExistingPayment;
        const adminPaymentActions = canShowAdminPaymentControls
            ? `<div class="mb-3 flex flex-wrap gap-2 justify-start">
                    <button type="button" id="${rowId}-edit-btn" onclick="app.togglePaymentRowEdit('${rowId}')" class="text-xs px-2 py-1 rounded border border-yellow-500 text-yellow-400 hover:bg-yellow-500/10">ویرایش پرداخت</button>
                    <button type="button" onclick="app.removePaymentRow('${rowId}')" class="text-xs px-2 py-1 rounded border border-red-500 text-red-400 hover:bg-red-500/10">حذف پرداخت</button>
               </div>`
            : '';
        const removeButton = (canShowAdminPaymentControls || effectiveReadonly || !canDeletePayment || !canDeletePaymentByRbac)
            ? ''
            : `<button type="button" onclick="app.removePaymentRow('${rowId}')" class="absolute top-2 left-2 text-red-500 hover:text-red-400 text-sm"><i class="fa-solid fa-times"></i></button>`;

        row.innerHTML = `
            ${adminPaymentActions}
            ${removeButton}
            <div class="space-y-3 ${effectiveReadonly ? 'pr-0' : 'pr-6'}">
                <div class="grid grid-cols-2 gap-3">
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block">تاریخ ثبت <span class="text-gray-500">(قفل‌شده)</span></label>
                        <input type="text" class="input bg-gray-900 cursor-not-allowed opacity-70" value="${registeredAtDisplay}" disabled>
                        <input type="hidden" name="payment_registered_at[]" value="${registeredAtValue}">
                        <input type="hidden" name="payment_sequence[]" value="${paymentSequenceValue || ''}">
                    </div>
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block">کارشناس ثبت‌کننده پرداخت <span class="text-gray-500">(قفل‌شده)</span></label>
                        <input type="text" class="input bg-gray-900 cursor-not-allowed opacity-70" value="${this.escapeHtml(paymentOwnerNameSnapshotValue || paymentRegisteredByNameValue || '-')}" disabled>
                        <input type="hidden" name="payment_registered_by_didar_id[]" value="${this.escapeHtml(paymentRegisteredByDidarIdValue)}">
                        <input type="hidden" name="payment_registered_by_name[]" value="${this.escapeHtml(paymentRegisteredByNameValue)}">
                        <input type="hidden" name="payment_owner_user_id[]" value="${this.escapeHtml(paymentOwnerUserIdValue)}">
                        <input type="hidden" name="payment_owner_didar_id[]" value="${this.escapeHtml(paymentOwnerDidarIdValue)}">
                        <input type="hidden" name="payment_owner_name_snapshot[]" value="${this.escapeHtml(paymentOwnerNameSnapshotValue)}">
                        <input type="hidden" name="payment_owner_locked_at[]" value="${this.escapeHtml(paymentOwnerLockedAtValue)}">
                        <input type="hidden" name="payment_owner_source[]" value="${this.escapeHtml(paymentOwnerSourceValue)}">
                    </div>
                </div>
                <div class="grid grid-cols-2 gap-3">
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block">نحوه پرداخت <span class="text-red-500">*</span></label>
                        <select name="payment_method[]" class="input bg-gray-800 payment-method-select ${readonlyClass}">
                            <option value="">-- انتخاب نحوه پرداخت --</option>
                        </select>
                    </div>
                </div>
                <div class="grid grid-cols-2 gap-3">
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block payment-date-label">${this.getPaymentDateLabel(methodValue)} <span class="text-red-500">*</span></label>
                        <input type="text" name="payment_date[]" class="input payment-date-jalali ${readonlyClass}" placeholder="1403/01/01" value="${dateValue}" ${readonlyAttr}>
                    </div>
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block payment-amount-label">${this.isCheckMethod(methodValue) ? 'مبلغ چک (تومان)' : 'مبلغ پرداخت (تومان)'} <span class="text-red-500">*</span></label>
                        <input type="text" inputmode="numeric" name="payment_amount[]" class="input money-input ${readonlyClass}" placeholder="0" value="${amountDisplay}" ${readonlyAttr} autocomplete="off" dir="ltr" style="text-align:right">
                    </div>
                </div>
                <input type="hidden" name="payment_refund_amount[]" value="${paymentRefundAmountValue || ''}">
                <textarea name="payment_refund_description[]" class="hidden">${this.escapeHtml(paymentRefundDescriptionValue)}</textarea>
                <input type="hidden" name="payment_refund_count[]" value="${paymentRefundCountValue || ''}">
                <div class="grid grid-cols-2 gap-3">
                    <div class="payment-card-last4 ${(this.isCardToCardMethod(methodValue) || this.isCardReaderMethod(methodValue) || this.isShebaMethod(methodValue)) ? '' : 'hidden'}">
                        <label class="text-xs text-gray-400 mb-1 block">4 رقم آخر کارت مبدا <span class="text-red-500">*</span></label>
                        <input type="text" name="payment_card_last4[]" inputmode="numeric" maxlength="4" pattern="\\d{4}" class="input ${effectiveReadonly ? readonlyClass : ''}" placeholder="1234" value="${cardLast4Value}" ${readonlyAttr}>
                    </div>
                    <div class="payment-depositor ${(this.isCardToCardMethod(methodValue) || this.isCardReaderMethod(methodValue) || this.isShebaMethod(methodValue)) ? '' : 'hidden'}">
                        <label class="text-xs text-gray-400 mb-1 block">نام و نام خانوادگی واریز‌کننده <span class="text-red-500">*</span></label>
                        <input type="text" name="depositor_name[]" class="input ${effectiveReadonly ? readonlyClass : ''}" value="${this.escapeHtml(depositorNameValue)}" placeholder="نام و نام خانوادگی واریز‌کننده" ${readonlyAttr}>
                    </div>
                </div>
                <div class="payment-bank ${(this.isCardToCardMethod(methodValue) || this.isShebaMethod(methodValue)) ? '' : 'hidden'}">
                    <label class="text-xs text-gray-400 mb-1 block">نام بانک مقصد <span class="text-red-500">*</span></label>
                    <select name="destination_bank[]" class="input bg-gray-800 destination-bank-select w-full ${readonlyClass}">
                        <option value="">-- انتخاب بانک --</option>
                    </select>
                </div>
                <div class="payment-bank-other ${(this.isCardToCardMethod(methodValue) || this.isShebaMethod(methodValue)) && bankValue === '__other__' ? '' : 'hidden'}">
                    <div class="grid grid-cols-2 gap-3">
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">شماره کارت/حساب مقصد <span class="text-red-500">*</span></label>
                            <input type="text" name="destination_bank_other[]" class="input ${effectiveReadonly ? readonlyClass : ''}" value="${destinationBankOtherValue}" placeholder="شماره کارت/حساب مقصد" ${readonlyAttr}>
                        </div>
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">نام صاحب حساب <span class="text-red-500">*</span></label>
                            <input type="text" name="account_owner_name[]" class="input ${effectiveReadonly ? readonlyClass : ''}" value="${accountOwnerNameValue}" placeholder="نام صاحب حساب" ${readonlyAttr}>
                        </div>
                    </div>
                </div>
                <div class="payment-destination-details ${(this.isCardToCardMethod(methodValue) || this.isShebaMethod(methodValue)) && bankValue && bankValue !== '__other__' ? '' : 'hidden'} rounded border border-gray-700 bg-gray-900/50 p-3">
                    <input type="hidden" name="destination_card_number[]" value="${destinationCardValue}">
                    <input type="hidden" name="destination_bank_title[]" value="${this.escapeHtml(destinationBankTitleValue)}">
                    <input type="hidden" name="destination_card_full[]" value="${this.escapeHtml(destinationCardFullValue)}">
                    <input type="hidden" name="destination_account_number[]" value="${this.escapeHtml(destinationAccountNumberValue)}">
                    <input type="hidden" name="destination_sheba[]" value="${this.escapeHtml(destinationShebaValue)}">
                    <input type="hidden" name="destination_account_owner[]" value="${this.escapeHtml(destinationAccountOwnerValue)}">
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
                        <div>
                            <div class="text-gray-500 mb-1">صاحب حساب</div>
                            <div class="text-gray-200 payment-destination-owner-display">${this.escapeHtml(destinationAccountOwnerValue || '-')}</div>
                        </div>
                        <div>
                            <div class="text-gray-500 mb-1">کارت مقصد</div>
                            <div class="text-gray-200 font-mono ltr payment-destination-card-display">${this.escapeHtml(destinationCardFullValue || '-')}</div>
                        </div>
                        <div>
                            <div class="text-gray-500 mb-1">شماره حساب</div>
                            <div class="text-gray-200 font-mono ltr payment-destination-account-display">${this.escapeHtml(destinationAccountNumberValue || '-')}</div>
                        </div>
                        <div>
                            <div class="text-gray-500 mb-1">شبا</div>
                            <div class="text-gray-200 font-mono ltr payment-destination-sheba-display">${this.escapeHtml(destinationShebaValue || '-')}</div>
                        </div>
                    </div>
                </div>
                <div class="payment-card-reader-extra ${(this.isCardReaderMethod(methodValue)) ? '' : 'hidden'}">
                    <label class="text-xs text-gray-400 mb-1 block">نام دستگاه <span class="text-red-500">*</span></label>
                    <select name="card_reader_device[]" class="input bg-gray-800 ${readonlyClass}">
                        <option value="">-- انتخاب دستگاه --</option>
                        <option value="academy" ${cardReaderDeviceValue === 'academy' ? 'selected' : ''}>آکادمی</option>
                        <option value="showroom" ${cardReaderDeviceValue === 'showroom' ? 'selected' : ''}>نمایشگاه</option>
                    </select>
                </div>
                <div class="payment-cash-extra ${(this.isCashMethod(methodValue)) ? '' : 'hidden'} space-y-3">
                    <div class="grid grid-cols-2 gap-3">
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">نوع ارز <span class="text-red-500">*</span></label>
                            <select name="payment_currency[]" class="input bg-gray-800 payment-currency-select ${readonlyClass}"></select>
                        </div>
                        <div class="payment-currency-other ${currencyValue === '__other__' ? '' : 'hidden'}">
                            <label class="text-xs text-gray-400 mb-1 block">واحد ارز (دستی)</label>
                            <input type="text" name="payment_currency_other[]" class="input ${effectiveReadonly ? readonlyClass : ''}" value="${currencyOtherValue}" placeholder="مثال: درهم" ${readonlyAttr}>
                        </div>
                    </div>
                    <div class="payment-currency-amount ${currencyValue && currencyValue !== 'IRR' ? '' : 'hidden'}">
                        <label class="text-xs text-gray-400 mb-1 block">مبلغ ارز</label>
                        <input type="text" name="payment_currency_amount[]" class="input money-input ${effectiveReadonly ? readonlyClass : ''}" value="${currencyAmountDisplay}" placeholder="مبلغ به واحد ارز" ${readonlyAttr} dir="ltr" style="text-align:right">
                    </div>
                </div>
                <div class="payment-check-extra ${(this.isCheckMethod(methodValue)) ? '' : 'hidden'} space-y-3">
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block">نام بانک مبدا <span class="text-gray-500">(پس از ثبت قفل)</span></label>
                        <input type="text" name="check_bank_name[]" class="input check-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${checkBankValue}" placeholder="نام بانک مبدا" ${readonlyAttr}>
                    </div>
                    <div class="grid grid-cols-2 gap-3">
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">تاریخ ثبت صیادی <span class="text-gray-500">(پس از ثبت قفل)</span></label>
                            <input type="text" name="sayadi_date[]" class="input payment-sayadi-date-jalali check-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${sayadiDateValue}" placeholder="1403/01/01" ${readonlyAttr}>
                        </div>
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">شماره چک <span class="text-gray-500">(پس از ثبت قفل)</span></label>
                            <input type="text" name="check_number[]" inputmode="numeric" maxlength="6" pattern="\\d{6}" class="input check-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${checkNumberValue}" placeholder="شماره چک ۶ رقمی" ${readonlyAttr}>
                        </div>
                    </div>
                    <div>
                        <label class="text-xs text-gray-400 mb-1 block">نام صاحب چک <span class="text-gray-500">(پس از ثبت قفل)</span></label>
                        <input type="text" name="check_owner_name[]" class="input check-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${checkOwnerNameValue}" placeholder="نام صاحب چک" ${readonlyAttr}>
                    </div>
                    <div class="grid grid-cols-2 gap-3">
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">تصویر چک (تصویر/PDF) ${isSavedPaymentRow ? '' : '<span class="text-red-500">*</span>'}</label>
                            <input type="hidden" name="check_image_path[]" value="${checkImagePath}">
                            <input type="file" accept="image/*,application/pdf" class="input text-xs ${effectiveReadonly ? 'pointer-events-none opacity-60' : ''}" onchange="app.uploadPaymentFile(this, 'check')" ${effectiveReadonly ? 'disabled' : ''}>
                            <a href="${checkImageUrl}" target="_blank" rel="noopener" onclick="event.stopPropagation()" class="payment-check-link text-xs text-blue-400 hover:underline ${checkImageUrl !== '#' ? '' : 'hidden'}">تصویر چک</a>
                        </div>
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">تصویر ثبت چک صیادی (تصویر/PDF) ${isSavedPaymentRow ? '' : '<span class="text-red-500">*</span>'}</label>
                            <input type="hidden" name="check_sayadi_image_path[]" value="${checkSayadiImagePath}">
                            <input type="file" accept="image/*,application/pdf" class="input text-xs ${effectiveReadonly ? 'pointer-events-none opacity-60' : ''}" onchange="app.uploadPaymentFile(this, 'check_sayadi')" ${effectiveReadonly ? 'disabled' : ''}>
                            <a href="${checkSayadiImageUrl}" target="_blank" rel="noopener" onclick="event.stopPropagation()" class="payment-check-sayadi-link text-xs text-blue-400 hover:underline ${checkSayadiImageUrl !== '#' ? '' : 'hidden'}">ثبت چک صیادی</a>
                        </div>
                    </div>
                    <div class="grid grid-cols-2 gap-3 border-t border-gray-700 pt-3">
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">تاریخ تحویل چک <span class="text-gray-500">(اختیاری، پس از ثبت قفل)</span></label>
                            <input type="text" name="check_delivery_date[]" class="input payment-check-delivery-date-jalali check-delivery-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${checkDeliveryDateValue}" placeholder="1403/01/01" ${readonlyAttr}>
                        </div>
                        <div>
                            <label class="text-xs text-gray-400 mb-1 block">نام تحویل‌گیرنده <span class="text-gray-500">(اختیاری، پس از ثبت قفل)</span></label>
                            <input type="text" name="check_recipient_name[]" class="input check-delivery-lock-field ${effectiveReadonly ? readonlyClass : ''}" value="${checkRecipientNameValue}" placeholder="نام تحویل‌گیرنده" ${readonlyAttr}>
                        </div>
                    </div>
                </div>
                <div class="payment-receipt-upload">
                    <label class="text-xs text-gray-400 mb-1 block">فیش واریزی (تصویر/PDF) <span class="text-red-500">*</span></label>
                    <input type="hidden" name="receipt_image_path[]" value="${receiptImagePath}">
                    <input type="file" accept="image/*,application/pdf" class="input text-xs ${effectiveReadonly ? 'pointer-events-none opacity-60' : ''}" onchange="app.uploadPaymentFile(this, 'receipt')" ${effectiveReadonly ? 'disabled' : ''}>
                    <a href="${receiptImageUrl}" target="_blank" rel="noopener" onclick="event.stopPropagation()" class="payment-receipt-link text-xs text-blue-400 hover:underline ${receiptImageUrl !== '#' ? '' : 'hidden'}">مشاهده فیش بارگذاری‌شده</a>
                </div>
                <div>
                    <label class="text-xs text-gray-400 mb-1 block">توضیح پرداخت</label>
                    <textarea name="payment_description[]" class="input h-20 resize-none ${readonlyClass}" placeholder="توضیحات این پرداخت" ${readonlyAttr}>${descriptionValue}</textarea>
                </div>
            </div>
        `;

        container.appendChild(row);
        row.dataset.existingPayment = isSavedPaymentRow ? '1' : '0';
        row.dataset.adminLocked = lockExistingForAdmin ? '1' : '0';
        row.dataset.adminEditing = '0';

        const methodSelect = row.querySelector('.payment-method-select');
        const bankSelect = row.querySelector('.destination-bank-select');
        const cardInput = row.querySelector('input[name="payment_card_last4[]"]');
        const destinationCardInput = row.querySelector('input[name="destination_card_number[]"]');
        const currencySelect = row.querySelector('.payment-currency-select');
        if (methodSelect) this.populatePaymentMethods(methodSelect, methodValue);
        if (bankSelect) this.populateDestinationBanks(bankSelect, bankValue || '');
        if (currencySelect) this.populateCurrencyTypes(currencySelect, currencyValue || '');
        row.querySelectorAll('input[name="check_number[]"]').forEach((input) => {
            input.addEventListener('input', () => {
                input.value = toEnglishDigits(String(input.value || '')).replace(/\D/g, '').slice(0, 6);
                this.updatePaymentsSubmitState();
            });
        });

        const canEditMethod = this.canUserEditFieldClient('payment_method', isExistingPayment);
        const canEditCard = this.canUserEditFieldClient('payment_card_last4', isExistingPayment);
        const canEditBank = this.canUserEditFieldClient('destination_bank', isExistingPayment);
        const disableSelect = (el) => { if (el) { el.disabled = true; el.classList.add('bg-gray-900', 'cursor-not-allowed', 'opacity-70'); } };
        const disableInput = (el) => { if (el) { el.readOnly = true; el.classList.add('bg-gray-900', 'cursor-not-allowed', 'opacity-70', 'pointer-events-none'); } };

        if (!canEditMethod && methodSelect) disableSelect(methodSelect);
        if (!canEditCard && cardInput) disableInput(cardInput);
        if (!canEditBank && bankSelect) disableSelect(bankSelect);

        // C7/C8: once a payment is registered, cheque identity fields (bank/owner/number/Sayadi)
        // and the optional delivery fields lock for the sales expert. Admins can re-open the
        // whole row via the "ویرایش پرداخت" toggle (togglePaymentRowEdit re-enables all inputs).
        if (isExistingPayment && !isAdminUser) {
            row.querySelectorAll('.check-lock-field, .check-delivery-lock-field').forEach(disableInput);
        }

        // Agent editing a SAVED cheque payment via the edit option (editExistingPayment sets
        // editingPaymentIndex): cheque fields are write-once — fields that already have a value
        // are locked, empty fields stay fillable and lock on the next save. Admins edit freely.
        if (isSavedPaymentRow && this.state.editingPaymentIndex !== null && !isAdminUser) {
            row.querySelectorAll('.check-lock-field, .check-delivery-lock-field').forEach(el => {
                if (String(el.value || '').trim() !== '') disableInput(el);
            });
            if (String(checkImagePath || '').trim() !== '') {
                const checkFileInput = row.querySelector('.payment-check-extra input[type="file"]');
                if (checkFileInput) {
                    checkFileInput.disabled = true;
                    checkFileInput.classList.add('pointer-events-none', 'opacity-60');
                }
            }
        }

        const togglePaymentFields = () => {
            const method = methodSelect?.value || '';
            const isCardTransfer = this.isCardToCardMethod(method) || this.isShebaMethod(method);
            const isCardReader = this.isCardReaderMethod(method);
            const isCardMethod = isCardTransfer || isCardReader;
            const isCash = this.isCashMethod(method);
            const isCheck = this.isCheckMethod(method);
            const isSudranGateway = this.isSudranGatewayMethod(method);

            const cardWrap = row.querySelector('.payment-card-last4');
            const depositorWrap = row.querySelector('.payment-depositor');
            const depositorInput = row.querySelector('input[name="depositor_name[]"]');
            const bankWrap = row.querySelector('.payment-bank');
            const bankOtherWrap = row.querySelector('.payment-bank-other');
            const bankOtherInput = row.querySelector('input[name="destination_bank_other[]"]');
            const destinationDetailsWrap = row.querySelector('.payment-destination-details');
            const destinationBankTitleInput = row.querySelector('input[name="destination_bank_title[]"]');
            const destinationCardFullInput = row.querySelector('input[name="destination_card_full[]"]');
            const destinationAccountInput = row.querySelector('input[name="destination_account_number[]"]');
            const destinationShebaInput = row.querySelector('input[name="destination_sheba[]"]');
            const destinationOwnerInput = row.querySelector('input[name="destination_account_owner[]"]');
            const destinationCardDisplay = row.querySelector('.payment-destination-card-display');
            const destinationAccountDisplay = row.querySelector('.payment-destination-account-display');
            const destinationShebaDisplay = row.querySelector('.payment-destination-sheba-display');
            const destinationOwnerDisplay = row.querySelector('.payment-destination-owner-display');
            const transferWrap = row.querySelector('.payment-transfer-extra');
            const cardReaderWrap = row.querySelector('.payment-card-reader-extra');
            const cashWrap = row.querySelector('.payment-cash-extra');
            const checkWrap = row.querySelector('.payment-check-extra');
            const receiptUploadWrap = row.querySelector('.payment-receipt-upload');
            const currencyOtherWrap = row.querySelector('.payment-currency-other');
            const currencyOtherInput = row.querySelector('input[name="payment_currency_other[]"]');
            const currencyAmountWrap = row.querySelector('.payment-currency-amount');
            const currencyAmountInput = row.querySelector('input[name="payment_currency_amount[]"]');
            const checkBankInput = row.querySelector('input[name="check_bank_name[]"]');
            const checkDateInput = row.querySelector('input[name="check_date[]"]');
            const checkNumberInput = row.querySelector('input[name="check_number[]"]');
            const checkOwnerInput = row.querySelector('input[name="check_owner_name[]"]');
            const accountOwnerInput = row.querySelector('input[name="account_owner_name[]"]');
            const depositDateWrap = row.querySelector('.payment-deposit-date');
            const depositDateInput = row.querySelector('input[name="deposit_date[]"]');
            const checkDueDateInput = row.querySelector('input[name="check_due_date[]"]');
            const sayadiDateInput = row.querySelector('input[name="sayadi_date[]"]');
            const checkDeliveryDateInput = row.querySelector('input[name="check_delivery_date[]"]');
            const checkRecipientInput = row.querySelector('input[name="check_recipient_name[]"]');
            const checkSayadiImageInput = row.querySelector('input[name="check_sayadi_image_path[]"]');

            const existingRow = row.dataset.existingPayment === '1';
            const requireCurrentRowFields = !effectiveReadonly;
            const paymentDateInput = row.querySelector('input[name="payment_date[]"]');
            const paymentAmountInput = row.querySelector('input[name="payment_amount[]"]');

            if (methodSelect) methodSelect.required = requireCurrentRowFields;
            if (paymentDateInput) paymentDateInput.required = requireCurrentRowFields;
            if (paymentAmountInput) paymentAmountInput.required = requireCurrentRowFields;

            if (cardWrap) cardWrap.classList.toggle('hidden', !isCardMethod);
            if (cardInput) {
                cardInput.required = requireCurrentRowFields && isCardMethod;
                if (!isCardMethod) cardInput.value = '';
            }
            // نام واریز‌کننده برای روش‌های کارتی (کارت به کارت/کارتخوان/شبا) نمایش داده می‌شود.
            if (depositorWrap) depositorWrap.classList.toggle('hidden', !isCardMethod);
            if (depositorInput) {
                depositorInput.required = requireCurrentRowFields && isCardMethod;
                if (!isCardMethod) depositorInput.value = '';
            }
            // برای چک، فیلد بالای فرم نقش «تاریخ سررسید چک» را دارد.
            const dateLabel = row.querySelector('.payment-date-label');
            if (dateLabel) dateLabel.innerHTML = `${this.escapeHtml(this.getPaymentDateLabel(method))} <span class="text-red-500">*</span>`;
            // Deposit date can't be in the future; cheque due date can — keep the picker in sync.
            this.applyPaymentDateLimit(row);
            if (bankWrap) bankWrap.classList.toggle('hidden', !isCardTransfer);
            if (bankSelect) {
                bankSelect.required = requireCurrentRowFields && isCardTransfer;
                if (!isCardTransfer) bankSelect.value = '';
            }
            const bankIsOther = bankSelect?.value === '__other__';
            // C6: "سایر" destination → manual destination number + account holder (required).
            if (bankOtherWrap) bankOtherWrap.classList.toggle('hidden', !isCardTransfer || !bankIsOther);
            if (bankOtherInput) {
                bankOtherInput.required = isCardTransfer && bankIsOther;
                if (!isCardTransfer || !bankIsOther) bankOtherInput.value = '';
            }
            if (accountOwnerInput) {
                accountOwnerInput.required = isCardTransfer && bankIsOther;
                if (!isCardTransfer || !bankIsOther) accountOwnerInput.value = '';
            }
            const showDestinationDetails = isCardTransfer && !!bankSelect?.value && !bankIsOther;
            if (destinationDetailsWrap) destinationDetailsWrap.classList.toggle('hidden', !showDestinationDetails);
            if (showDestinationDetails) {
                const snapshot = this.getPaymentDestinationSnapshot(bankSelect.value);
                if (destinationCardInput) destinationCardInput.value = snapshot.cardLast4 || '';
                if (destinationBankTitleInput) destinationBankTitleInput.value = snapshot.title || '';
                if (destinationCardFullInput) destinationCardFullInput.value = snapshot.cardFull || '';
                if (destinationAccountInput) destinationAccountInput.value = snapshot.accountNumber || '';
                if (destinationShebaInput) destinationShebaInput.value = snapshot.sheba || '';
                if (destinationOwnerInput) destinationOwnerInput.value = snapshot.accountOwner || '';
                if (destinationCardDisplay) destinationCardDisplay.textContent = snapshot.cardFull || '-';
                if (destinationAccountDisplay) destinationAccountDisplay.textContent = snapshot.accountNumber || '-';
                if (destinationShebaDisplay) destinationShebaDisplay.textContent = snapshot.sheba || '-';
                if (destinationOwnerDisplay) destinationOwnerDisplay.textContent = snapshot.accountOwner || '-';
            } else {
                [destinationCardInput, destinationBankTitleInput, destinationCardFullInput, destinationAccountInput, destinationShebaInput, destinationOwnerInput].forEach(input => {
                    if (input) input.value = '';
                });
                if (destinationCardDisplay) destinationCardDisplay.textContent = '-';
                if (destinationAccountDisplay) destinationAccountDisplay.textContent = '-';
                if (destinationShebaDisplay) destinationShebaDisplay.textContent = '-';
                if (destinationOwnerDisplay) destinationOwnerDisplay.textContent = '-';
            }
            // C4: deposit date applies to deposits/transfers, cash, and Sudran gateway.
            if (depositDateWrap) depositDateWrap.classList.toggle('hidden', !(isCardTransfer || isCardReader || isCash || isSudranGateway));
            if (depositDateInput && !(isCardTransfer || isCardReader || isCash || isSudranGateway)) depositDateInput.value = '';

            if (transferWrap) transferWrap.classList.toggle('hidden', !isCardTransfer);
            if (destinationCardInput) {
                destinationCardInput.required = false;
                if (!isCardTransfer) destinationCardInput.value = '';
            }
            if (cardReaderWrap) cardReaderWrap.classList.toggle('hidden', !isCardReader);
            if (cashWrap) cashWrap.classList.toggle('hidden', !isCash);
            if (checkWrap) checkWrap.classList.toggle('hidden', !isCheck);
            // The deposit receipt (فیش واریزی) is irrelevant for cheque (it has its own
            // cheque image), cash, and Sudran gateway, so hide its upload there.
            if (receiptUploadWrap) {
                const hideReceipt = isCheck || isCash || isSudranGateway;
                receiptUploadWrap.classList.toggle('hidden', hideReceipt);
                if (hideReceipt) {
                    const rp = receiptUploadWrap.querySelector('input[name="receipt_image_path[]"]');
                    if (rp) rp.value = '';
                }
            }
            // Cheque uses "مبلغ چک"; everything else "مبلغ پرداخت".
            const amountLabel = row.querySelector('.payment-amount-label');
            if (amountLabel) amountLabel.innerHTML = `${isCheck ? 'مبلغ چک (تومان)' : 'مبلغ پرداخت (تومان)'} <span class="text-red-500">*</span>`;
            const currencyValueNow = currencySelect?.value || '';
            const currencyOther = currencyValueNow === '__other__';
            const showCurrencyAmount = !!currencyValueNow && currencyValueNow !== 'IRR';
            if (currencySelect) {
                currencySelect.required = isCash;
                if (!isCash) currencySelect.value = '';
            }
            if (currencyOtherWrap) currencyOtherWrap.classList.toggle('hidden', !isCash || !currencyOther);
            if (currencyOtherInput) {
                currencyOtherInput.required = requireCurrentRowFields && isCash && currencyOther;
                if (!isCash || !currencyOther) currencyOtherInput.value = '';
            }
            if (currencyAmountWrap) currencyAmountWrap.classList.toggle('hidden', !isCash || !showCurrencyAmount);
            if (currencyAmountInput) {
                currencyAmountInput.required = requireCurrentRowFields && isCash && showCurrencyAmount;
                if (!isCash || !showCurrencyAmount) currencyAmountInput.value = '';
            }
            // C7: cheque identity fields + payment date required when method is cheque.
            [checkBankInput, checkNumberInput, checkOwnerInput, sayadiDateInput].forEach((input) => {
                if (!input) return;
                input.required = isCheck;
                if (!isCheck) input.value = '';
            });
            if (checkDateInput) {
                checkDateInput.required = isCheck;
                if (!isCheck) checkDateInput.value = '';
            }
            const receiptPathInput = row.querySelector('input[name="receipt_image_path[]"]');
            const receiptFileInput = receiptUploadWrap?.querySelector('input[type="file"]');
            if (receiptFileInput) {
                receiptFileInput.required = requireCurrentRowFields
                    && (isCardTransfer || isCardReader)
                    && !String(receiptPathInput?.value || '').trim();
            }
            const checkImagePathInput = row.querySelector('input[name="check_image_path[]"]');
            const checkFileInputs = Array.from(row.querySelectorAll('.payment-check-extra input[type="file"]'));
            const checkImageFileInput = checkFileInputs[0] || null;
            if (checkImageFileInput) {
                checkImageFileInput.required = requireCurrentRowFields && isCheck && !String(checkImagePathInput?.value || '').trim();
            }
            const checkSayadiFileInput = checkFileInputs[1] || null;
            if (checkSayadiFileInput) {
                checkSayadiFileInput.required = requireCurrentRowFields && isCheck && !String(checkSayadiImageInput?.value || '').trim();
            }
            // C4 (cheque due) + C8 (delivery, optional): clear when not a cheque.
            [checkDueDateInput, checkDeliveryDateInput, checkRecipientInput].forEach((input) => {
                if (input && !isCheck) input.value = '';
            });
            if (checkSayadiImageInput && !isCheck) checkSayadiImageInput.value = '';
        };

        if (methodSelect) methodSelect.addEventListener('change', togglePaymentFields);
        if (bankSelect) bankSelect.addEventListener('change', togglePaymentFields);
        if (currencySelect) currencySelect.addEventListener('change', togglePaymentFields);
        row.addEventListener('input', () => this.updatePaymentsSubmitState());
        row.addEventListener('change', () => this.updatePaymentsSubmitState());
        togglePaymentFields();
        this.updatePaymentsSubmitState();

        setTimeout(() => {
            const initPicker = (input, opts = {}) => {
                if (!input) return;
                const allowFuture = !!opts.allowFuture;
                // Past-event dates (sayadi registration, cheque delivery, deposit) can't be in the future.
                const clamp = () => {
                    if (allowFuture) return;
                    const norm = app.normalizeJalaliInputValue(input.value || '');
                    if (norm && norm !== input.value) input.value = norm;
                    const today = app.getTodayGregorianDateOnly();
                    const g = norm ? app.jalaliToGregorianDateOnly(norm, false) : '';
                    if (g && g > today) input.value = toPersianDate(today) || input.value;
                };
                const start = () => {
                    if (typeof window.$ !== 'undefined' && typeof window.$.fn !== 'undefined' && typeof window.$.fn.persianDatepicker !== 'undefined') {
                        try {
                            if (window.$(input).data('persianDatepicker')) window.$(input).persianDatepicker('destroy');
                            window.$(input).persianDatepicker({
                                observer: true,
                                format: 'YYYY/MM/DD',
                                calendarType: 'persian',
                                initialValue: false,
                                autoClose: true,
                                maxDate: allowFuture ? null : app.getTodayGregorianDateOnly(),
                                onSelect: () => setTimeout(clamp, 0)
                            });
                            window.$(input).off('change.payClamp blur.payClamp').on('change.payClamp blur.payClamp', clamp);
                        } catch (e) {
                            console.error('Error initializing date picker:', e);
                        }
                    } else {
                        setTimeout(start, 200);
                    }
                };
                start();
            };
            // Main date is method-aware (cheque DUE date may be future) → handled by applyPaymentDateLimit.
            initPicker(row.querySelector('.payment-date-jalali'), { allowFuture: true });
            // Sayadi-registration and cheque-delivery dates are past events → never in the future.
            initPicker(row.querySelector('.payment-sayadi-date-jalali'));
            initPicker(row.querySelector('.payment-check-delivery-date-jalali'));
            // Now that the picker exists, apply the deposit-date (no-future) vs cheque-due-date rule.
            this.applyPaymentDateLimit(row);
        }, 200);
    },

    getPaymentRowValidation(row, index = 0, showErrors = false) {
        const field = (selector) => row?.querySelector(selector);
        const isExistingRow = row?.dataset?.existingPayment === '1';
        const method = field('select[name="payment_method[]"]')?.value || '';
        const amount = parseMoneyValue(field('input[name="payment_amount[]"]')?.value || '');
        const date = field('input[name="payment_date[]"]')?.value?.trim() || '';
        const description = field('textarea[name="payment_description[]"]')?.value?.trim() || '';
        const cardLast4 = field('input[name="payment_card_last4[]"]')?.value?.trim() || '';
        const depositorNameVal = field('input[name="depositor_name[]"]')?.value?.trim() || '';
        const bankVal = field('select[name="destination_bank[]"]')?.value?.trim() || '';
        const bankOtherVal = field('input[name="destination_bank_other[]"]')?.value?.trim() || '';
        const accountOwnerVal = field('input[name="account_owner_name[]"]')?.value?.trim() || '';
        const cardReaderDeviceVal = field('select[name="card_reader_device[]"]')?.value?.trim() || '';
        const currencyVal = field('select[name="payment_currency[]"]')?.value?.trim() || '';
        const currencyOtherVal = field('input[name="payment_currency_other[]"]')?.value?.trim() || '';
        const currencyAmountVal = parseMoneyValue(field('input[name="payment_currency_amount[]"]')?.value || '');
        const checkBankVal = field('input[name="check_bank_name[]"]')?.value?.trim() || '';
        const checkOwnerVal = field('input[name="check_owner_name[]"]')?.value?.trim() || '';
        const checkNumberVal = toEnglishDigits(String(field('input[name="check_number[]"]')?.value || '')).replace(/\D/g, '');
        const sayadiDateVal = field('input[name="sayadi_date[]"]')?.value?.trim() || '';
        const checkImageVal = field('input[name="check_image_path[]"]')?.value?.trim() || '';
        const checkSayadiImageVal = field('input[name="check_sayadi_image_path[]"]')?.value?.trim() || '';
        const hasAnyValue = [
            method, date, description, cardLast4, depositorNameVal, bankVal, bankOtherVal, accountOwnerVal,
            cardReaderDeviceVal, currencyVal, currencyOtherVal, checkBankVal, checkOwnerVal,
            checkNumberVal, sayadiDateVal, checkImageVal, checkSayadiImageVal
        ].some(v => String(v || '').trim() !== '') || amount > 0 || currencyAmountVal > 0;
        const fail = (message, input = null) => {
            if (showErrors) {
                Swal.fire('خطا', `${message} (ردیف ${index + 1})`, 'error');
                input?.focus?.();
            }
            return { complete: false, hasAnyValue, message };
        };
        if (!hasAnyValue) return { complete: false, hasAnyValue: false, message: '' };
        if (!method) return fail('نحوه پرداخت الزامی است', field('select[name="payment_method[]"]'));
        if (!date) return fail(this.isCheckMethod(method) ? 'تاریخ سررسید چک الزامی است' : 'تاریخ پرداخت الزامی است', field('input[name="payment_date[]"]'));
        if (amount <= 0) return fail(this.isCheckMethod(method) ? 'مبلغ چک الزامی است' : 'مبلغ پرداخت الزامی است', field('input[name="payment_amount[]"]'));
        if ((this.isCardToCardMethod(method) || this.isShebaMethod(method) || this.isCardReaderMethod(method)) && !/^\d{4}$/.test(String(cardLast4).replace(/\D/g, ''))) {
            return fail('وارد کردن ۴ رقم آخر کارت مبدا الزامی است', field('input[name="payment_card_last4[]"]'));
        }
        if ((this.isCardToCardMethod(method) || this.isShebaMethod(method) || this.isCardReaderMethod(method)) && !depositorNameVal) {
            return fail('نام و نام خانوادگی واریزکننده الزامی است', field('input[name="depositor_name[]"]'));
        }
        if ((this.isCardToCardMethod(method) || this.isShebaMethod(method)) && !bankVal) {
            return fail('انتخاب بانک مقصد الزامی است', field('select[name="destination_bank[]"]'));
        }
        if ((this.isCardToCardMethod(method) || this.isShebaMethod(method)) && bankVal === '__other__' && (!bankOtherVal || !accountOwnerVal)) {
            return fail('برای انتخاب «سایر»، شماره بانک مقصد و نام صاحب حساب الزامی است', field('input[name="destination_bank_other[]"]'));
        }
        if (this.isCardReaderMethod(method) && !cardReaderDeviceVal) {
            return fail('انتخاب دستگاه کارتخوان الزامی است', field('select[name="card_reader_device[]"]'));
        }
        if (this.isCashMethod(method) && !currencyVal) {
            return fail('انتخاب نوع ارز الزامی است', field('select[name="payment_currency[]"]'));
        }
        if (this.isCashMethod(method) && currencyVal === '__other__' && !currencyOtherVal) {
            return fail('واحد ارز دستی الزامی است', field('input[name="payment_currency_other[]"]'));
        }
        if (this.isCashMethod(method) && currencyVal && currencyVal !== 'IRR' && currencyAmountVal <= 0) {
            return fail('مبلغ ارز الزامی است', field('input[name="payment_currency_amount[]"]'));
        }
        if (this.isCheckMethod(method) && (!checkBankVal || !checkOwnerVal || !/^\d{6}$/.test(checkNumberVal) || !sayadiDateVal || (!isExistingRow && !checkImageVal) || (!isExistingRow && !checkSayadiImageVal))) {
            return fail(isExistingRow
                ? 'برای چک، نام بانک مبدا، نام صاحب چک، شماره چک ۶ رقمی، تاریخ صیادی و تاریخ سررسید الزامی است'
                : 'برای چک، نام بانک مبدا، نام صاحب چک، شماره چک ۶ رقمی، تاریخ صیادی، تاریخ سررسید، تصویر چک و تصویر ثبت چک صیادی الزامی است',
                field('input[name="check_number[]"]'));
        }
        // Receipt upload is mandatory only for transfer/card-reader payments. Cash has no
        // transfer slip, and cheques use their own mandatory cheque image above.
        if (this.isCardToCardMethod(method) || this.isShebaMethod(method) || this.isCardReaderMethod(method)) {
            const receiptVal = field('input[name="receipt_image_path[]"]')?.value?.trim() || '';
            if (!receiptVal) {
                return fail('بارگذاری فیش واریزی الزامی است', field('input[name="receipt_image_path[]"]'));
            }
        }
        return { complete: true, hasAnyValue: true, message: '' };
    },

    updatePaymentsSubmitState() {
        const btn = document.getElementById('deal-payments-submit-btn');
        if (!btn) return;
        const legacyLocked = this.isLegacyDidarDeal();
        const canSavePaymentsViaDeal = this.canActionClient('payments.create') || this.canActionClient('payments.edit') || this.canActionClient('payments.delete');
        const rows = Array.from(document.querySelectorAll('#payment-rows > [data-existing-payment]'))
            .filter(row => row.offsetParent !== null);
        const hasCompleteRow = rows.some((row, index) => this.getPaymentRowValidation(row, index, false).complete);
        const formVisible = document.getElementById('deal-payment-details')?.style.display !== 'none';
        const needsCompletePayment = this.state.activeDealFormTab === 'payments' && formVisible && !this.state.savingPaymentHistoryMutation;
        const incomplete = needsCompletePayment && !hasCompleteRow;
        const disabled = legacyLocked || !canSavePaymentsViaDeal;
        btn.disabled = disabled;
        btn.classList.toggle('opacity-50', disabled || incomplete);
        btn.classList.toggle('pointer-events-none', disabled);
        btn.title = legacyLocked ? this.getLegacyDidarReadonlyMessage() : (!canSavePaymentsViaDeal ? '' : (incomplete ? 'لطفاً موارد الزامی فرم پرداخت را کامل کنید' : ''));
    },

    togglePaymentRowEdit(rowId) {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.isAdmin()) return;
        const row = document.getElementById(rowId);
        if (!row || row.dataset.existingPayment !== '1') return;

        const editBtn = document.getElementById(`${rowId}-edit-btn`);
        const isEditing = row.dataset.adminEditing === '1';
        const shouldEdit = !isEditing;
        const controls = row.querySelectorAll('input, select, textarea');
        controls.forEach((el) => {
            const tag = (el.tagName || '').toLowerCase();
            if (tag === 'select') {
                el.disabled = !shouldEdit;
            } else {
                el.readOnly = !shouldEdit;
            }
            el.classList.toggle('pointer-events-none', !shouldEdit);
            el.classList.toggle('cursor-not-allowed', !shouldEdit);
            el.classList.toggle('opacity-70', !shouldEdit);
            el.classList.toggle('bg-gray-900', !shouldEdit);
        });
        row.dataset.adminEditing = shouldEdit ? '1' : '0';
        if (editBtn) {
            editBtn.textContent = shouldEdit ? 'پایان ویرایش' : 'ویرایش پرداخت';
        }
    },
    
    async removePaymentRow(rowId) {
        if (this.isLegacyDidarDeal()) {
            this.blockLegacyDidarMutation();
            return;
        }
        if (!this.canActionClient('payments.delete')) {
            Swal.fire('خطا', 'دسترسی به حذف پرداخت ندارید', 'error');
            return;
        }
        const row = document.getElementById(rowId);
        if (row) {
            const result = await Swal.fire({
                title: 'حذف پرداخت',
                text: 'آیا از حذف این پرداخت مطمئن هستید؟',
                icon: 'warning',
                showCancelButton: true,
                confirmButtonText: 'بله، حذف شود',
                cancelButtonText: 'انصراف',
                confirmButtonColor: '#ef4444'
            });
            if (!result.isConfirmed) return;
            row.remove();
            this.updatePaymentsSubmitState();
        }
    },
    
    enableDealFormFields() {
        // Remove readonly/disabled state from all form fields
        const readonlyClasses = ['bg-gray-900', 'cursor-not-allowed'];
        const fields = [
            'deal-title',
            'deal-has-discount',
            'deal-discount-type',
            'deal-discount-occasion',
            'deal-discount-occasion-custom',
            'deal-discount-amount',
            'deal-payment-conditions'
        ];
        
        fields.forEach(fieldId => {
            const field = document.getElementById(fieldId);
            if (field) {
                if (field.tagName === 'SELECT') {
                    field.disabled = false;
                } else {
                    field.readOnly = false;
                }
                // Remove each class separately
                readonlyClasses.forEach(className => {
                    field.classList.remove(className);
                });
            }
        });

        ['deal-status', 'deal-pipeline-stage'].forEach((fieldId) => {
            const field = document.getElementById(fieldId);
            if (!field) return;
            field.disabled = false;
            readonlyClasses.forEach(className => field.classList.remove(className));
        });

        const productsContainer = document.getElementById('deal-products-list');
        if (productsContainer) {
            productsContainer.querySelectorAll('input[name="product_ids[]"]').forEach((checkbox) => {
                checkbox.disabled = false;
                checkbox.classList.remove('cursor-not-allowed', 'opacity-70');
            });
        }
        const addProductBtn = document.querySelector('button[onclick="app.addProductPrompt()"]');
        if (addProductBtn) {
            addProductBtn.disabled = false;
            addProductBtn.classList.remove('opacity-50', 'pointer-events-none');
        }
        this.setDealServiceCostLookupLock(true);
    },

    dealHasRegisteredPayments(deal = null) {
        const targetDeal = deal || this.state.currentDeal || {};
        if (targetDeal.has_registered_payments === true) return true;
        const payments = Array.isArray(targetDeal.payments) ? targetDeal.payments : [];
        return payments.some((payment) => parseMoneyValue(payment?.amount || 0) > 0);
    },

    dealRequestedServicesArray(deal = null) {
        const targetDeal = deal || this.state.currentDeal || {};
        const raw = targetDeal.requested_services;
        if (Array.isArray(raw)) {
            return raw.map(v => String(v || '').trim()).filter(Boolean);
        }
        if (!raw) return [];
        try {
            const parsed = JSON.parse(raw);
            if (Array.isArray(parsed)) {
                return parsed.map(v => String(v || '').trim()).filter(Boolean);
            }
        } catch (e) {
            // Plain strings from older data are handled below.
        }
        return String(raw).split(/[،,]+/u).map(v => v.trim()).filter(Boolean);
    },

    dealTitleIsAllowedService(title) {
        const serviceTitle = String(title || '').trim();
        if (!serviceTitle) return false;
        return (this.getLookupItems('allowed_services') || []).some(item => {
            const value = String(item?.value || item?.title || '').trim();
            const label = String(item?.title || item?.value || '').trim();
            return serviceTitle === value || serviceTitle === label;
        });
    },

    canEditBootstrapDealService(deal = null) {
        const targetDeal = deal || this.state.currentDeal || {};
        const isExistingDeal = !!(targetDeal && (targetDeal.id || targetDeal.didar_deal_id || targetDeal.deal_id));
        if (!isExistingDeal || this.dealHasRegisteredPayments(targetDeal)) return false;
        const requestedServices = this.dealRequestedServicesArray(targetDeal);
        if (requestedServices.length > 0) return false;
        const title = String(targetDeal.title || '').trim();
        if (this.dealTitleIsAllowedService(title)) return false;
        return true;
    },

    setDealInfoLockState(locked, options = {}) {
        const isLocked = !!locked;
        const allowAdminUnlock = options.allowAdminUnlock === true;
        const readonlyClasses = ['bg-gray-900', 'cursor-not-allowed', 'opacity-70'];
        const fieldSpecs = [
            { id: 'deal-title', type: 'select' },
            { id: 'deal-service-cost', type: 'input' },
            { id: 'deal-price-list-code', type: 'input' },
            { id: 'deal-has-discount', type: 'select' },
            { id: 'deal-discount-type', type: 'select' },
            { id: 'deal-discount-occasion', type: 'select' },
            { id: 'deal-discount-occasion-custom', type: 'input' },
            { id: 'deal-discount-amount', type: 'input' },
            { id: 'deal-refund-amount', type: 'input' },
            { id: 'deal-refund-description', type: 'textarea' },
            { id: 'deal-payable-amount', type: 'input' },
            { id: 'deal-pipeline-stage', type: 'select' }
        ];

        fieldSpecs.forEach(({ id, type }) => {
            const field = document.getElementById(id);
            if (!field) return;
            if (['deal-service-cost', 'deal-price-list-code', 'deal-payable-amount'].includes(id)) {
                return;
            }
            if (type === 'select') {
                field.disabled = isLocked;
            } else {
                field.readOnly = isLocked;
            }
            readonlyClasses.forEach((className) => field.classList.toggle(className, isLocked));
        });

        const paymentConditionsField = document.getElementById('deal-payment-conditions');
        if (paymentConditionsField) {
            paymentConditionsField.readOnly = false;
            readonlyClasses.forEach((className) => paymentConditionsField.classList.remove(className));
        }
        const dealStatusField = document.getElementById('deal-status');
        if (dealStatusField) {
            dealStatusField.disabled = false;
            readonlyClasses.forEach((className) => dealStatusField.classList.remove(className));
        }

        const productsContainer = document.getElementById('deal-products-list');
        if (productsContainer) {
            productsContainer.querySelectorAll('input[name="product_ids[]"]').forEach((checkbox) => {
                checkbox.disabled = isLocked;
                checkbox.classList.toggle('cursor-not-allowed', isLocked);
                checkbox.classList.toggle('opacity-70', isLocked);
            });
        }
        const addProductBtn = document.querySelector('button[onclick="app.addProductPrompt()"]');
        if (addProductBtn) {
            addProductBtn.disabled = isLocked;
            addProductBtn.classList.toggle('opacity-50', isLocked);
            addProductBtn.classList.toggle('pointer-events-none', isLocked);
        }
        if (!isLocked && allowAdminUnlock && this.isAdmin() && this.dealHasRegisteredPayments()) {
            this.setDealServiceCostLookupLock(false);
        } else {
            this.setDealServiceCostLookupLock(true);
        }

        this.state.dealInfoLocked = isLocked;
        const paidEditBtn = document.getElementById('deal-paid-edit-toggle-btn');
        if (paidEditBtn) {
            const showBtn = allowAdminUnlock && this.isAdmin();
            paidEditBtn.classList.toggle('hidden', !showBtn);
            paidEditBtn.textContent = isLocked ? 'ویرایش اطلاعات قفل‌شده' : 'پایان ویرایش اطلاعات';
        }

        // The deal service ("خدمات قابل ارائه" = the deal-title) may only be set when the deal is
        // being created. Once it exists / has been submitted, it is read-only for non-admins —
        // an agent/کارشناس must not be able to change which service the deal is for. Applied last so
        // it also covers open/Pending deals that the lock logic above would otherwise leave editable.
        if (!(this.isAdmin() || this.isSystemRole())) {
            const cur = this.state.currentDeal || null;
            const isExistingDeal = !!(cur && (cur.id || cur.didar_deal_id || cur.deal_id));
            const titleEl = document.getElementById('deal-title');
            if (isExistingDeal && titleEl && !this.canEditBootstrapDealService(cur)) {
                titleEl.disabled = true;
                titleEl.classList.add('bg-gray-900', 'cursor-not-allowed');
            } else if (titleEl && !isLocked) {
                titleEl.disabled = false;
                titleEl.classList.remove('bg-gray-900', 'cursor-not-allowed');
            }
        }
    },

    togglePaidDealInfoEdit() {
        if (!this.isAdmin() || !this.dealHasRegisteredPayments()) {
            return;
        }
        const nextUnlocked = !this.state.dealInfoAdminUnlocked;
        this.state.dealInfoAdminUnlocked = nextUnlocked;
        this.setDealInfoLockState(!nextUnlocked, { allowAdminUnlock: true });
    },
    
    loadPayments(payments, readonly = null, addDefault = false) {
        const container = document.getElementById('payment-rows');
        if (!container) return;
        this.cleanupPersianDatepickers(container);
        container.innerHTML = '';
        const hasData = (p) => {
            if (!p) return false;
            if (this.isDeletedPayment(p)) return true;
            const amountVal = parseMoneyValue(p.amount || 0);
            const hasMethod = !!(p.method && String(p.method).trim() !== '');
            const hasDesc = !!(p.description && String(p.description).trim() !== '');
            const hasDate = !!(p.date && String(p.date).trim() !== '') || !!(p.date_persian && String(p.date_persian).trim() !== '');
            return amountVal > 0 || hasMethod || hasDesc || (hasDate && amountVal > 0);
        };
        const normalized = this.normalizePaymentSequences(Array.isArray(payments) ? payments.filter(hasData) : []);
        this.state.lastPaymentsCache = JSON.parse(JSON.stringify(normalized));
        this.state.editingPaymentIndex = null;
        this.renderExistingPayments();

        if (addDefault && this.canActionClient('payments.create')) {
            this.addPaymentRow(null, false);
        } else {
            this.updatePaymentsSubmitState();
        }
    },
    
    // TOGGLE DISCOUNT FIELDS
    toggleDiscountFields() {
        const hasDiscount = document.getElementById('deal-has-discount').value === '1';
        const discountFields = document.getElementById('discount-fields');
        if (hasDiscount) {
            discountFields.classList.remove('hidden');
        } else {
            discountFields.classList.add('hidden');
            const type = document.getElementById('deal-discount-type');
            const occasion = document.getElementById('deal-discount-occasion');
            const custom = document.getElementById('deal-discount-occasion-custom');
            const amount = document.getElementById('deal-discount-amount');
            const refundAmount = document.getElementById('deal-refund-amount');
            const refundDesc = document.getElementById('deal-refund-description');
            if (type) type.value = '';
            if (occasion) occasion.value = '';
            if (custom) {
                custom.value = '';
                custom.classList.add('hidden');
                custom.required = false;
            }
            if (amount) amount.value = '';
            if (refundAmount) refundAmount.value = '';
            if (refundDesc) refundDesc.value = '';
        }
        this.toggleDiscountOccasion();
        this.applyDealServiceLookupPricing();
    },
    
    // TOGGLE DISCOUNT OCCASION
    toggleDiscountOccasion() {
        const discountType = document.getElementById('deal-discount-type').value;
        const occasionField = document.getElementById('discount-occasion-field');
        const refundFields = document.getElementById('refund-fields');
        const refundAmount = document.getElementById('deal-refund-amount');
        const refundDesc = document.getElementById('deal-refund-description');
        const discountAmount = document.getElementById('deal-discount-amount');
        if (discountType === 'occasion') {
            occasionField.classList.remove('hidden');
        } else {
            occasionField.classList.add('hidden');
        }
        const isRefund = discountType === 'refund';
        if (refundFields) refundFields.classList.toggle('hidden', !isRefund);
        if (refundAmount) refundAmount.required = isRefund;
        if (refundDesc) refundDesc.required = isRefund;
        if (discountAmount) discountAmount.classList.toggle('hidden', isRefund);
        if (!isRefund) {
            if (refundAmount) refundAmount.value = '';
            if (refundDesc) refundDesc.value = '';
        }
        this.toggleCustomOccasion();
        this.applyDealServiceLookupPricing();
    },
    
    // TOGGLE CUSTOM OCCASION
    toggleCustomOccasion() {
        const discountType = document.getElementById('deal-discount-type')?.value || '';
        if (discountType === 'refund') {
            const customField = document.getElementById('deal-discount-occasion-custom');
            if (customField) {
                customField.classList.add('hidden');
                customField.required = false;
                customField.value = '';
            }
            const occasionSelect = document.getElementById('deal-discount-occasion');
            if (occasionSelect) {
                occasionSelect.value = '';
            }
            return;
        }
        const occasion = document.getElementById('deal-discount-occasion').value;
        const customField = document.getElementById('deal-discount-occasion-custom');
        if (occasion === 'other') {
            customField.classList.remove('hidden');
            customField.required = true;
        } else {
            customField.classList.add('hidden');
            customField.required = false;
        }
    },
    
    // USERS
    async loadUsers() {
        if (this.canCurrentUserCapabilityClient('rbac.admin.manage')) {
            try {
                const rbacRes = await this.req('rbac_catalog');
                if (rbacRes.status === 'success') {
                    this.state.rbacCatalog = {
                        roles: rbacRes.roles || [],
                        capabilities: rbacRes.capabilities || []
                    };
                    this.applyLookupsToStaticSelects();
                }
            } catch (e) {
                console.warn('RBAC catalog load failed for users page:', e);
            }
        }
        const res = await this.req('get_users_list');
        if(res.status === 'success') {
            window.didarUsers = res.didar_users || [];
            this.state.masterPasswordConfigured = !!res.master_password_configured;
            this.state.lastUserHierarchyAssignments = res.hierarchy_assignments || [];
            this.renderUsers(res.users || [], res.crm_owner || {}, res.hierarchy_assignments || []);
        }
    },
    
    async saveCrmOwner() {
        if (!this.isAdmin()) return;
        const select = document.getElementById('crm-owner-select');
        if (!select) return;
        const crmOwnerId = select.value;
        if (!crmOwnerId) {
            Swal.fire('خطا', 'لطفاً کارشناس فروش را انتخاب کنید', 'error');
            return;
        }
        Swal.showLoading();
        let res;
        try {
            res = await this.req('save_crm_settings', {crm_owner_id: crmOwnerId});
        } catch (err) {
            console.error('saveCrmOwner failed', err);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (err.message || err), 'error');
            return;
        }
        this.closeBlockingSwal();
        if (res.status === 'success') {
            Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'ذخیره شد', showConfirmButton:false, timer:2000});
            this.loadUsers();
        } else {
            Swal.fire('خطا', res.message || 'خطا در ذخیره تنظیمات', 'error');
        }
    },

    async loadMaintenanceDashboard() {
        if (!this.canCurrentUserCapabilityClient('maintenance.admin.view')) {
            Swal.fire('عدم دسترسی', 'دسترسی شما به نگهداری دیتابیس مجاز نیست', 'warning');
            return;
        }
        const healthEl = document.getElementById('maintenance-health');
        const backupsEl = document.getElementById('maintenance-backups');
        if (healthEl) healthEl.innerHTML = '<div class="text-gray-500">در حال بررسی...</div>';
        if (backupsEl) backupsEl.innerHTML = '<div class="text-gray-500">در حال بارگذاری backupها...</div>';

        try {
            const res = await this.req('get_maintenance_dashboard', {});
            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطا در دریافت وضعیت نگهداری دیتابیس', 'error');
                return;
            }
            this.renderMaintenanceHealth(res.health || {});
            this.renderMaintenanceBackups(res.backups || []);
            this.renderMaintenanceReport({
                health: res.health || {},
                backup_dir: res.backup_dir || '',
                backups_count: Array.isArray(res.backups) ? res.backups.length : 0
            });
        } catch (error) {
            console.error('loadMaintenanceDashboard failed', error);
            Swal.fire('خطا', 'خطا در ارتباط با سرور: ' + (error.message || error), 'error');
        }
    },

    renderMaintenanceHealth(health = {}) {
        const el = document.getElementById('maintenance-health');
        const badge = document.getElementById('maintenance-health-badge');
        if (badge) {
            const ok = !!health.ok;
            badge.textContent = ok ? 'Schema آماده است' : 'نیازمند بررسی';
            badge.className = `text-xs px-2 py-1 rounded ${ok ? 'bg-emerald-900/70 text-emerald-200' : 'bg-amber-900/70 text-amber-200'}`;
        }
        if (!el) return;

        const summary = health.summary || {};
        const tables = health.tables || {};
        const columns = health.columns || {};
        const tableRows = Object.entries(tables).map(([name, meta]) => `
            <tr class="border-b border-gray-800">
                <td class="p-2 font-mono text-xs text-left direction-ltr">${this.escapeHtml(name)}</td>
                <td class="p-2">${meta && meta.exists ? '<span class="text-emerald-300">موجود</span>' : '<span class="text-red-300">مفقود</span>'}</td>
                <td class="p-2 text-left direction-ltr">${meta && meta.rows !== null && meta.rows !== undefined ? this.formatNumber(meta.rows) : '-'}</td>
            </tr>
        `).join('');
        const missingColumns = Object.entries(columns)
            .filter(([, meta]) => meta && Array.isArray(meta.missing) && meta.missing.length)
            .map(([table, meta]) => `<div class="text-xs text-amber-200 font-mono direction-ltr text-left">${this.escapeHtml(table)}: ${meta.missing.map(c => this.escapeHtml(c)).join(', ')}</div>`)
            .join('');

        el.innerHTML = `
            <div class="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
                <div class="bg-gray-900/60 border border-gray-800 rounded p-3">
                    <div class="text-xs text-gray-500">Database</div>
                    <div class="font-mono text-sm direction-ltr text-left">${this.escapeHtml(health.database || '-')}</div>
                </div>
                <div class="bg-gray-900/60 border border-gray-800 rounded p-3">
                    <div class="text-xs text-gray-500">Missing tables</div>
                    <div class="text-lg font-bold">${this.formatNumber(summary.missing_tables || 0)}</div>
                </div>
                <div class="bg-gray-900/60 border border-gray-800 rounded p-3">
                    <div class="text-xs text-gray-500">Missing columns</div>
                    <div class="text-lg font-bold">${this.formatNumber(summary.missing_columns || 0)}</div>
                </div>
            </div>
            ${missingColumns ? `<div class="mb-4 bg-amber-950/30 border border-amber-900 rounded p-3 space-y-1">${missingColumns}</div>` : '<div class="mb-4 text-xs text-emerald-300">ستون‌های کلیدی ماژول پورسانت و دفتر پرداخت کامل هستند.</div>'}
            <div class="overflow-x-auto">
                <table class="w-full text-right text-sm min-w-[560px]">
                    <thead class="bg-gray-800 text-gray-400">
                        <tr><th class="p-2 text-left">Table</th><th class="p-2">وضعیت</th><th class="p-2 text-left">Rows</th></tr>
                    </thead>
                    <tbody>${tableRows || '<tr><td class="p-3 text-gray-500" colspan="3">اطلاعاتی پیدا نشد</td></tr>'}</tbody>
                </table>
            </div>
        `;
    },

    renderMaintenanceBackups(backups = []) {
        const el = document.getElementById('maintenance-backups');
        const select = document.getElementById('maintenance-restore-file');
        if (select) {
            select.innerHTML = backups.length
                ? backups.map(b => `<option value="${this.escapeHtml(b.filename || '')}" data-phrase="${this.escapeHtml(b.restore_phrase || '')}">${this.escapeHtml(b.filename || '')} (${this.escapeHtml(b.size_human || '-')})</option>`).join('')
                : '<option value="">Backup موجود نیست</option>';
            this.updateMaintenanceRestorePhrase();
        }
        if (!el) return;
        if (!backups.length) {
            el.innerHTML = '<div class="text-gray-500">هنوز backup ساخته نشده است.</div>';
            return;
        }
        el.innerHTML = backups.map(b => `
            <div class="bg-gray-900/60 border border-gray-800 rounded p-3">
                <div class="flex items-center justify-between gap-3">
                    <div class="font-mono text-xs direction-ltr text-left break-all">${this.escapeHtml(b.filename || '')}</div>
                    <div class="text-xs text-gray-400 whitespace-nowrap">${this.escapeHtml(b.size_human || '-')}</div>
                </div>
                <div class="text-[11px] text-gray-500 mt-1 direction-ltr text-left">${this.escapeHtml(b.created_at || '-')}</div>
            </div>
        `).join('');
    },

    updateMaintenanceRestorePhrase() {
        const select = document.getElementById('maintenance-restore-file');
        const phraseEl = document.getElementById('maintenance-restore-phrase');
        const confirmation = document.getElementById('maintenance-restore-confirmation');
        const selected = select?.selectedOptions?.[0] || null;
        const phrase = selected?.dataset?.phrase || (selected?.value ? `RESTORE ${selected.value}` : '-');
        if (phraseEl) phraseEl.textContent = phrase;
        if (confirmation && !confirmation.value) confirmation.placeholder = phrase;
    },

    renderMaintenanceReport(data) {
        const el = document.getElementById('maintenance-report');
        if (!el) return;
        el.textContent = JSON.stringify(data || {}, null, 2);
    },

    maintenanceNumber(value) {
        const n = Number(value || 0);
        return Number.isFinite(n) ? this.formatNumber(n) : '0';
    },

    setMaintenanceValidationSummary(html, ok = true) {
        const el = document.getElementById('maintenance-validation-summary');
        if (!el) return;
        el.className = `mb-4 rounded-lg border p-4 text-sm leading-7 ${ok ? 'border-emerald-700 bg-emerald-950/30 text-emerald-100' : 'border-amber-700 bg-amber-950/30 text-amber-100'}`;
        el.innerHTML = html || '';
        el.classList.toggle('hidden', !html);
    },

    paymentRecordsBackfillSummary(report = {}, backup = null) {
        const verify = report.verify || {};
        const writes = report.writes || {};
        const sources = report.sources || {};
        const warnings = Array.isArray(report.warnings) ? report.warnings : [];
        const mode = String(report.mode || '');
        const hasBlockingVerifyIssue = Number(verify.duplicate_source_hash_groups || 0) > 0
            || Number(verify.duplicate_payment_uuid_groups || 0) > 0
            || Number(verify.orphan_active_transactions || 0) > 0
            || (mode !== 'dry-run' && Number(verify.missing_candidate_hashes || 0) > 0);
        const ok = !!report.success && !hasBlockingVerifyIssue && warnings.length === 0;
        const title = ok
            ? (mode === 'dry-run' ? 'Dry-run دفتر پرداخت آماده است' : 'اعتبارسنجی دفتر پرداخت موفق بود')
            : 'دفتر پرداخت نیاز به بررسی دارد';
        const backupLine = backup?.filename ? `<div><b>Backup:</b> <span class="font-mono direction-ltr">${this.escapeHtml(backup.filename)}</span></div>` : '';
        const html = `
            <div class="font-bold mb-2">${title}</div>
            ${backupLine}
            <div>کاندیدها: <b>${this.maintenanceNumber(verify.candidate_hashes)}</b> | موجود در ledger: <b>${this.maintenanceNumber(verify.existing_candidate_hashes)}</b> | missing: <b>${this.maintenanceNumber(verify.missing_candidate_hashes)}</b></div>
            <div>created/updated: <b>${this.maintenanceNumber(writes.created_or_updated)}</b> | would update: <b>${this.maintenanceNumber(writes.would_create_or_update)}</b> | links: <b>${this.maintenanceNumber(writes.links)}</b> | transaction projections: <b>${this.maintenanceNumber(writes.transaction_projection_updates)}</b></div>
            <div>deal JSON: <b>${this.maintenanceNumber(sources.deal_json?.candidates)}</b> | transactions: <b>${this.maintenanceNumber(sources.transactions?.candidates)}</b> | matched transactions: <b>${this.maintenanceNumber(sources.transactions?.matched_to_deal_json)}</b></div>
            <div>duplicates source/payment UUID: <b>${this.maintenanceNumber(verify.duplicate_source_hash_groups)}</b>/<b>${this.maintenanceNumber(verify.duplicate_payment_uuid_groups)}</b> | orphan active transactions: <b>${this.maintenanceNumber(verify.orphan_active_transactions)}</b></div>
            ${report.report_path ? `<div>Report: <span class="font-mono direction-ltr">${this.escapeHtml(report.report_path)}</span></div>` : ''}
            ${warnings.length ? `<div>Warnings: ${warnings.map(w => this.escapeHtml(String(w))).join('، ')}</div>` : ''}
        `;
        return { ok, title, html };
    },

    tafsiliBackfillSummary(report = {}, backup = null) {
        const mode = String(report.mode || '');
        const ok = mode === 'verify'
            ? (!!report.ok && Number(report.missing_or_mismatched || 0) === 0)
            : !!report.ok;
        const title = ok
            ? (mode === 'dry-run' ? 'Dry-run کد تفصیلی آماده است' : 'اعتبارسنجی کد تفصیلی موفق بود')
            : 'کد تفصیلی نیاز به بررسی دارد';
        const backupLine = backup?.filename ? `<div><b>Backup:</b> <span class="font-mono direction-ltr">${this.escapeHtml(backup.filename)}</span></div>` : '';
        const html = `
            <div class="font-bold mb-2">${title}</div>
            ${backupLine}
            <div>اسکن شده: <b>${this.maintenanceNumber(report.scanned)}</b> | درست: <b>${this.maintenanceNumber(report.already_ok)}</b> | updated: <b>${this.maintenanceNumber(report.updated)}</b> | would update: <b>${this.maintenanceNumber(report.would_update)}</b></div>
            <div>missing/mismatched: <b>${this.maintenanceNumber(report.missing_or_mismatched)}</b> | unresolved untouched: <b>${this.maintenanceNumber(report.unresolved)}</b> | blank destination: <b>${this.maintenanceNumber(report.blank_destination)}</b></div>
            <div class="text-xs opacity-80">ردیف‌های unresolved تغییر داده نمی‌شوند؛ فقط بانک‌هایی که mapping تفصیلی معتبر دارند update می‌شوند.</div>
        `;
        return { ok, title, html };
    },

    showMaintenanceBackfillResult(summary, successTitle = 'انجام شد') {
        this.setMaintenanceValidationSummary(summary.html, summary.ok);
        Swal.fire({
            title: summary.ok ? successTitle : 'نیاز به بررسی',
            html: `<div class="text-right text-sm leading-7">${summary.html}</div>`,
            icon: summary.ok ? 'success' : 'warning',
            confirmButtonText: 'باشه'
        });
    },

    getMaintenanceBackfillLimit() {
        const raw = document.getElementById('maintenance-backfill-limit')?.value || '0';
        const limit = Number.parseInt(raw, 10);
        return Number.isFinite(limit) && limit > 0 ? String(limit) : '0';
    },

    getMaintenanceTafsiliBackfillLimit() {
        const raw = document.getElementById('maintenance-tafsili-backfill-limit')?.value || '0';
        const limit = Number.parseInt(raw, 10);
        return Number.isFinite(limit) && limit > 0 ? String(limit) : '0';
    },

    async maintenanceCreateBackup() {
        if (!this.canCurrentUserCapabilityClient('maintenance.admin.backup')) {
            Swal.fire('عدم دسترسی', 'دسترسی ساخت backup ندارید', 'warning');
            return;
        }
        const confirm = await Swal.fire({
            title: 'ساخت Backup دیتابیس',
            text: 'یک dump کامل از دیتابیس فعلی ساخته می‌شود.',
            icon: 'question',
            showCancelButton: true,
            confirmButtonText: 'ساخت Backup',
            cancelButtonText: 'انصراف'
        });
        if (!confirm.isConfirmed) return;
        Swal.fire({ title: 'در حال ساخت backup...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
        const res = await this.req('create_db_backup', { label: 'manual' });
        Swal.close();
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'ساخت backup ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.backup || {});
        await this.loadMaintenanceDashboard();
        Swal.fire('انجام شد', `Backup ساخته شد: ${res.backup?.filename || ''}`, 'success');
    },

    async maintenanceBackfillPreview() {
        const res = await this.req('preview_payment_records_backfill', { limit: this.getMaintenanceBackfillLimit() });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'پیش‌نمایش backfill ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.report || {});
        this.showMaintenanceBackfillResult(this.paymentRecordsBackfillSummary(res.report || {}), 'Dry-run آماده است');
    },

    async maintenanceBackfillVerify() {
        const res = await this.req('verify_payment_records_backfill', { limit: this.getMaintenanceBackfillLimit() });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'verify ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.report || {});
        this.showMaintenanceBackfillResult(this.paymentRecordsBackfillSummary(res.report || {}), 'Verify موفق بود');
    },

    async maintenanceBackfillRun() {
        if (!this.canCurrentUserCapabilityClient('maintenance.admin.backfill.run')) {
            Swal.fire('عدم دسترسی', 'دسترسی اجرای backfill ندارید', 'warning');
            return;
        }
        const confirm = await Swal.fire({
            title: 'اجرای Backfill',
            html: '<div class="text-right text-sm">قبل از اجرا backup کامل ساخته می‌شود. برای ادامه عبارت <code>RUN_BACKFILL</code> را وارد کنید.</div>',
            input: 'text',
            inputPlaceholder: 'RUN_BACKFILL',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'اجرای Backfill',
            cancelButtonText: 'انصراف',
            preConfirm: (value) => {
                if (String(value || '').trim() !== 'RUN_BACKFILL') {
                    Swal.showValidationMessage('عبارت تایید نادرست است');
                    return false;
                }
                return value;
            }
        });
        if (!confirm.isConfirmed) return;
        Swal.fire({ title: 'در حال اجرای backfill...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
        const res = await this.req('run_payment_records_backfill', {
            limit: this.getMaintenanceBackfillLimit(),
            confirmation: 'RUN_BACKFILL'
        });
        Swal.close();
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'اجرای backfill ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport({ backup: res.backup || {}, report: res.report || {} });
        await this.loadMaintenanceDashboard();
        this.showMaintenanceBackfillResult(this.paymentRecordsBackfillSummary(res.report || {}, res.backup || null), 'Backfill اجرا و verify شد');
    },

    async maintenanceTafsiliBackfillPreview() {
        const res = await this.req('preview_payment_tafsili_backfill', { limit: this.getMaintenanceTafsiliBackfillLimit() });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'پیش‌نمایش backfill کد تفصیلی ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.report || {});
        this.showMaintenanceBackfillResult(this.tafsiliBackfillSummary(res.report || {}), 'Dry-run کد تفصیلی آماده است');
    },

    async maintenanceTafsiliBackfillVerify() {
        const res = await this.req('verify_payment_tafsili_backfill', { limit: this.getMaintenanceTafsiliBackfillLimit() });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'verify کد تفصیلی ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.report || {});
        this.showMaintenanceBackfillResult(this.tafsiliBackfillSummary(res.report || {}), 'Verify کد تفصیلی موفق بود');
    },

    async maintenanceTafsiliBackfillRun() {
        if (!this.canCurrentUserCapabilityClient('maintenance.admin.backfill.run')) {
            Swal.fire('عدم دسترسی', 'دسترسی اجرای backfill ندارید', 'warning');
            return;
        }
        const confirm = await Swal.fire({
            title: 'اجرای Backfill کد تفصیلی',
            html: '<div class="text-right text-sm">قبل از اجرا backup کامل ساخته می‌شود. برای ادامه عبارت <code>RUN_TAFSILI_BACKFILL</code> را وارد کنید.</div>',
            input: 'text',
            inputPlaceholder: 'RUN_TAFSILI_BACKFILL',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'اجرای Backfill',
            cancelButtonText: 'انصراف',
            preConfirm: (value) => {
                if (String(value || '').trim() !== 'RUN_TAFSILI_BACKFILL') {
                    Swal.showValidationMessage('عبارت تایید نادرست است');
                    return false;
                }
                return value;
            }
        });
        if (!confirm.isConfirmed) return;
        Swal.fire({ title: 'در حال اجرای backfill کد تفصیلی...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
        const res = await this.req('run_payment_tafsili_backfill', {
            limit: this.getMaintenanceTafsiliBackfillLimit(),
            confirmation: 'RUN_TAFSILI_BACKFILL'
        });
        Swal.close();
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'اجرای backfill کد تفصیلی ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport({ backup: res.backup || {}, report: res.report || {} });
        await this.loadMaintenanceDashboard();
        this.showMaintenanceBackfillResult(this.tafsiliBackfillSummary(res.report || {}, res.backup || null), 'Backfill کد تفصیلی اجرا و verify شد');
    },

    async maintenanceRestoreBackup() {
        if (!this.canCurrentUserCapabilityClient('maintenance.admin.restore')) {
            Swal.fire('عدم دسترسی', 'دسترسی restore ندارید', 'warning');
            return;
        }
        const filename = document.getElementById('maintenance-restore-file')?.value || '';
        const confirmation = document.getElementById('maintenance-restore-confirmation')?.value || '';
        if (!filename) {
            Swal.fire('خطا', 'یک فایل backup انتخاب کنید', 'error');
            return;
        }
        const expected = `RESTORE ${filename}`;
        if (confirmation.trim() !== expected) {
            Swal.fire('خطا', `عبارت تایید باید دقیقاً ${expected} باشد`, 'error');
            return;
        }
        const confirm = await Swal.fire({
            title: 'Restore دیتابیس؟',
            text: 'قبل از restore یک backup جدید ساخته می‌شود، سپس دیتابیس فعلی با فایل انتخاب‌شده جایگزین می‌شود.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'Restore',
            cancelButtonText: 'انصراف'
        });
        if (!confirm.isConfirmed) return;
        Swal.fire({ title: 'در حال restore...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
        const res = await this.req('restore_db_backup', { filename, confirmation });
        Swal.close();
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'restore ناموفق بود', 'error');
            return;
        }
        this.renderMaintenanceReport(res.report || {});
        await this.loadMaintenanceDashboard();
        Swal.fire('انجام شد', 'Restore انجام شد', 'success');
    },

    async runMigration() {
        if (!this.canCurrentUserCapabilityClient('migration.admin.run')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند migration را اجرا کند', 'error');
            return;
        }

        try {
            const preview = await this.req('preview_migration', {});
            if (preview.status !== 'success') {
                Swal.fire('خطا', preview.message || 'خطا در بررسی migration', 'error');
                return;
            }
            const pendingSummary = preview.pending_summary || {};
            const pendingItems = Array.isArray(preview.pending_items) ? preview.pending_items : [];
            const previewHtml = `
                <div class="text-right text-sm space-y-2">
                    <div>تعداد جدول‌های مفقود: <b>${pendingSummary.missing_tables || 0}</b></div>
                    <div>تعداد ستون‌های مفقود (پایش‌شده): <b>${pendingSummary.missing_columns || 0}</b></div>
                    <div>تعداد ایندکس‌های مفقود (پایش‌شده): <b>${pendingSummary.missing_indexes || 0}</b></div>
                    ${pendingItems.length ? `<div class="mt-2 max-h-48 overflow-auto border border-slate-700 rounded p-2 bg-slate-900/40">${pendingItems.slice(0,50).map(i => `<div class="py-0.5">${this.escapeHtml(i)}</div>`).join('')}</div>` : '<div class="text-green-400">مورد جدیدی در لیست پایش‌شده پیدا نشد. اجرای Migration همچنان امن و idempotent است.</div>'}
                </div>`;
            const confirm = await Swal.fire({
                title: 'پیش‌نمایش Migration',
                html: previewHtml,
                icon: 'info',
                showCancelButton: true,
                confirmButtonText: 'اجرای Migration',
                cancelButtonText: 'انصراف',
                width: 700
            });
            if (!confirm.isConfirmed) return;

            Swal.fire({
                title: 'در حال اجرای Migration...',
                text: 'لطفاً صبر کنید',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });
            const res = await this.req('run_migration', {});
            Swal.close();
            if (res.status === 'success') {
                const report = res.report || {};
                const html = `
                    <div class="text-right text-sm space-y-2">
                        <div>مدت اجرا: <b>${report.duration_ms ?? 0}</b> ms</div>
                        <div>جدول‌های جدید: <b>${(report.created_tables || []).length}</b></div>
                        <div>ستون‌های جدید: <b>${(report.added_columns || []).length}</b></div>
                        <div>ایندکس‌های جدید: <b>${(report.added_indexes || []).length}</b></div>
                        ${(report.created_tables || []).length ? `<div><b>جدول‌ها:</b> ${(report.created_tables || []).map(x => this.escapeHtml(x)).join('، ')}</div>` : ''}
                    </div>`;
                Swal.fire({
                    icon: 'success',
                    title: 'موفق',
                    html: html || (res.message || 'Migration با موفقیت اجرا شد'),
                    width: 700
                });
                // Reload users list
                this.loadUsers();
            } else {
                Swal.fire('خطا', res.message || 'خطا در اجرای migration', 'error');
            }
        } catch (error) {
            console.error('Error in runMigration:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در اجرای migration: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    async runActivityTypeRepair() {
        if (!this.canCurrentUserCapabilityClient('activity_repair.admin.run')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند ترمیم نوع فعالیت‌ها را اجرا کند', 'error');
            return;
        }

        try {
            const preview = await this.req('preview_activity_type_repair', {});
            if (preview.status !== 'success') {
                Swal.fire('خطا', preview.message || 'خطا در پیش‌نمایش ترمیم', 'error');
                return;
            }

            const report = preview.report || {};
            const sources = report.match_sources || {};
            const previewItems = Array.isArray(report.preview) ? report.preview : [];
            const previewHtml = `
                <div class="text-right text-sm space-y-2">
                    <div>فایل بکاپ: <b>${this.escapeHtml(report.backup_path || '-')}</b></div>
                    <div>تعداد رکوردهای خراب فعلی: <b>${this.formatNumber(report.current_broken_rows || 0)}</b></div>
                    <div>تعداد رکوردهای قابل ترمیم: <b>${this.formatNumber(report.matched_rows || 0)}</b></div>
                    <div>با stage: <b>${this.formatNumber(sources.stage || 0)}</b> | با lookup: <b>${this.formatNumber(sources.lookup || 0)}</b> | با backup match: <b>${this.formatNumber((sources.didar_activity_id || 0) + (sources.composite || 0) + (sources.raw_json || 0))}</b></div>
                    ${previewItems.length ? `<div class="mt-2 max-h-56 overflow-auto border border-slate-700 rounded p-2 bg-slate-900/40">${previewItems.map(item => `<div class="py-1 border-b border-slate-800 last:border-b-0"><div>#${this.escapeHtml(String(item.id || ''))} → <b>${this.escapeHtml(item.new_title || '')}</b></div><div class="text-xs text-gray-500">منبع: ${this.escapeHtml(item.match_source || '-')} | stage: ${this.escapeHtml(item.stage || '-')}</div></div>`).join('')}</div>` : '<div class="text-yellow-300">نمونه‌ای برای نمایش پیدا نشد.</div>'}
                    <div class="text-xs text-gray-500 pt-2">فقط رکوردهایی که نوع فعالیت‌شان خالی/فعالیت/نامشخص است تغییر می‌کنند.</div>
                </div>`;

            const confirm = await Swal.fire({
                title: 'پیش‌نمایش ترمیم نوع فعالیت‌ها',
                html: previewHtml,
                icon: 'info',
                showCancelButton: true,
                confirmButtonText: 'اجرای ترمیم',
                cancelButtonText: 'انصراف',
                width: 760
            });
            if (!confirm.isConfirmed) return;

            Swal.fire({
                title: 'در حال اجرای ترمیم...',
                text: 'لطفاً صبر کنید',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });

            const res = await this.req('run_activity_type_repair', {});
            Swal.close();

            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطا در اجرای ترمیم', 'error');
                return;
            }

            const runReport = res.report || {};
            const runSources = runReport.match_sources || {};
            Swal.fire({
                icon: 'success',
                title: 'ترمیم انجام شد',
                html: `
                    <div class="text-right text-sm space-y-2">
                        <div>رکوردهای خراب بررسی‌شده: <b>${this.formatNumber(runReport.current_broken_rows || 0)}</b></div>
                        <div>رکوردهای ترمیم‌شده: <b>${this.formatNumber(runReport.updated_rows || 0)}</b></div>
                        <div>stage: <b>${this.formatNumber(runSources.stage || 0)}</b> | lookup: <b>${this.formatNumber(runSources.lookup || 0)}</b> | backup/raw: <b>${this.formatNumber((runSources.didar_activity_id || 0) + (runSources.composite || 0) + (runSources.raw_json || 0))}</b></div>
                    </div>
                `,
                width: 700
            });
        } catch (error) {
            console.error('Error in runActivityTypeRepair:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در اجرای ترمیم: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    async runWonKarizReconcile() {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند ترمیم کاریز معاملات موفق را اجرا کند', 'error');
            return;
        }

        try {
            const preview = await this.req('deal/reconcile_won_kariz_preview', {});
            if (preview.status !== 'success') {
                Swal.fire('خطا', preview.message || 'خطا در پیش‌نمایش ترمیم', 'error');
                return;
            }

            const report = preview.report || {};
            const rows = Array.isArray(report.preview) ? report.preview : [];
            const previewHtml = `
                <div class="text-right text-sm space-y-2">
                    <div>کل موارد نیازمند ترمیم: <b>${this.formatNumber(report.total_candidates || 0)}</b></div>
                    <div>نیازمند اصلاح مرحله کاریز: <b>${this.formatNumber(report.needs_pipeline_fix || 0)}</b></div>
                    <div>نیازمند اصلاح خروج از کاریز: <b>${this.formatNumber(report.needs_virtual_fix || 0)}</b></div>
                    ${rows.length ? `<div class="mt-2 max-h-64 overflow-auto border border-slate-700 rounded p-2 bg-slate-900/40">${rows.map(item => `
                        <div class="py-1 border-b border-slate-800 last:border-b-0">
                            <div><b>${this.escapeHtml(item.didar_deal_id || '')}</b> | ${this.escapeHtml(item.title || '-')}</div>
                            <div class="text-xs text-gray-500">
                                stage: ${this.escapeHtml(item.pipeline_stage || '-')}
                                | fix-stage: ${Number(item.needs_pipeline_fix || 0) ? 'بله' : 'خیر'}
                                | fix-kariz: ${Number(item.needs_virtual_fix || 0) ? 'بله' : 'خیر'}
                            </div>
                        </div>
                    `).join('')}</div>` : '<div class="text-green-300">موردی برای ترمیم وجود ندارد.</div>'}
                </div>`;

            const confirm = await Swal.fire({
                title: 'پیش‌نمایش ترمیم معاملات موفق',
                html: previewHtml,
                icon: 'info',
                showCancelButton: true,
                confirmButtonText: 'اجرای ترمیم',
                cancelButtonText: 'انصراف',
                width: 760
            });
            if (!confirm.isConfirmed) return;

            Swal.fire({
                title: 'در حال ترمیم...',
                text: 'لطفاً صبر کنید',
                allowOutsideClick: false,
                didOpen: () => Swal.showLoading()
            });

            const res = await this.req('deal/reconcile_won_kariz_run', {});
            Swal.close();
            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'ترمیم انجام نشد', 'error');
                return;
            }

            const runReport = res.report || {};
            const errors = Array.isArray(runReport.errors) ? runReport.errors : [];
            const resultHtml = `
                <div class="text-right text-sm space-y-2">
                    <div>کل موارد بررسی‌شده: <b>${this.formatNumber(runReport.total_candidates || 0)}</b></div>
                    <div>اصلاح مرحله کاریز: <b>${this.formatNumber(runReport.fixed_pipeline || 0)}</b></div>
                    <div>اصلاح خروج از کاریز: <b>${this.formatNumber(runReport.fixed_virtual || 0)}</b></div>
                    <div>خطاها: <b>${this.formatNumber(runReport.errors_count || 0)}</b></div>
                    ${errors.length ? `<div class="mt-2 max-h-56 overflow-auto border border-red-800 rounded p-2 bg-red-950/20">${errors.map(e => `
                        <div class="py-1 border-b border-red-900/50 last:border-b-0 text-xs">
                            ${this.escapeHtml(e.deal_id || '-')} : ${this.escapeHtml(e.error || '')}
                        </div>
                    `).join('')}</div>` : ''}
                </div>`;

            Swal.fire({
                icon: 'success',
                title: 'ترمیم انجام شد',
                html: resultHtml,
                width: 720
            });
        } catch (error) {
            console.error('Error in runWonKarizReconcile:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در اجرای ترمیم: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    async loadCommissions() {
        const isManager = this.isAdminOrCrmSpecialist();
        const wrap = document.getElementById('commissions-list');
        if (wrap) wrap.innerHTML = '<div class="text-gray-400 text-sm">در حال بارگذاری...</div>';
            const res = await this.req(isManager ? 'get_commission_rules' : 'get_deal_commissions', {});
        if (res.status !== 'success') {
            if (wrap) wrap.innerHTML = `<div class="text-red-400 text-sm">${this.escapeHtml(res.message || 'خطا در دریافت')}</div>`;
            return;
        }
        const rows = Array.isArray(isManager ? res.rules : res.commissions) ? (isManager ? res.rules : res.commissions) : [];

        if (isManager) {
            this.initJalaliDatePickers([
                document.getElementById('commission-filter-date-from'),
                document.getElementById('commission-filter-date-to')
            ], { linkRange: true });
            this.bindCommissionFilterReload();
            const dcRes = await this.req('get_deal_commissions', this.collectCommissionFilters());
            const dealRows = dcRes.status === 'success' && Array.isArray(dcRes.commissions) ? dcRes.commissions : [];
            if (dcRes.status !== 'success') {
                wrap.innerHTML = `<div class="text-red-400 text-sm">${this.escapeHtml(dcRes.message || 'خطا در دریافت صف پورسانت')}</div>`;
                return;
            }
            this.state.visibleCommissionIds = dealRows.map((r) => Number(r.id || 0)).filter((v) => v > 0);
            this.state.selectedCommissionIds = this.state.selectedCommissionIds || [];
            const role = String(this.state.user?.role || '').toLowerCase();
            const isSystem = role === 'system' || role === 'super_admin';
            const isAdmin = role === 'admin' || role === 'super_admin';
            const canDeleteByRole = isSystem || isAdmin;
            const isHistoricalFinalized = (row) => {
                const s = String(row.status || '').toUpperCase();
                const finalized = ['APPROVED', 'REJECTED', 'SETTLED', 'READY_FOR_PAYOUT', 'TRANSFERRED_TO_PAYROLL'].includes(s);
                const dateStr = String(row.settled_at || row.updated_at || row.created_at || '');
                const rowMonth = dateStr ? dateStr.slice(0, 7) : '';
                const nowMonth = new Date().toISOString().slice(0, 7);
                return finalized && rowMonth && rowMonth < nowMonth;
            };
            const deleteAction = (row) => {
                if (!canDeleteByRole) return '<span class="text-gray-500 text-[11px]">-</span>';
                if (!isSystem && isHistoricalFinalized(row)) {
                    return `<button disabled class="px-2 py-1 rounded text-[11px] bg-gray-700 text-gray-400 cursor-not-allowed" title="حذف رکورد نهایی ماه‌های گذشته مجاز نیست"><i class="fa-solid fa-trash"></i></button>`;
                }
                return `<button class="px-2 py-1 rounded text-[11px] bg-red-700 hover:bg-red-600 text-white" title="حذف رکورد" onclick="app.deleteCommissionRecord(${Number(row.id || 0)})"><i class="fa-solid fa-trash"></i></button>`;
            };
            const statusLabel = (status) => ({
                CALCULATED: 'محاسبه‌شده',
                PENDING_MANAGER_REVIEW: 'در انتظار تایید مدیر',
                PENDING_FINANCE: 'در انتظار مالی',
                PENDING_FINANCE_APPROVAL: 'در انتظار تایید مالی',
                READY_FOR_PAYOUT: 'آماده پرداخت',
                APPROVED: 'تایید شده',
                SETTLED: 'تسویه شده',
                REJECTED: 'رد شده'
            }[String(status || '').toUpperCase()] || String(status || '-'));
            const adminStatusActions = (row) => {
                if (String(row.row_type || '') === 'rule_performance') {
                    return '<span class="text-gray-500 text-[11px]">محاسبه مستقیم</span>';
                }
                const id = Number(row.id || 0);
                return `
                    <button class="btn-primary px-2 py-1 rounded text-[11px] bg-green-700 hover:bg-green-600" onclick="app.advanceCommissionStatus(${id}, 'APPROVED')">تایید</button>
                    <button class="btn-primary px-2 py-1 rounded text-[11px] bg-yellow-700 hover:bg-yellow-600" onclick="app.advanceCommissionStatus(${id}, 'PENDING_MANAGER_REVIEW')">معلق</button>
                    <button class="btn-primary px-2 py-1 rounded text-[11px] bg-red-700 hover:bg-red-600" onclick="app.advanceCommissionStatus(${id}, 'REJECTED')">رد</button>
                `;
            };
            const nextActions = (status, id) => {
                if (String(id || '').startsWith('rule-')) {
                    return '<span class="text-gray-500 text-[11px]">محاسبه مستقیم</span>';
                }
                const s = String(status || '').toUpperCase();
                if (s === 'PENDING_MANAGER_REVIEW') {
                    return `<button class="btn-primary px-2 py-1 rounded text-[11px] bg-indigo-600 hover:bg-indigo-500" onclick="app.advanceCommissionStatus(${Number(id)}, 'PENDING_FINANCE')">تایید مدیر</button>`;
                }
                if (s === 'PENDING_FINANCE' || s === 'PENDING_FINANCE_APPROVAL') {
                    return `<button class="btn-primary px-2 py-1 rounded text-[11px] bg-blue-600 hover:bg-blue-500" onclick="app.advanceCommissionStatus(${Number(id)}, 'READY_FOR_PAYOUT')">تایید مالی</button>`;
                }
                if (s === 'READY_FOR_PAYOUT' || s === 'APPROVED') {
                    return `<button class="btn-primary px-2 py-1 rounded text-[11px] bg-green-600 hover:bg-green-500" onclick="app.advanceCommissionStatus(${Number(id)}, 'SETTLED')">تسویه شد</button>`;
                }
                return '<span class="text-gray-500 text-[11px]">-</span>';
            };

            wrap.innerHTML = `
            <div class="text-sm text-cyan-300 font-bold mb-2">صف تایید پورسانت معاملات</div>
            <table class="w-full text-xs text-gray-300 mb-6">
                <thead>
                    <tr class="border-b border-gray-700 text-gray-400">
                        <th class="text-right p-2"><input type="checkbox" id="commission-queue-select-all" onchange="app.toggleSelectAllCommissions(this.checked)"></th>
                        <th class="text-right p-2">Deal</th>
                        <th class="text-right p-2">تاریخ واریز</th>
                        <th class="text-right p-2">کارشناس</th>
                        <th class="text-right p-2">مبلغ پرداختی</th>
                        <th class="text-right p-2">نرخ</th>
                        <th class="text-right p-2">پورسانت</th>
                        <th class="text-right p-2">وضعیت</th>
                        <th class="text-right p-2">اقدامات</th>
                    </tr>
                </thead>
                <tbody>
                    ${dealRows.length ? dealRows.map(row => `
                        <tr class="border-b border-gray-800">
                            <td class="p-2">${String(row.row_type || '') === 'rule_performance' ? '' : `<input type="checkbox" class="commission-row-select" value="${Number(row.id || 0)}" onchange="app.toggleCommissionSelection(${Number(row.id || 0)}, this.checked)">`}</td>
                            <td class="p-2">${this.escapeHtml(String(row.rule_title || row.deal_didar_id || '-'))}</td>
                            <td class="p-2">${this.escapeHtml(row.deposit_date_to && row.deposit_date_to !== row.deposit_date ? this.ccDateRangeLabel(row.deposit_date, row.deposit_date_to) : this.ccDateLabel(row.deposit_date || ''))}</td>
                            <td class="p-2">${this.escapeHtml(String(row.salesperson_name || '-'))}</td>
                            <td class="p-2">
                                <div>${this.formatMoney(row.ncr_amount || 0)}</div>
                                ${row.payments_count ? `<div class="text-[10px] text-gray-500">${this.formatNumber(row.payments_count)} پرداخت</div>` : ''}
                            </td>
                            <td class="p-2">${Number(row.applied_rate_percent || 0).toFixed(2)}%</td>
                            <td class="p-2">${this.formatMoney(row.calculated_commission || 0)}</td>
                            <td class="p-2">${this.escapeHtml(statusLabel(row.status))}</td>
                            <td class="p-2">
                                <div class="flex flex-wrap gap-1 items-center">
                                    ${adminStatusActions(row)}
                                    ${String(row.row_type || '') === 'rule_performance' ? '' : deleteAction(row)}
                                </div>
                            </td>
                        </tr>
                    `).join('') : `<tr><td colspan="9" class="p-2 text-gray-500">رکوردی برای تایید وجود ندارد</td></tr>`}
                </tbody>
            </table>
            <div class="text-sm text-cyan-300 font-bold mb-2">قوانین پورسانت</div>
            <table class="w-full text-xs text-gray-300">
                <thead>
                    <tr class="border-b border-gray-700 text-gray-400">
                        <th class="text-right p-2">عنوان قانون</th>
                        <th class="text-right p-2">نوع قانون</th>
                        <th class="text-right p-2">دامنه محصول</th>
                        <th class="text-right p-2">مبلغ ثابت</th>
                        <th class="text-right p-2">درصد پاداش</th>
                        <th class="text-right p-2">وضعیت</th>
                    </tr>
                </thead>
                <tbody>
                    ${rows.map(row => `
                        <tr class="border-b border-gray-800">
                            <td class="p-2">${this.escapeHtml(String(row.title || '-'))}</td>
                            <td class="p-2">${this.escapeHtml(this.getCommissionRuleTypeLabel(row.rule_type))}</td>
                            <td class="p-2">${this.escapeHtml(this.getCommissionProductScopeLabel(row))}</td>
                            <td class="p-2">${this.escapeHtml(this.getCommissionRuleAmountLabel(row))}</td>
                            <td class="p-2">${Number(row.reward_percentage || 0).toFixed(2)}%</td>
                            <td class="p-2">فعال</td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
            `;
            this.populateCommissionSalespersonFilter(dealRows);
            this.syncCommissionBulkDeleteButton();
            return;
        }

        if (!rows.length) {
            if (wrap) wrap.innerHTML = '<div class="text-gray-400 text-sm">رکورد پورسانت موجود نیست</div>';
            return;
        }

        wrap.innerHTML = `
            <table class="w-full text-xs text-gray-300">
                <thead>
                    <tr class="border-b border-gray-700 text-gray-400">
                        <th class="text-right p-2">قانون</th>
                        <th class="text-right p-2">مبلغ واریزی</th>
                        <th class="text-right p-2">مبلغ پورسانت</th>
                        <th class="text-right p-2">تاریخ واریز</th>
                    </tr>
                </thead>
                <tbody>
                    ${rows.map(row => `
                        <tr class="border-b border-gray-800">
                            <td class="p-2">
                                <div>${this.escapeHtml(String(row.rule_title || row.deal_didar_id || '-'))}</div>
                                <div class="text-[10px] text-gray-500">${this.escapeHtml(String(row.product_name || ''))}</div>
                            </td>
                            <td class="p-2">
                                <div>${this.formatMoney(row.ncr_amount || 0)}</div>
                                ${row.payments_count ? `<div class="text-[10px] text-gray-500">${this.formatNumber(row.payments_count)} پرداخت</div>` : ''}
                            </td>
                            <td class="p-2">${this.formatMoney(row.calculated_commission || row.calculated_amount || 0)}</td>
                            <td class="p-2">${this.escapeHtml(row.deposit_date_to && row.deposit_date_to !== row.deposit_date ? this.ccDateRangeLabel(row.deposit_date, row.deposit_date_to) : this.ccDateLabel(row.deposit_date || row.created_at || ''))}</td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
        `;
    },

    toggleSelectAllCommissions(checked) {
        const val = !!checked;
        const ids = [];
        Array.from(document.querySelectorAll('.commission-row-select')).forEach((el) => {
            el.checked = val;
            const id = Number(el.value || 0);
            if (id > 0) ids.push(id);
        });
        this.state.selectedCommissionIds = val ? ids : [];
        this.syncCommissionBulkDeleteButton();
    },

    toggleCommissionSelection(id, checked) {
        id = Number(id || 0);
        if (id <= 0) return;
        const selected = new Set(Array.isArray(this.state.selectedCommissionIds) ? this.state.selectedCommissionIds : []);
        if (checked) selected.add(id); else selected.delete(id);
        this.state.selectedCommissionIds = Array.from(selected);
        const all = Array.from(document.querySelectorAll('.commission-row-select'));
        const selectAll = document.getElementById('commission-queue-select-all');
        if (selectAll) {
            selectAll.checked = all.length > 0 && all.every((el) => el.checked);
        }
        this.syncCommissionBulkDeleteButton();
    },

    syncCommissionBulkDeleteButton() {
        const btn = document.getElementById('commission-bulk-delete-btn');
        if (!btn) return;
        const count = Array.isArray(this.state.selectedCommissionIds) ? this.state.selectedCommissionIds.length : 0;
        const enabled = count > 0;
        btn.disabled = !enabled;
        btn.classList.toggle('opacity-60', !enabled);
        btn.classList.toggle('cursor-not-allowed', !enabled);
        btn.title = enabled ? `حذف ${count} رکورد انتخاب‌شده` : 'ابتدا رکوردها را انتخاب کنید';
    },

    async bulkDeleteSelectedCommissions() {
        const ids = Array.isArray(this.state.selectedCommissionIds) ? this.state.selectedCommissionIds : [];
        if (!ids.length) {
            Swal.fire('اطلاع', 'هیچ رکوردی انتخاب نشده است', 'info');
            return;
        }
        const confirm = await Swal.fire({
            title: 'حذف گروهی رکوردها',
            text: `آیا از حذف ${ids.length} رکورد انتخاب شده اطمینان دارید؟`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'لغو'
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('bulk_delete_deal_commissions', { ids: JSON.stringify(ids) });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'حذف گروهی انجام نشد', 'error');
            return;
        }
        this.state.selectedCommissionIds = [];
        await this.loadCommissions();
        const deleted = Array.isArray(res.deleted) ? res.deleted.length : 0;
        const skipped = Array.isArray(res.skipped) ? res.skipped.length : 0;
        const icon = deleted > 0 && skipped === 0 ? 'success' : (deleted > 0 ? 'warning' : 'info');
        Swal.fire({ toast: true, position: 'bottom-end', icon, title: `حذف: ${deleted} | رد شده: ${skipped}`, showConfirmButton: false, timer: 2600 });
    },

    collectCommissionFilters() {
        const toGregorianDate = (value) => {
            const raw = normalizeDate(value || '');
            if (!raw) return '';
            const parts = raw.split('/');
            const year = Number(parts[0] || 0);
            if (year > 1600) {
                return raw.replace(/\//g, '-');
            }
            const converted = persianToGregorian(raw, '00:00');
            return converted ? String(converted).slice(0, 10) : raw;
        };
        const dateFromRaw = (document.getElementById('commission-filter-date-from')?.value || '').trim();
        const dateToRaw = (document.getElementById('commission-filter-date-to')?.value || '').trim();
        return {
            date_from: toGregorianDate(dateFromRaw),
            date_to: toGregorianDate(dateToRaw),
            date_from_jalali: normalizeDate(dateFromRaw),
            date_to_jalali: normalizeDate(dateToRaw),
            salesperson_user_id: (document.getElementById('commission-filter-salesperson')?.value || '').trim(),
            amount_min: (document.getElementById('commission-filter-amount-min')?.value || '').trim(),
            amount_max: (document.getElementById('commission-filter-amount-max')?.value || '').trim(),
            status: (document.getElementById('commission-filter-status')?.value || '').trim()
        };
    },

    bindCommissionFilterReload() {
        const ids = [
            'commission-filter-date-from',
            'commission-filter-date-to',
            'commission-filter-salesperson',
            'commission-filter-status',
            'commission-filter-amount-min',
            'commission-filter-amount-max'
        ];
        ids.forEach((id) => {
            const el = document.getElementById(id);
            if (!el || el.dataset.commissionFilterBound === '1') return;
            el.dataset.commissionFilterBound = '1';
            const reload = () => {
                clearTimeout(this._commissionFilterReloadTimer);
                this._commissionFilterReloadTimer = setTimeout(() => this.loadCommissions(), 250);
            };
            el.addEventListener('change', reload);
            if (id === 'commission-filter-date-from' || id === 'commission-filter-date-to') {
                el.addEventListener('blur', reload);
            }
        });
    },

    populateCommissionSalespersonFilter(rows) {
        const sel = document.getElementById('commission-filter-salesperson');
        if (!sel) return;
        const current = String(sel.value || '');
        const map = {};
        (rows || []).forEach((r) => {
            const id = Number(r.salesperson_user_id || 0);
            if (id <= 0) return;
            if (!map[id]) map[id] = String(r.salesperson_name || `کاربر #${id}`);
        });
        sel.innerHTML = '<option value="">همه کارشناسان</option>' + Object.keys(map).map((id) => `<option value="${id}">${this.escapeHtml(map[id])}</option>`).join('');
        if (current !== '') {
            sel.value = current;
        }
    },

    getCommissionProductScopeLabel(row) {
        const scope = this.getCommissionProductScopeItems(row);
        if (scope.all) return scope.label;
        if (scope.items.length) return scope.items.join('، ');
        return scope.label;
    },

    getCommissionProductScopeItems(row) {
        if (row?.product_scope_label) {
            const label = String(row.product_scope_label);
            if (label === 'همه محصولات') return { all: true, label, items: [] };
            return { all: false, label, items: label.split(/[،,]/).map(item => item.trim()).filter(Boolean) };
        }
        if (row?.product_name) {
            const label = String(row.product_name);
            return { all: false, label, items: [label] };
        }
        const services = Array.isArray(row?.requested_services) ? row.requested_services : (() => {
            try { return JSON.parse(row?.requested_services || '[]'); } catch (_) { return []; }
        })();
        if (!Array.isArray(services) || !services.length || (services.length === 1 && String(services[0]) === 'ALL')) {
            return { all: true, label: 'همه محصولات', items: [] };
        }
        const ids = services.flatMap(item => {
            if (item == null || String(item).toUpperCase() === 'ALL') return [];
            if (typeof item === 'object') {
                const out = [];
                ['product_id', 'id'].forEach(key => { if (Number(item[key] || 0) > 0) out.push(Number(item[key])); });
                String(item.product_ids || '').split(',').forEach(v => { if (Number(v || 0) > 0) out.push(Number(v)); });
                return out;
            }
            return Number(item || 0) > 0 ? [Number(item)] : [];
        });
        const productNames = {};
        ((this._ccSetup && this._ccSetup.products) || []).forEach(p => { productNames[Number(p.id || 0)] = String(p.name || p.title || ''); });
        const labels = Array.from(new Set(ids)).map(id => productNames[id]).filter(Boolean);
        return labels.length
            ? { all: false, label: labels.join('، '), items: labels }
            : { all: false, label: `${services.length} مورد`, items: [] };
    },

    getCommissionRuleTypeLabel(ruleType) {
        return String(ruleType || '').toUpperCase() === 'FIXED_RULE' ? 'ثابت' : 'پلکانی';
    },

    formatCommissionPercentInput(value) {
        const raw = String(value ?? '').trim();
        if (raw === '') return '';
        const numeric = Number(raw);
        if (!Number.isFinite(numeric)) return raw;
        return String(parseFloat(numeric.toFixed(4)));
    },

    getCommissionRuleAmountLabel(row) {
        const type = String(row?.rule_type || '').toUpperCase();
        if (type === 'FIXED_RULE') {
            const amount = Number(row?.base_amount || row?.fixed_budget_amount || 0);
            const rate = Number(row?.reward_percentage || row?.base_rate_percent || 0);
            if (amount > 0 && rate > 0) return `${this.formatMoney(amount)} + ${rate.toFixed(2)}%`;
            if (amount > 0) return this.formatMoney(amount);
            return `${rate.toFixed(2)}%`;
        }
        const tiers = Array.isArray(row?.tiers) ? row.tiers : [];
        if (!tiers.length) return 'پلکان تعریف نشده';
        return tiers.map((tier) => {
            const min = this.formatMoney(tier.min_amount || 0);
            const max = tier.max_amount ? this.formatMoney(tier.max_amount) : 'بدون سقف';
            const pct = Number(tier.allocated_commission_percent ?? tier.commission_percent ?? 0).toFixed(2);
            return `از ${min} تا ${max}: ${pct}%`;
        }).join('\n');
    },

    async batchApproveVisibleCommissions() {
        const ids = Array.isArray(this.state.visibleCommissionIds) ? this.state.visibleCommissionIds : [];
        if (!ids.length) {
            Swal.fire('اطلاع', 'رکوردی برای تایید گروهی وجود ندارد', 'info');
            return;
        }
        const confirm = await Swal.fire({
            title: 'تایید گروهی',
            text: `تعداد ${ids.length} رکورد نمایش داده شده به وضعیت SETTLED منتقل شود؟`,
            icon: 'question',
            showCancelButton: true,
            confirmButtonText: 'بله',
            cancelButtonText: 'لغو'
        });
        if (!confirm.isConfirmed) return;
        for (const id of ids) {
            await this.req('update_deal_commission_status', { commission_id: id, status: 'SETTLED' });
        }
        await this.loadCommissions();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تایید گروهی انجام شد', showConfirmButton: false, timer: 1800 });
    },

    toggleSelectAllCommissionRules(checked, type) {
        const cls = type === 'fixed' ? '.commission-rule-select-fixed' : '.commission-rule-select-tiered';
        Array.from(document.querySelectorAll(cls)).forEach((el) => {
            el.checked = !!checked;
        });
        this.syncCommissionRulesBulkDeleteButton();
    },

    toggleSelectAllCommissionRuleTypes(checked) {
        Array.from(document.querySelectorAll('.commission-rule-select')).forEach((el) => {
            el.checked = !!checked;
        });
        const fixed = document.getElementById('commission-rules-select-all-fixed');
        const tiered = document.getElementById('commission-rules-select-all-tiered');
        if (fixed) fixed.checked = !!checked;
        if (tiered) tiered.checked = !!checked;
        this.syncCommissionRulesBulkDeleteButton();
    },

    syncCommissionRulesBulkDeleteButton() {
        const btn = document.getElementById('commission-rules-bulk-delete-btn');
        if (!btn) return;
        const canDelete = btn.getAttribute('data-can-delete') === '1';
        const selectedCount = document.querySelectorAll('.commission-rule-select:checked').length;
        const shouldDisable = !canDelete || selectedCount === 0;
        btn.disabled = shouldDisable;
        btn.classList.toggle('opacity-60', shouldDisable);
        btn.classList.toggle('cursor-not-allowed', shouldDisable);
        btn.title = selectedCount > 0 ? `حذف ${selectedCount} قانون انتخاب‌شده` : 'ابتدا قانون‌ها را انتخاب کنید';
        const all = Array.from(document.querySelectorAll('.commission-rule-select'));
        const selectAll = document.getElementById('commission-rules-select-all-all');
        const fixedAll = document.getElementById('commission-rules-select-all-fixed');
        const tieredAll = document.getElementById('commission-rules-select-all-tiered');
        if (selectAll) {
            selectAll.checked = all.length > 0 && all.every((el) => el.checked);
        }
        if (fixedAll) {
            const fixedRows = Array.from(document.querySelectorAll('.commission-rule-select-fixed'));
            fixedAll.checked = fixedRows.length > 0 && fixedRows.every((el) => el.checked);
        }
        if (tieredAll) {
            const tieredRows = Array.from(document.querySelectorAll('.commission-rule-select-tiered'));
            tieredAll.checked = tieredRows.length > 0 && tieredRows.every((el) => el.checked);
        }
    },

    async deleteCommissionRule(id) {
        id = Number(id || 0);
        if (!id) return;
        const confirm = await Swal.fire({
            title: 'حذف قانون پورسانت',
            text: 'آیا از حذف این قوانین اطمینان دارید؟ توجه: این کار ممکن است روی محاسبات مرتبط تاثیر بگذارد.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'لغو'
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('delete_commission_rule', { rule_id: id });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'حذف قانون انجام نشد', 'error');
            return;
        }
        await this.openCommissionMatrixModal();
        await this.loadCommissions();
    },

    async bulkDeleteSelectedCommissionRules() {
        const ids = Array.from(document.querySelectorAll('.commission-rule-select:checked'))
            .map((el) => Number(el.value || 0))
            .filter((v) => v > 0);
        if (!ids.length) {
            Swal.fire('اطلاع', 'هیچ قانونی انتخاب نشده است', 'info');
            return;
        }
        const confirm = await Swal.fire({
            title: 'حذف گروهی قوانین',
            text: `آیا از حذف این قوانین اطمینان دارید؟ توجه: این کار ممکن است روی محاسبات مرتبط تاثیر بگذارد. (تعداد: ${ids.length})`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'لغو'
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('bulk_delete_commission_rules', { ids: JSON.stringify(ids) });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'حذف گروهی انجام نشد', 'error');
            return;
        }
        const deletedCount = Array.isArray(res.deleted) ? res.deleted.length : 0;
        const skippedCount = Array.isArray(res.skipped) ? res.skipped.length : 0;
        const icon = deletedCount > 0 && skippedCount === 0 ? 'success' : (deletedCount > 0 ? 'warning' : 'info');
        Swal.fire({ toast: true, position: 'bottom-end', icon, title: `حذف: ${deletedCount} | رد شده: ${skippedCount}`, showConfirmButton: false, timer: 2600 });
        await this.openCommissionMatrixModal();
        await this.loadCommissions();
    },

    async deleteCommissionRecord(id) {
        id = Number(id || 0);
        if (!id) return;
        const confirm = await Swal.fire({
            title: 'حذف رکورد پورسانت',
            text: 'آیا از حذف این رکورد مطمئن هستید؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف شود',
            cancelButtonText: 'انصراف'
        });
        if (!confirm.isConfirmed) return;
        const res = await this.req('delete_deal_commission', { commission_id: id });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'حذف رکورد انجام نشد', 'error');
            return;
        }
        await this.loadCommissions();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'رکورد حذف شد', showConfirmButton: false, timer: 1500 });
    },

    async openCommissionMatrixModal() {
        const res = await this.req('get_commission_setup_data', {});
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در دریافت داده‌ها', 'error');
            return;
        }
        const users = Array.isArray(res.users) ? res.users : [];
        const rulesRes = await this.req('get_commission_rules', {});
        const rules = rulesRes.status === 'success' && Array.isArray(rulesRes.rules) ? rulesRes.rules : [];
        const assignments = rulesRes.status === 'success' && Array.isArray(rulesRes.assignments) ? rulesRes.assignments : [];
        const servicesRes = await this.req('get_allowed_services', {});
        const services = servicesRes.status === 'success' && Array.isArray(servicesRes.services) ? servicesRes.services : [];
        this.state.commissionRules = rules;

        const userSelect = document.getElementById('commission-salesperson-user-id');
        const assignmentRuleSelect = document.getElementById('commission-assignment-rule-ids');
        const productSelect = document.getElementById('commission-product-id');
        const rulesList = document.getElementById('commission-rules-list');
        const assignmentsList = document.getElementById('commission-assignments-list');
        const assignmentsCount = document.getElementById('commission-assignments-count');
        if (!userSelect || !rulesList) return;

        userSelect.innerHTML = '<option value="">-- انتخاب کارشناس --</option>' + users
            .filter(u => Number(u.is_active || 0) === 1 && String(u.role || '').trim() === 'agent')
            .map(u => {
                const fallbackName = `${u.first_name || ''} ${u.last_name || ''}`.trim();
                const label = String(u.display_name || fallbackName || `کاربر #${u.id}`);
                return `<option value="${u.id}">${this.escapeHtml(label)}</option>`;
            }).join('');
        if (assignmentRuleSelect) {
            assignmentRuleSelect.innerHTML = rules.map((r) => {
                const type = this.getCommissionRuleTypeLabel(r.rule_type);
                const label = String(r.title || `${type} - ${this.getCommissionRuleAmountLabel(r)}`);
                return `<option value="${Number(r.id || 0)}">${this.escapeHtml(label)}</option>`;
            }).join('');
        }

        if (productSelect) {
            const dealTitleSelect = document.getElementById('deal-title');
            let options = [];
            if (dealTitleSelect && dealTitleSelect.options && dealTitleSelect.options.length > 0) {
                options = Array.from(dealTitleSelect.options)
                    .filter((o) => String(o.value || '').trim() !== '')
                    .map((o, idx) => ({
                        id: String(o.value || `service_${idx + 1}`),
                        title: String(o.textContent || o.innerText || o.value || `خدمت ${idx + 1}`),
                        lookupId: String(o.getAttribute('data-service-lookup-item-id') || ''),
                        productIds: String(o.getAttribute('data-product-ids') || o.getAttribute('data-product-id') || '')
                    }));
            } else {
                options = services.map((srv, idx) => {
                    if (typeof srv === 'string') return { id: `service_${idx + 1}`, title: srv, lookupId: '', productIds: '' };
                    return {
                        id: String(srv.value || srv.id || srv.title || `service_${idx + 1}`),
                        title: String(srv.title || srv.name || srv.label || srv.value || `خدمت ${idx + 1}`),
                        lookupId: String(srv.service_lookup_item_id || ''),
                        productIds: String(srv.product_ids || srv.product_id || srv.id || '')
                    };
                });
            }
            productSelect.innerHTML = options.map((o) => `
                <option value="${this.escapeHtml(String(o.id))}" data-service-lookup-item-id="${this.escapeHtml(String(o.lookupId))}" data-product-ids="${this.escapeHtml(String(o.productIds))}">
                    ${this.escapeHtml(String(o.title))}
                </option>
            `).join('');
            this.renderCommissionProductPicker();
        }

        if (assignmentsCount) {
            const activeCount = assignments.filter((a) => Number(a.is_active || 0) === 1).length;
            assignmentsCount.textContent = `${activeCount.toLocaleString('fa-IR')} فعال از ${assignments.length.toLocaleString('fa-IR')}`;
        }
        if (assignmentsList) {
            assignmentsList.innerHTML = this.renderCommissionAssignmentCards(assignments);
        }
        rulesList.innerHTML = this.renderCommissionRuleAdminTables(rules);
        this.syncCommissionRulesBulkDeleteButton();

        this.initializeCommissionMatrixSelects();
        this.onCommissionRuleTypeChange();
        this.scrollToCommissionMatrix();
        this.goCommissionWizardStep(1);
    },

    renderCommissionAssignmentCards(assignments = []) {
        if (!assignments.length) {
            return `
                <div class="commission-assignment-empty">
                    <i class="fa-solid fa-link-slash"></i>
                    <span>هنوز تخصیصی ثبت نشده است.</span>
                </div>`;
        }
        return assignments.map((a) => {
            const isActive = Number(a.is_active || 0) === 1;
            const ruleTitle = String(a.rule_title || `Rule #${a.rule_id || '-'}`);
            const salesperson = String(a.salesperson_name || 'همه کارشناسان');
            const productScope = String(a.product_scope_label || 'همه محصولات');
            const assignedAt = this.ccDateLabel(a.assigned_at || '');
            return `
                <article class="commission-assignment-card ${isActive ? 'is-active' : 'is-inactive'}">
                    <div class="commission-assignment-card-main">
                        <div class="flex items-start justify-between gap-3">
                            <span class="commission-role-badge ${isActive ? 'is-agent' : 'is-muted'}">کارشناس</span>
                            <div class="text-right flex-1 min-w-0">
                                <h5>${this.escapeHtml(ruleTitle)}</h5>
                                <p><i class="fa-solid fa-user"></i> ${this.escapeHtml(salesperson)} · ${this.escapeHtml(productScope)} · ${this.escapeHtml(assignedAt)}</p>
                            </div>
                        </div>
                        <div class="commission-assignment-card-footer">
                            <button type="button" class="commission-switch ${isActive ? 'is-on' : 'is-off'}" aria-label="تغییر وضعیت تخصیص" onclick="app.toggleCommissionAssignment(${Number(a.id || 0)}, ${isActive ? 0 : 1})">
                                <span></span>
                            </button>
                            <span class="commission-status-pill ${isActive ? 'is-active' : 'is-inactive'}">${isActive ? 'فعال' : 'غیرفعال'}</span>
                        </div>
                    </div>
                </article>`;
        }).join('');
    },

    renderCommissionRuleAdminTables(rules = []) {
        if (!rules.length) {
            return '<div class="text-xs text-gray-400">قانونی ثبت نشده است.</div>';
        }
        const fixedRules = rules.filter((r) => String(r.rule_type || '').toUpperCase() === 'FIXED_RULE');
        const tieredRules = rules.filter((r) => String(r.rule_type || '').toUpperCase() !== 'FIXED_RULE');
        const canBulkDelete = this.canUseGlobalBulkDelete();
        const canDeleteSingle = canBulkDelete;
        const productScopeLabel = (r) => this.getCommissionProductScopeLabel(r);
        const tierLabel = (r) => this.getCommissionRuleAmountLabel(r).replace(/\n/g, '<br>');
        const actionButtons = (id) => `
            <div class="flex items-center gap-2">
                <button type="button" class="text-blue-300 hover:text-blue-200" title="ویرایش قانون" onclick="app.editCommissionRuleInline(${Number(id || 0)})">
                    <i class="fa-solid fa-pen-to-square"></i>
                </button>
                <button type="button" class="text-red-400 hover:text-red-300 ${canDeleteSingle ? '' : 'opacity-50 cursor-not-allowed'}" ${canDeleteSingle ? '' : 'disabled title="دسترسی حذف ندارید"'} onclick="app.deleteCommissionRule(${Number(id || 0)})">
                    <i class="fa-solid fa-trash"></i>
                </button>
            </div>`;
        return `
            <div class="mb-2 flex items-center gap-2">
                <button type="button" id="commission-rules-bulk-delete-btn" data-can-delete="${canBulkDelete ? '1' : '0'}" class="btn-primary px-3 py-1.5 rounded text-xs bg-red-700 hover:bg-red-600 opacity-60 cursor-not-allowed" disabled onclick="app.bulkDeleteSelectedCommissionRules()">
                    <i class="fa-solid fa-trash ml-1"></i> حذف گروهی
                </button>
                <label class="text-xs text-gray-400 flex items-center gap-1">
                    <input type="checkbox" id="commission-rules-select-all-all" onchange="app.toggleSelectAllCommissionRuleTypes(this.checked)">
                    انتخاب همه قوانین
                </label>
            </div>
            <div class="text-xs text-gray-300 mb-2">قوانین ثابت</div>
            <table class="w-full text-xs text-gray-300 mb-4">
                <thead><tr class="border-b border-gray-700 text-gray-400">
                    <th class="text-right p-2"><input type="checkbox" id="commission-rules-select-all-fixed" onchange="app.toggleSelectAllCommissionRules(this.checked, 'fixed')"></th>
                    <th class="text-right p-2">نام قانون</th><th class="text-right p-2">نوع</th>
                    <th class="text-right p-2">مبلغ ثابت</th><th class="text-right p-2">درصد</th><th class="text-right p-2">دامنه محصول</th><th class="text-right p-2">عملیات</th>
                </tr></thead>
                <tbody>${fixedRules.length ? fixedRules.map(r => `
                    <tr class="border-b border-gray-800">
                        <td class="p-2"><input type="checkbox" class="commission-rule-select commission-rule-select-fixed" value="${Number(r.id || 0)}" onchange="app.syncCommissionRulesBulkDeleteButton()"></td>
                        <td class="p-2">${this.escapeHtml(String(r.title || `#${r.id || '-'}`))}</td><td class="p-2">${this.escapeHtml(this.getCommissionRuleTypeLabel(r.rule_type))}</td>
                        <td class="p-2">${this.escapeHtml(this.getCommissionRuleAmountLabel(r))}</td>
                        <td class="p-2">${Number(r.reward_percentage || r.base_rate_percent || 0).toFixed(2)}%</td><td class="p-2">${this.escapeHtml(productScopeLabel(r))}</td><td class="p-2">${actionButtons(r.id)}</td>
                    </tr>`).join('') : `<tr><td colspan="7" class="p-2 text-gray-500">رکوردی وجود ندارد</td></tr>`}</tbody>
            </table>
            <div class="text-xs text-gray-300 mb-2">قوانین پلکانی بر اساس مبلغ واریزی</div>
            <table class="w-full text-xs text-gray-300">
                <thead><tr class="border-b border-gray-700 text-gray-400">
                    <th class="text-right p-2"><input type="checkbox" id="commission-rules-select-all-tiered" onchange="app.toggleSelectAllCommissionRules(this.checked, 'tiered')"></th>
                    <th class="text-right p-2">نام قانون</th><th class="text-right p-2">نوع</th>
                    <th class="text-right p-2">ساختار پلکان</th><th class="text-right p-2">دامنه محصول</th><th class="text-right p-2">عملیات</th>
                </tr></thead>
                <tbody>${tieredRules.length ? tieredRules.map(r => `
                    <tr class="border-b border-gray-800">
                        <td class="p-2"><input type="checkbox" class="commission-rule-select commission-rule-select-tiered" value="${Number(r.id || 0)}" onchange="app.syncCommissionRulesBulkDeleteButton()"></td>
                        <td class="p-2">${this.escapeHtml(String(r.title || `#${r.id || '-'}`))}</td><td class="p-2">${this.escapeHtml(this.getCommissionRuleTypeLabel(r.rule_type))}</td>
                        <td class="p-2 leading-6">${tierLabel(r)}</td>
                        <td class="p-2">${this.escapeHtml(productScopeLabel(r))}</td><td class="p-2">${actionButtons(r.id)}</td>
                    </tr>`).join('') : `<tr><td colspan="7" class="p-2 text-gray-500">رکوردی وجود ندارد</td></tr>`}</tbody>
            </table>`;
    },

    scrollToCommissionMatrix(options = {}) {
        const section = document.getElementById('commission-matrix-section');
        if (!section) return;
        requestAnimationFrame(() => {
            section.scrollIntoView({ behavior: 'smooth', block: 'start' });
            if (options.highlight) {
                section.classList.remove('commission-edit-highlight');
                void section.offsetWidth;
                section.classList.add('commission-edit-highlight');
                setTimeout(() => section.classList.remove('commission-edit-highlight'), 1400);
            }
        });
    },

    editCommissionRuleInline(ruleId) {
        const rule = (this.state.commissionRules || []).find((item) => Number(item.id || 0) === Number(ruleId || 0));
        if (!rule) return;
        this.scrollToCommissionMatrix({ highlight: true });
        this.goCommissionWizardStep(1);
        const idInput = document.getElementById('commission-rule-id');
        const typeInput = document.getElementById('commission-rule-type');
        const fixedAmountInput = document.getElementById('commission-fixed-budget-amount');
        const rateInput = document.getElementById('commission-base-rate-percent');
        if (idInput) idInput.value = String(rule.id || '');
        if (typeInput) typeInput.value = String(rule.rule_type || 'TIERED_RULE').toUpperCase() === 'FIXED_RULE' ? 'FIXED_RULE' : 'TIERED_RULE';
        this.onCommissionRuleTypeChange();

        if (String(typeInput?.value || '').toUpperCase() === 'FIXED_RULE') {
            if (fixedAmountInput) {
                const amount = Number(rule.base_amount || rule.fixed_budget_amount || 0);
                fixedAmountInput.value = amount > 0 ? amount.toLocaleString('en-US') : '';
            }
            if (rateInput) {
                const rate = Number(rule.reward_percentage || rule.base_rate_percent || 0);
                rateInput.value = rate > 0 ? String(rate) : '';
            }
        } else {
            this.resetCommissionConstraintRows();
            const tiers = Array.isArray(rule.tiers) ? rule.tiers : [];
            const hasAmountValue = (value) => value !== null && value !== undefined && String(value).trim() !== '';
            const formatTierAmountInput = (value) => {
                if (!hasAmountValue(value)) return '';
                const numeric = Number(String(value).replace(/,/g, ''));
                return Number.isFinite(numeric) ? numeric.toLocaleString('en-US') : String(value);
            };
            tiers.forEach((tier, tierIndex) => {
                this.addCommissionConstraintRow();
                const row = Array.from(document.querySelectorAll('#commission-constraints-rows > div')).pop();
                if (!row) return;
                const minInput = row.querySelector('.commission-tier-min-amount');
                const maxInput = row.querySelector('.commission-tier-max-amount');
                const pctInput = row.querySelector('.commission-tier-commission');
                const minValue = hasAmountValue(tier.min_amount) ? tier.min_amount : (tierIndex === 0 ? '0' : '');
                if (minInput) minInput.value = formatTierAmountInput(minValue);
                if (maxInput) maxInput.value = formatTierAmountInput(tier.max_amount);
                if (pctInput) pctInput.value = this.formatCommissionPercentInput(tier.allocated_commission_percent ?? tier.commission_percent ?? '');
            });
            if (!tiers.length) this.addCommissionConstraintRow();
        }
        this.updateCommissionRulePreview();
    },

    resetCommissionRuleForm() {
        const form = document.getElementById('commission-matrix-form');
        if (form) form.reset();
        const idInput = document.getElementById('commission-rule-id');
        if (idInput) idInput.value = '';
        this.resetCommissionConstraintRows();
        this.onCommissionRuleTypeChange();
        this.goCommissionWizardStep(1);
        this.scrollToCommissionMatrix();
    },

    goCommissionWizardStep(step) {
        const target = Math.max(1, Math.min(3, Number(step || 1)));
        Array.from(document.querySelectorAll('[data-commission-step]')).forEach((panel) => {
            panel.classList.toggle('hidden', Number(panel.getAttribute('data-commission-step')) !== target);
        });
        Array.from(document.querySelectorAll('[data-commission-step-tab]')).forEach((tab) => {
            const tabStep = Number(tab.getAttribute('data-commission-step-tab') || 0);
            tab.classList.toggle('is-active', tabStep === target);
            tab.classList.toggle('is-complete', tabStep < target);
        });
        this.state.commissionWizardStep = target;
        this.updateCommissionRulePreview();
        this.initializeCommissionMatrixSelects();
    },

    nextCommissionWizardStep() {
        const current = Number(this.state.commissionWizardStep || 1);
        this.goCommissionWizardStep(current + 1);
    },

    prevCommissionWizardStep() {
        const current = Number(this.state.commissionWizardStep || 1);
        this.goCommissionWizardStep(current - 1);
    },

    updateCommissionRulePreview() {
        const preview = document.getElementById('commission-rule-name-preview');
        if (!preview) return;
        const ruleType = (document.getElementById('commission-rule-type')?.value || 'TIERED_RULE').trim();
        if (ruleType === 'FIXED_RULE') {
            const fixedAmount = ((document.getElementById('commission-fixed-budget-amount')?.value || '').trim() || '0').replace(/,/g, '');
            const fixedRate = (document.getElementById('commission-base-rate-percent')?.value || '').trim();
            const amountLabel = Number(fixedAmount || 0) > 0 ? `${this.formatMoney(fixedAmount)} مبلغ ثابت` : '';
            const rateLabel = Number(fixedRate || 0) > 0 ? `${Number(fixedRate).toFixed(2)}٪ فروش` : '';
            preview.textContent = `ثابت - ${amountLabel || rateLabel || 'در انتظار مبلغ یا درصد'}`;
            return;
        }
        const tiers = this.collectCommissionConstraints();
        if (!tiers.length) {
            preview.textContent = 'پلکانی - در انتظار تعریف پلکان';
            return;
        }
        const first = tiers[0];
        const last = tiers[tiers.length - 1];
        const firstRate = Number(first.commission_percent || 0).toFixed(2);
        const lastRate = Number(last.commission_percent || 0).toFixed(2);
        preview.textContent = `پلکانی - ${this.formatMoney(first.min_amount || 0)} تا ${last.max_amount ? this.formatMoney(last.max_amount) : 'بدون سقف'} - ${firstRate}٪ تا ${lastRate}٪`;
    },

    onCommissionRuleTypeChange() {
        const ruleType = (document.getElementById('commission-rule-type')?.value || 'TIERED_RULE').trim();
        const fixedFields = document.getElementById('commission-fixed-fields');
        const baseRateWrap = document.getElementById('commission-base-rate-wrap');
        const baseRateInput = document.getElementById('commission-base-rate-percent');
        const tiersSection = document.getElementById('commission-tiers-section');
        const isTiered = ruleType === 'TIERED_RULE';
        const tiersTitle = document.getElementById('commission-tiers-title');
        if (fixedFields) fixedFields.style.display = isTiered ? 'none' : '';
        if (tiersSection) tiersSection.style.display = isTiered ? '' : 'none';
        if (baseRateWrap) baseRateWrap.style.display = isTiered ? 'none' : '';
        if (tiersTitle) tiersTitle.textContent = 'تعریف پلکان‌های مبلغی';
        if (baseRateInput) {
            if (isTiered) { baseRateInput.removeAttribute('required'); baseRateInput.value = ''; }
            else { baseRateInput.setAttribute('required', 'required'); }
        }
        const fixedAmountInput = document.getElementById('commission-fixed-budget-amount');
        if (fixedAmountInput) {
            fixedAmountInput.value = isTiered ? '' : fixedAmountInput.value;
        }
        this.resetCommissionConstraintRows();
        if (isTiered) this.addCommissionConstraintRow();
        const helper = document.getElementById('commission-finance-helper');
        if (helper) {
            helper.textContent = isTiered
                ? 'در قانون پلکانی، ابتدا مجموع پرداخت‌های مشمول محاسبه می‌شود؛ سپس پلکان‌ها به‌صورت مرحله‌ای اعمال می‌شوند.'
                : 'در قانون ثابت، می‌توانید یک مبلغ ثابت یا یک درصد ساده از مجموع پرداخت‌ها تعریف کنید.';
        }
        this.updateCommissionRulePreview();
    },

    resetCommissionConstraintRows() {
        const wrap = document.getElementById('commission-constraints-rows');
        if (!wrap) return;
        wrap.innerHTML = '';
    },

    addCommissionConstraintRow() {
        const wrap = document.getElementById('commission-constraints-rows');
        if (!wrap) return;
        const row = document.createElement('div');
        row.className = 'commission-tier-row grid grid-cols-1 md:grid-cols-12 gap-2 items-end';
        row.innerHTML = `
            <div class="md:col-span-4"><label class="text-[11px] text-gray-400 mb-1 block">از مبلغ</label><input type="text" inputmode="numeric" class="input commission-tier-min-amount" placeholder="0"></div>
            <div class="md:col-span-4"><label class="text-[11px] text-gray-400 mb-1 block">تا مبلغ</label><input type="text" inputmode="numeric" class="input commission-tier-max-amount" placeholder="بدون سقف = خالی"></div>
            <div class="md:col-span-3"><label class="text-[11px] text-gray-400 mb-1 block">درصد پورسانت</label><input type="text" inputmode="decimal" class="input commission-tier-commission" placeholder="2"></div>
            <div class="md:col-span-1 flex justify-center"><button type="button" class="text-gray-400 hover:text-red-400 p-2 rounded transition" title="حذف پلکان" onclick="this.closest('div.grid').remove(); app.updateCommissionRulePreview();"><i class="fa-solid fa-trash"></i></button></div>`;
        wrap.appendChild(row);
        this.bindCommissionTierAmountFormatters(row);
        Array.from(row.querySelectorAll('.commission-tier-commission')).forEach((input) => {
            const normalize = () => {
                input.value = this.formatCommissionPercentInput(input.value);
                this.updateCommissionRulePreview();
            };
            input.addEventListener('input', () => this.updateCommissionRulePreview());
            input.addEventListener('blur', normalize);
            input.addEventListener('change', normalize);
        });
        this.initializeCommissionMatrixSelects();
        this.updateCommissionRulePreview();
    },

    bindCommissionTierAmountFormatters(scope = document) {
        Array.from(scope.querySelectorAll('.commission-tier-min-amount, .commission-tier-max-amount')).forEach((input) => {
            if (input.dataset.boundFormatter === '1') return;
            input.dataset.boundFormatter = '1';
            input.addEventListener('input', (e) => {
                const target = e.target;
                const digits = String(target.value || '').replace(/[^\d]/g, '');
                target.value = digits ? Number(digits).toLocaleString('en-US') : '';
                this.updateCommissionRulePreview();
            });
        });
    },

    initializeCommissionMatrixSelects() {
        const ids = ['commission-rule-type', 'commission-salesperson-user-id', 'commission-product-id', 'commission-assignment-rule-ids'];
        ids.forEach((id) => {
            const el = document.getElementById(id);
            if (!el) return;
            el.style.position = 'relative';
            el.style.zIndex = '90';
            el.classList.add('relative');
        });
        ['commission-base-rate-percent', 'commission-fixed-budget-amount'].forEach((id) => {
            const el = document.getElementById(id);
            if (!el || el.dataset.commissionPreviewBound === '1') return;
            el.dataset.commissionPreviewBound = '1';
            el.addEventListener('input', () => this.updateCommissionRulePreview());
        });
        this.bindCommissionBudgetFormatter();
        this.renderCommissionProductPicker();
    },

    toggleCommissionServicesSelectAll() {
        const select = document.getElementById('commission-product-id');
        if (!select) return;
        const options = Array.from(select.options || []);
        if (!options.length) return;
        const allSelected = options.every((o) => o.selected);
        options.forEach((o) => { o.selected = !allSelected; });
        this.renderCommissionProductPicker();
    },

    renderCommissionProductPicker() {
        const picker = document.getElementById('commission-product-picker');
        const select = document.getElementById('commission-product-id');
        if (!picker || !select) return;
        const options = Array.from(select.options || []);
        const selected = options.filter((opt) => opt.selected);
        const allSelected = options.length > 0 && selected.length === options.length;
        const chips = !selected.length ? '<span class="text-xs text-gray-500">انتخاب نشده</span>' : selected.map((opt) => `
            <span class="inline-flex items-center gap-1 bg-blue-900/60 border border-blue-700 text-blue-100 px-2 py-1 rounded text-xs"><span class="truncate max-w-[130px]">${this.escapeHtml(opt.textContent || '')}</span><button type="button" class="text-blue-200 hover:text-white" onclick="app.toggleCommissionServiceValue('${this.escapeHtml(String(opt.value))}', false, event)">×</button></span>`).join('');
        picker.innerHTML = `
            <div class="kpi-tag-picker-root relative" onpointerdown="event.stopPropagation()" onclick="event.stopPropagation()">
                <button type="button" onclick="app.toggleCommissionProductPanel(event)" class="w-full min-h-[44px] bg-gray-800 border border-gray-700 rounded p-2 text-right"><div class="flex items-center justify-between gap-2"><div class="flex flex-wrap gap-1 flex-1">${chips}</div><span class="text-[11px] text-gray-400 whitespace-nowrap">${allSelected ? 'همه' : `${selected.length} مورد`}</span><span class="text-gray-400 text-xs">▼</span></div></button>
                <div id="commission-product-panel" class="hidden absolute z-[120] mt-1 w-full bg-gray-900 border border-gray-700 rounded shadow-2xl">
                    <div class="p-2 border-b border-gray-700"><div class="text-xs text-gray-400 mb-2">انتخاب دسته/محصول</div><div class="flex items-center gap-2 mb-2"><button type="button" class="text-[11px] px-2 py-1 rounded bg-emerald-700/70 hover:bg-emerald-600 text-white" onclick="app.selectAllCommissionServices(event)">انتخاب همه</button><button type="button" class="text-[11px] px-2 py-1 rounded bg-gray-700 hover:bg-gray-600 text-white" onclick="app.clearCommissionServices(event)">پاک‌کردن همه</button></div><input id="commission-product-search" type="text" oninput="app.filterCommissionServices(this.value)" class="w-full bg-gray-800 border border-gray-700 rounded p-2 text-xs text-white" placeholder="جستجو..."></div>
                    <div id="commission-product-list" class="max-h-56 overflow-auto p-1">${options.map((opt) => `<label class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-700 cursor-pointer text-sm commission-product-item" data-title="${this.escapeHtml(String(opt.textContent || '').toLowerCase())}"><input type="checkbox" ${opt.selected ? 'checked' : ''} onchange="app.toggleCommissionServiceValue('${this.escapeHtml(String(opt.value))}', this.checked, event)"><span class="text-gray-200">${this.escapeHtml(opt.textContent || '')}</span></label>`).join('')}</div>
                </div>
            </div>`;
    },

    toggleCommissionProductPanel(event) {
        if (event) { event.preventDefault(); event.stopPropagation(); }
        const panel = document.getElementById('commission-product-panel');
        if (!panel) return;
        const willOpen = panel.classList.contains('hidden');
        panel.classList.toggle('hidden');
        if (willOpen) {
            const trigger = event?.currentTarget || panel.previousElementSibling;
            const rect = trigger?.getBoundingClientRect ? trigger.getBoundingClientRect() : null;
            if (rect) {
                const availableHeight = Math.max(220, window.innerHeight - rect.bottom - 18);
                panel.style.position = 'fixed';
                panel.style.top = `${rect.bottom + 6}px`;
                panel.style.left = `${rect.left}px`;
                panel.style.width = `${rect.width}px`;
                panel.style.maxHeight = `${Math.min(420, availableHeight)}px`;
            }
        }
    },

    filterCommissionServices(term) {
        const q = String(term || '').trim().toLowerCase();
        Array.from(document.querySelectorAll('#commission-product-list .commission-product-item')).forEach((el) => {
            const t = String(el.getAttribute('data-title') || '');
            el.classList.toggle('hidden', q !== '' && !t.includes(q));
        });
    },

    toggleCommissionServiceValue(value, checked, event = null) {
        if (event) event.stopPropagation();
        const select = document.getElementById('commission-product-id');
        if (!select) return;
        const option = Array.from(select.options || []).find((o) => String(o.value) === String(value));
        if (!option) return;
        option.selected = !!checked;
        this.renderCommissionProductPicker();
    },

    selectAllCommissionServices(event = null) {
        if (event) { event.preventDefault(); event.stopPropagation(); }
        const select = document.getElementById('commission-product-id');
        if (!select) return;
        Array.from(select.options || []).forEach((o) => { o.selected = true; });
        this.renderCommissionProductPicker();
    },

    clearCommissionServices(event = null) {
        if (event) { event.preventDefault(); event.stopPropagation(); }
        const select = document.getElementById('commission-product-id');
        if (!select) return;
        Array.from(select.options || []).forEach((o) => { o.selected = false; });
        this.renderCommissionProductPicker();
    },

    bindCommissionBudgetFormatter() {
        const input = document.getElementById('commission-fixed-budget-amount');
        if (!input || input.dataset.boundFormatter === '1') return;
        input.dataset.boundFormatter = '1';
        input.addEventListener('input', (e) => {
            const target = e.target;
            const digits = String(target.value || '').replace(/[^\d]/g, '');
            target.value = digits ? Number(digits).toLocaleString('en-US') : '';
            this.updateCommissionRulePreview();
        });
    },

    collectCommissionConstraints() {
        const rows = Array.from(document.querySelectorAll('#commission-constraints-rows > div'));
        return rows.map((row) => ({
            min_amount: (row.querySelector('.commission-tier-min-amount')?.value || '').replace(/,/g, '').trim(),
            max_amount: (row.querySelector('.commission-tier-max-amount')?.value || '').replace(/,/g, '').trim(),
            commission_percent: (row.querySelector('.commission-tier-commission')?.value || '').trim()
        })).filter((item) => item.min_amount !== '' && item.commission_percent !== '');
    },

    async saveCommissionRule(event) {
        event.preventDefault();
        const submitBtn = event?.target?.querySelector('button[type="submit"]');
        if (submitBtn?.disabled) return;
        if (submitBtn) {
            submitBtn.disabled = true;
            submitBtn.dataset.originalText = submitBtn.innerHTML;
            submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin ml-1"></i>در حال ذخیره...';
        }
        try {
            const selectedSalespersonId = parseInt((document.getElementById('commission-salesperson-user-id')?.value || '').trim(), 10);
            const ruleType = (document.getElementById('commission-rule-type')?.value || 'TIERED_RULE').trim();
            const tierRows = this.collectCommissionConstraints();
            if (ruleType === 'TIERED_RULE' && !tierRows.length) {
                Swal.fire('خطا', 'حداقل یک پلکان پورسانت تعریف کنید', 'error');
                return;
            }
            const fixedAmount = ((document.getElementById('commission-fixed-budget-amount')?.value || '').trim() || '0').replace(/,/g, '');
            const fixedRate = (document.getElementById('commission-base-rate-percent')?.value || '').trim();
            if (ruleType === 'FIXED_RULE' && Number(fixedAmount || 0) <= 0 && Number(fixedRate || 0) <= 0) {
                Swal.fire('خطا', 'برای قانون ثابت، مبلغ ثابت یا درصد پورسانت را وارد کنید', 'error');
                return;
            }
            const payload = {
                id: (document.getElementById('commission-rule-id')?.value || '').trim(),
                rule_type: ruleType,
                salesperson_user_id: '',
                target_users: JSON.stringify([]),
                requested_services: JSON.stringify([]),
                base_amount: fixedAmount,
                reward_percentage: ruleType === 'TIERED_RULE' ? String(tierRows[0]?.commission_percent || '0') : fixedRate,
                tiers: JSON.stringify(ruleType === 'TIERED_RULE' ? tierRows : [])
            };
            const res = await this.req('store_commission_rule', payload);
            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطا در ذخیره قانون', 'error');
                return;
            }
            Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون ذخیره شد', showConfirmButton: false, timer: 2000 });
            event.target.reset();
            this.resetCommissionConstraintRows();
            await this.openCommissionMatrixModal();
            this.goCommissionWizardStep(3);
            await this.loadCommissions();
        } finally {
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.innerHTML = submitBtn.dataset.originalText || 'ذخیره قانون';
            }
        }
    },

    selectAllCommissionAssignmentRules() {
        Array.from(document.getElementById('commission-assignment-rule-ids')?.options || []).forEach((o) => { o.selected = true; });
    },

    clearCommissionAssignmentRules() {
        Array.from(document.getElementById('commission-assignment-rule-ids')?.options || []).forEach((o) => { o.selected = false; });
    },

    selectAllCommissionAssignmentAgents() {
        Array.from(document.getElementById('commission-salesperson-user-id')?.options || []).forEach((o) => { if (String(o.value || '').trim() !== '') o.selected = true; });
    },

    clearCommissionAssignmentAgents() {
        Array.from(document.getElementById('commission-salesperson-user-id')?.options || []).forEach((o) => { o.selected = false; });
    },

    async assignCommissionRules(event) {
        event.preventDefault();
        const ruleIds = Array.from(document.getElementById('commission-assignment-rule-ids')?.selectedOptions || []).map((o) => Number(o.value || 0)).filter((v) => v > 0);
        const agentIds = Array.from(document.getElementById('commission-salesperson-user-id')?.selectedOptions || []).map((o) => Number(o.value || 0)).filter((v) => v > 0);
        const productIds = Array.from(document.getElementById('commission-product-id')?.selectedOptions || [])
            .flatMap((o) => String(o.getAttribute('data-product-ids') || o.value || '').split(','))
            .map((v) => Number(String(v || '').trim()))
            .filter((v) => v > 0);
        if (!ruleIds.length || !agentIds.length) {
            Swal.fire('خطا', 'حداقل یک قانون و یک کارشناس فروش انتخاب کنید', 'error');
            return;
        }
        const res = await this.req('assign_commission_rules', {
            rule_ids: JSON.stringify(ruleIds),
            agent_ids: JSON.stringify(agentIds),
            product_ids: JSON.stringify(Array.from(new Set(productIds)))
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ثبت تخصیص', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تخصیص ثبت شد', showConfirmButton: false, timer: 2000 });
        await this.openCommissionMatrixModal();
        await this.loadCommissions();
    },

    async toggleCommissionAssignment(assignmentId, isActive) {
        const res = await this.req('toggle_commission_assignment', { assignment_id: assignmentId, is_active: isActive ? 1 : 0 });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در تغییر وضعیت تخصیص', 'error');
            return;
        }
        await this.openCommissionMatrixModal();
        await this.loadCommissions();
    },

    async advanceCommissionStatus(id, status) {
        if (!id || !status) return;
        const res = await this.req('update_deal_commission_status', {
            commission_id: id,
            status
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در تغییر وضعیت', 'error');
            return;
        }
        await this.loadCommissions();
    },

    async openDealFromCommission(dealId) {
        if (!dealId) return;
        this.state.currentDealId = dealId;
        const moved = await this.navigateToView('deals', { source: 'commission_link' });
        if (!moved) return;
        try {
            await this.openDealModal(dealId);
        } catch (e) {
            console.warn('openDealFromCommission failed', e);
        }
    },

    async loadCommissionPayoutQueue() {
        const wrap = document.getElementById('commission-payout-queue');
        if (!wrap) return;
        wrap.innerHTML = '<div class="text-gray-400 text-xs">در حال بارگذاری صف پرداخت...</div>';
        const res = await this.req('get_commission_payout_queue', {
            billing_month: (document.getElementById('commission-cycle-billing-month')?.value || '').trim(),
            status: (document.getElementById('commission-cycle-status')?.value || '').trim(),
            salesperson_name: (document.getElementById('commission-cycle-salesperson')?.value || '').trim(),
            amount_min: (document.getElementById('commission-cycle-amount-min')?.value || '').trim(),
            amount_max: (document.getElementById('commission-cycle-amount-max')?.value || '').trim()
        });
        if (res.status !== 'success') {
            wrap.innerHTML = `<div class="text-red-400 text-xs">${this.escapeHtml(res.message || 'خطا در دریافت صف پرداخت')}</div>`;
            return;
        }
        const rows = Array.isArray(res.cycles) ? res.cycles : [];
        if (!rows.length) {
            wrap.innerHTML = '<div class="text-gray-500 text-xs">چرخه pending برای این ماه وجود ندارد</div>';
            return;
        }
        wrap.innerHTML = `
            <table class="w-full text-xs text-gray-300">
                <thead>
                    <tr class="border-b border-gray-700 text-gray-400">
                        <th class="text-right p-2">کارشناس</th>
                        <th class="text-right p-2">ماه</th>
                        <th class="text-right p-2">Calculated</th>
                        <th class="text-right p-2">Deadline</th>
                        <th class="text-right p-2">Status</th>
                        <th class="text-right p-2">Action</th>
                    </tr>
                </thead>
                <tbody>
                    ${rows.map(row => `
                        <tr class="border-b border-gray-800">
                            <td class="p-2">${this.escapeHtml(String(row.salesperson_name || '-'))}</td>
                            <td class="p-2">${this.escapeHtml(this.ccMonthLabel(row.billing_month || ''))}</td>
                            <td class="p-2">${this.formatMoney(row.calculated_amount || 0)}</td>
                            <td class="p-2">${this.escapeHtml(this.ccDateLabel(row.approval_deadline || ''))}</td>
                            <td class="p-2">${this.escapeHtml(String(row.status || '-'))}</td>
                            <td class="p-2">
                                <button class="btn-primary px-2 py-1 rounded text-[11px]" onclick='app.openApprovePayoutModal(${JSON.stringify(row).replace(/"/g, '&quot;')})'>بررسی</button>
                            </td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
        `;
    },

    async aggregateCommissionPayoutCycles() {
        const month = (document.getElementById('commission-cycle-billing-month')?.value || this.ccBillingMonth()).trim();
        const res = await this.req('aggregate_commission_payout_cycles', { billing_month: month, payroll_day: 30 });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در تجمیع چرخه‌ها', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'چرخه‌های پرداخت آماده شد', showConfirmButton: false, timer: 1800 });
        await this.loadCommissionPayoutQueue();
    },

    openApprovePayoutModal(cycle) {
        document.getElementById('payout-cycle-id').value = String(cycle.id || '');
        document.getElementById('payout-override-amount').value = '';
        document.getElementById('payout-override-reason').value = '';
        const summary = document.getElementById('payout-cycle-summary');
        if (summary) {
            summary.innerHTML = `
                <div class="glass p-2"><div class="text-gray-400">کارشناس</div><div class="text-white font-bold">${this.escapeHtml(String(cycle.salesperson_name || '-'))}</div></div>
                <div class="glass p-2"><div class="text-gray-400">ماه</div><div class="text-white font-bold">${this.escapeHtml(this.ccMonthLabel(cycle.billing_month || ''))}</div></div>
                <div class="glass p-2"><div class="text-gray-400">Calculated</div><div class="text-white font-bold">${this.formatMoney(cycle.calculated_amount || 0)}</div></div>
                <div class="glass p-2"><div class="text-gray-400">Deadline</div><div class="text-white font-bold">${this.escapeHtml(this.ccDateLabel(cycle.approval_deadline || ''))}</div></div>
            `;
        }
        const countdown = document.getElementById('payout-deadline-countdown');
        if (countdown) {
            const deadline = new Date(String(cycle.approval_deadline || '').replace(' ', 'T'));
            const now = new Date();
            const diff = Math.floor((deadline.getTime() - now.getTime()) / 1000);
            if (Number.isFinite(diff) && diff > 0) {
                const hours = Math.floor(diff / 3600);
                const mins = Math.floor((diff % 3600) / 60);
                countdown.textContent = `زمان باقی‌مانده تا T-2 Deadline: ${hours}h ${mins}m`;
            } else {
                countdown.textContent = 'مهلت تایید پایان یافته (escalation فعال می‌شود)';
            }
        }
        this.openModal('modalApprovePayout');
    },

    async decideCommissionPayout(action) {
        const cycleId = (document.getElementById('payout-cycle-id')?.value || '').trim();
        const overrideAmount = (document.getElementById('payout-override-amount')?.value || '').trim();
        const overrideReason = (document.getElementById('payout-override-reason')?.value || '').trim();
        if (!cycleId) return;

        if ((action === 'OVERRIDE' || action === 'REJECT') && !overrideReason) {
            Swal.fire('خطا', 'دلیل تصمیم الزامی است', 'error');
            return;
        }
        if (action === 'OVERRIDE' && overrideAmount === '') {
            Swal.fire('خطا', 'مبلغ override الزامی است', 'error');
            return;
        }

        const res = await this.req('decide_commission_payout', {
            cycle_id: cycleId,
            action,
            override_amount: overrideAmount,
            override_reason: overrideReason
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ثبت تصمیم', 'error');
            return;
        }
        this.closeModal('modalApprovePayout');
        await this.loadCommissionPayoutQueue();
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تصمیم ذخیره شد', showConfirmButton: false, timer: 1800 });
    },

    async loadFinanceChequeInbox() {
        const wrap = document.getElementById('finance-inbox-list');
        if (!wrap) return;
        wrap.innerHTML = '<div class="text-gray-400 text-xs"><i class="fa-solid fa-spinner fa-spin ml-1"></i>در حال بارگذاری کارتابل مالی...</div>';
        const res = await this.req('get_finance_cheque_inbox', {});
        if (res.status !== 'success') {
            wrap.innerHTML = `<div class="text-red-400 text-xs">${this.escapeHtml(res.message || 'خطا در دریافت کارتابل مالی')}</div>`;
            return;
        }
        const rows = Array.isArray(res.rows) ? res.rows : [];
        if (!rows.length) {
            wrap.innerHTML = '<div class="text-gray-500 text-xs">چکی در انتظار تایید مالی وجود ندارد</div>';
            return;
        }
        wrap.innerHTML = `
            <table class="w-full text-xs text-gray-300">
                <thead>
                    <tr class="border-b border-gray-700 text-gray-400">
                        <th class="text-right p-2">مشتری/معامله</th>
                        <th class="text-right p-2">مبلغ چک</th>
                        <th class="text-right p-2">درصد از کل</th>
                        <th class="text-right p-2">تاریخ سررسید</th>
                        <th class="text-right p-2">اقدامات</th>
                    </tr>
                </thead>
                <tbody>
                    ${rows.map((row) => {
                        const cheque = Array.isArray(row.cheques) && row.cheques.length ? row.cheques[0] : {};
                        return `
                            <tr class="border-b border-gray-800">
                                <td class="p-2">${this.escapeHtml(String(row.deal_didar_id || '-'))}</td>
                                <td class="p-2">${this.formatMoney(row.total_cheque_value || 0)}</td>
                                <td class="p-2">${this.formatNumber(Number(row.cheque_percentage_of_deal || 0).toFixed(2))}%</td>
                                <td class="p-2">${this.escapeHtml(String(cheque.cheque_due_date || '-'))}</td>
                                <td class="p-2">
                                    <div class="flex gap-1">
                                        <button class="btn-primary bg-green-600 hover:bg-green-500 px-2 py-1 rounded text-[11px]" onclick="app.approveChequeAndCash('${this.escapeHtml(String(cheque.id || ''))}')">تایید و وصول</button>
                                        <button class="btn-primary bg-red-600 hover:bg-red-500 px-2 py-1 rounded text-[11px]" onclick="app.rejectCheque('${this.escapeHtml(String(cheque.id || ''))}')">رد/برگشت</button>
                                    </div>
                                </td>
                            </tr>
                        `;
                    }).join('')}
                </tbody>
            </table>
        `;
    },

    async approveChequeAndCash(paymentId) {
        if (!paymentId) return;
        const result = await Swal.fire({
            title: 'تاریخ دقیق وصول را وارد کنید',
            input: 'text',
            inputPlaceholder: '1405/02/15 یا 2026-05-05',
            showCancelButton: true,
            confirmButtonText: 'ثبت وصول',
            cancelButtonText: 'انصراف'
        });
        if (!result.isConfirmed) return;
        const cashedDateRaw = String(result.value || '').trim();
        const cashedDate = this.toGregorianDateOnly(cashedDateRaw, '00:00');
        if (!cashedDate) {
            Swal.fire('خطا', 'تاریخ وصول معتبر نیست', 'error');
            return;
        }
        const res = await this.req('finance_approve_cheque', {
            deal_payment_id: paymentId,
            cashed_date: cashedDate
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ثبت تایید مالی', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'وصول چک تایید شد', showConfirmButton: false, timer: 1800 });
        await this.loadFinanceChequeInbox();
        await this.loadCommissions();
    },

    async rejectCheque(paymentId) {
        if (!paymentId) return;
        const result = await Swal.fire({
            title: 'دلیل رد/برگشت',
            input: 'textarea',
            inputPlaceholder: 'علت برگشت یا رد چک',
            showCancelButton: true,
            confirmButtonText: 'ثبت رد',
            cancelButtonText: 'انصراف'
        });
        if (!result.isConfirmed) return;
        const reason = String(result.value || '').trim();
        if (!reason) {
            Swal.fire('خطا', 'دلیل رد الزامی است', 'error');
            return;
        }
        const res = await this.req('finance_reject_cheque', {
            deal_payment_id: paymentId,
            reason
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ثبت رد مالی', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'رد چک ثبت شد', showConfirmButton: false, timer: 1800 });
        await this.loadFinanceChequeInbox();
    },

    toGregorianDateOnly(inputValue, time = '00:00') {
        const value = (inputValue || '').trim();
        if (!value) return '';
        const normalized = this.toEnglishDigits(value).replace(/-/g, '/');
        const converted = persianToGregorian(normalized, time);
        if (converted) {
            return String(converted).split(' ')[0];
        }
        if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
            return value;
        }
        return '';
    },
    
    renderUsers(users, crmOwner = {}, hierarchyAssignments = []) {
        const tbody = document.getElementById('users-table');
        if(!tbody) return;
        this.state.lastUsersList = users || [];
        this.state.lastUserHierarchyAssignments = Array.isArray(hierarchyAssignments) ? hierarchyAssignments : [];
        tbody.innerHTML = '';
        const canEditUsers = this.canCurrentUserCapabilityClient('users.edit');
        const canEditRoles = this.canCurrentUserCapabilityClient('users.roles.edit');
        const canTransferUsers = this.canCurrentUserCapabilityClient('users.transfer_data');
        const canDeleteUsers = this.canCurrentUserCapabilityClient('users.delete');
        const canChangePasswords = this.canCurrentUserCapabilityClient('users.passwords.change');
        const masterStatus = document.getElementById('master-password-status');
        if (masterStatus) {
            const configured = !!this.state.masterPasswordConfigured;
            masterStatus.textContent = configured ? 'تنظیم شده' : 'تنظیم نشده';
            masterStatus.className = `text-xs px-2 py-1 rounded ${configured ? 'bg-green-900/40 text-green-300' : 'bg-amber-900/40 text-amber-300'}`;
        }
        users.forEach(u => {
            const userDisplayName = this.escapeHtml(u.display_name || (u.first_name + ' ' + u.last_name));
            tbody.innerHTML += `<tr>
                <td class="p-3">${userDisplayName}<div class="text-xs text-gray-500 mt-1">${u.email || '-'}</div></td>
                <td class="p-3 font-mono text-xs">${this.escapeHtml(u.username || u.display_name || '-')}</td>
                <td class="p-3 text-xs"><span class="px-2 py-0.5 bg-slate-800 rounded">${this.escapeHtml(this.getUserRoleLabel(u.role))}</span></td>
                <td class="p-3 font-mono text-xs text-gray-500">${this.escapeHtml(u.didar_user_id || '-')}<div class="mt-1 ${Number(u.is_active) === 1 ? 'text-green-400' : 'text-red-400'}">${Number(u.is_active) === 1 ? 'فعال' : 'غیرفعال'}</div></td>
                <td class="p-3">
                    <div class="flex gap-2 flex-wrap">
                        ${canEditUsers ? `<button onclick="app.openUserModal(${u.id})" class="text-xs text-cyan-400 hover:text-cyan-300 px-2 py-1 rounded bg-cyan-500/10">
                            <i class="fa-solid fa-pen ml-1"></i> ویرایش
                        </button>` : ''}
                        ${canEditRoles ? `<button onclick="app.openEditUserRoleModal(${u.id}, '${u.role}', '${this.escapeHtml(u.display_name || (u.first_name + ' ' + u.last_name))}')" class="text-xs text-blue-500 hover:text-blue-400 px-2 py-1 rounded bg-blue-500/10">
                            <i class="fa-solid fa-user-gear ml-1"></i> ویرایش نقش
                        </button>` : ''}
                        ${canTransferUsers ? `<button onclick="app.transferUserData(${u.id}, '${userDisplayName.replace(/'/g, "\\'")}')" class="text-xs text-purple-400 hover:text-purple-300 px-2 py-1 rounded bg-purple-500/10">
                            <i class="fa-solid fa-right-left ml-1"></i> انتقال دیتا
                        </button>` : ''}
                        ${canDeleteUsers ? `<button onclick="app.deleteUser(${u.id}, '${userDisplayName.replace(/'/g, "\\'")}')" class="text-xs text-red-400 hover:text-red-300 px-2 py-1 rounded bg-red-500/10">
                            <i class="fa-solid fa-trash ml-1"></i> حذف
                        </button>` : ''}
                        ${canChangePasswords ? `<button onclick="app.openChangePasswordModal(${u.id})" class="text-xs text-yellow-500 hover:text-yellow-400 px-2 py-1 rounded bg-yellow-500/10">
                            <i class="fa-solid fa-key ml-1"></i> تغییر رمز
                        </button>` : ''}
                    </div>
                </td>
            </tr>`;
        });
        
        const crmSelect = document.getElementById('crm-owner-select');
        const crmLabel = document.getElementById('crm-owner-current');
        if (crmSelect) {
            crmSelect.innerHTML = '<option value="">-- انتخاب کاربر از CRM خارجی --</option>';
            (window.didarUsers || []).forEach(u => {
                const opt = document.createElement('option');
                const value = u.Id || u.UserId;
                opt.value = value || '';
                opt.textContent = (u.DisplayName || ((u.FirstName || '') + ' ' + (u.LastName || ''))) + (u.UserName ? ` (${u.UserName})` : '');
                crmSelect.appendChild(opt);
            });
            if (crmOwner?.id) {
                crmSelect.value = crmOwner.id;
            }
        }
        if (crmLabel) {
            crmLabel.textContent = crmOwner?.name ? `کاربر فعلی: ${crmOwner.name}` : 'کاربر فعلی: انتخاب نشده';
        }
        
        this.state.crmOwnerId = crmOwner?.id || null;
        this.state.crmOwnerName = crmOwner?.name || '';
        this.renderUserHierarchyManager();
    },

    renderUserHierarchyManager() {
        const users = Array.isArray(this.state.lastUsersList) ? this.state.lastUsersList : [];
        const rows = Array.isArray(this.state.lastUserHierarchyAssignments) ? this.state.lastUserHierarchyAssignments : [];
        const userSelect = document.getElementById('hierarchy-user-select');
        const supervisorSelect = document.getElementById('hierarchy-supervisor-select');
        const managerSelect = document.getElementById('hierarchy-manager-select');
        const tbody = document.getElementById('user-hierarchy-table');
        if (!userSelect || !supervisorSelect || !managerSelect || !tbody) return;

        const userOptions = users.map(u => {
            const id = String(u.id || '');
            if (!id) return '';
            const name = u.display_name || `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || id;
            return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(name)}</option>`;
        }).join('');
        userSelect.innerHTML = '<option value="">-- کاربر --</option>' + userOptions;
        supervisorSelect.innerHTML = '<option value="">بدون سرپرست</option>' + userOptions;
        managerSelect.innerHTML = '<option value="">بدون مدیر</option>' + userOptions;

        const effectiveFrom = document.getElementById('hierarchy-effective-from');
        if (effectiveFrom && !effectiveFrom.value) {
            effectiveFrom.value = new Date().toISOString().slice(0, 10);
        }

        if (!rows.length) {
            tbody.innerHTML = '<tr><td colspan="6" class="p-4 text-center text-gray-500">ساختاری ثبت نشده است.</td></tr>';
            return;
        }
        tbody.innerHTML = rows.map(row => `
            <tr>
                <td class="p-3">${this.escapeHtml(row.user_name || row.user_id || '-')}</td>
                <td class="p-3">${this.escapeHtml(row.supervisor_name || '-')}</td>
                <td class="p-3">${this.escapeHtml(row.manager_name || '-')}</td>
                <td class="p-3 font-mono text-gray-400">${this.escapeHtml(this.ccDateLabel(row.effective_from || ''))}</td>
                <td class="p-3 font-mono text-gray-400">${this.escapeHtml(row.effective_to ? this.ccDateLabel(row.effective_to) : 'تاکنون')}</td>
                <td class="p-3 font-mono text-gray-500">${this.escapeHtml(this.ccDateLabel(row.created_at || ''))}</td>
            </tr>
        `).join('');
    },

    async saveUserHierarchy() {
        if (!this.canCurrentUserCapabilityClient('users.edit')) return;
        const userId = document.getElementById('hierarchy-user-select')?.value || '';
        const supervisorUserId = document.getElementById('hierarchy-supervisor-select')?.value || '';
        const managerUserId = document.getElementById('hierarchy-manager-select')?.value || '';
        const effectiveFrom = document.getElementById('hierarchy-effective-from')?.value || '';
        const effectiveTo = document.getElementById('hierarchy-effective-to')?.value || '';
        if (!userId || !effectiveFrom) {
            Swal.fire('خطا', 'کاربر و تاریخ شروع الزامی است', 'error');
            return;
        }
        const res = await this.req('save_user_hierarchy', {
            user_id: userId,
            supervisor_user_id: supervisorUserId,
            manager_user_id: managerUserId,
            effective_from: effectiveFrom,
            effective_to: effectiveTo
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در ذخیره ساختار', 'error');
            return;
        }
        this.state.lastUserHierarchyAssignments = res.hierarchy_assignments || [];
        this.renderUserHierarchyManager();
        Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'ساختار ذخیره شد', showConfirmButton:false, timer:1800});
    },

    async loadLookupAdmin(force = false) {
        if (!this.canCurrentUserCapabilityClient('lookups.view')) return;
        const select = document.getElementById('lookup-group-select');
        if (!select) return;
        this.toggleLookupServiceSpecificColumns(false);
        this.toggleLookupDestinationBankColumns(false);

        if (!force && this.state.lookupGroups && this.state.lookupGroups.length) {
            if (this.state.lookupSelectedGroup) {
                select.value = this.state.lookupSelectedGroup;
                await this.onLookupGroupChange();
            }
            return;
        }

        const res = await this.req('get_lookup_groups', { include_inactive: 1 });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در بارگذاری گروه‌ها', 'error');
            return;
        }
        this.state.lookupGroups = res.groups || [];
        
        select.innerHTML = '<option value="">-- انتخاب کنید --</option>';
        
        // Add Lookup Groups
        const groupGroup = document.createElement('optgroup');
        groupGroup.label = 'گروه‌های لوکاپ (دارای لیست)';
        this.state.lookupGroups.forEach(group => {
            const opt = document.createElement('option');
            opt.value = group.code;
            opt.textContent = group.title || group.code;
            groupGroup.appendChild(opt);
        });
        select.appendChild(groupGroup);

        const toolsGroup = document.createElement('optgroup');
        toolsGroup.label = 'ابزارهای مرتبط';
        const productManagerOpt = document.createElement('option');
        productManagerOpt.value = '__products_manager__';
        productManagerOpt.textContent = 'مدیریت محصولات';
        toolsGroup.appendChild(productManagerOpt);
        const rbacManagerOpt = document.createElement('option');
        rbacManagerOpt.value = '__rbac_manager__';
        rbacManagerOpt.textContent = 'مدیریت نقش‌ها و دسترسی‌ها (RBAC)';
        toolsGroup.appendChild(rbacManagerOpt);
        select.appendChild(toolsGroup);

        // Add System Fields
        const systemGroup = document.createElement('optgroup');
        systemGroup.label = 'فیلدهای سیستمی';
        [
            { code: 'deal_title', title: 'عنوان معامله' },
            { code: 'payment_method', title: 'نحوه پرداخت' },
            { code: 'payment_amount', title: 'مبلغ پرداخت' },
            { code: 'payment_card_last4', title: '۴ رقم آخر کارت' },
            { code: 'destination_bank', title: 'بانک مقصد پرداخت' },
            { code: 'refund_amount', title: 'مبلغ مرجوعی' },
            { code: 'service_cost', title: 'مبلغ خدمات' },
            { code: 'payable_amount', title: 'مبلغ قابل پرداخت' },
            { code: 'person_name', title: 'نام لید' },
            { code: 'person_mobile', title: 'شماره موبایل لید' }
        ].forEach(field => {
            const opt = document.createElement('option');
            opt.value = field.code;
            opt.textContent = field.title;
            systemGroup.appendChild(opt);
        });
        select.appendChild(systemGroup);

        if (!this.state.lookupSelectedGroup && this.state.lookupGroups.length) {
            this.state.lookupSelectedGroup = this.state.lookupGroups[0].code;
        }
        if (this.state.lookupSelectedGroup) {
            select.value = this.state.lookupSelectedGroup;
            await this.onLookupGroupChange();
        }
    },

    async ensureFieldConfigsLoaded() {
        if (!this.state.fieldConfigurations || !this.state.fieldConfigurations.length) {
            await this.loadFieldConfigurationsPublic('deal');
        }
    },

    async loadFieldConfigurationsPublic(entityType = 'deal') {
        try {
            const res = await this.req('get_field_configurations_public', { entity_type: entityType });
            if (res.status === 'success') {
                this.state.fieldConfigurations = res.configurations || [];
            }
        } catch (error) {
            console.error('Error loading public field configurations:', error);
        }
    },

    async onLookupGroupChange() {
        const select = document.getElementById('lookup-group-select');
        const groupCode = select?.value || '';
        this.state.lookupSelectedGroup = groupCode;
        
        const addBtn = document.getElementById('lookup-add-item-btn');
        const itemsSection = document.getElementById('lookup-items-section');
        const quickSettings = document.getElementById('field-quick-settings');
        const tbody = document.getElementById('lookup-items-table');
        const isProductsManager = groupCode === '__products_manager__';
        const isRbacManager = groupCode === '__rbac_manager__';
        const isPipelineStageGroup = groupCode === 'pipeline_stages';

        this.setLookupManagerModeUI(
            isProductsManager ? 'products_manager' : (isRbacManager ? 'rbac_manager' : 'lookup_or_field')
        );
        this.toggleLookupServiceSpecificColumns(groupCode === 'allowed_services');
        this.toggleLookupDestinationBankColumns(groupCode === 'payment_destination_banks');
        this.toggleLookupPipelineStageUserColumns(isPipelineStageGroup);
        
        if (!groupCode) {
            if (tbody) tbody.innerHTML = '';
            if (addBtn) addBtn.disabled = true;
            if (itemsSection) itemsSection.classList.add('hidden');
            if (quickSettings) quickSettings.classList.add('hidden');
            return;
        }

        if (isProductsManager) {
            await this.loadProducts();
            this.renderLookupProductsManagerTable();
            return;
        }
        if (isRbacManager) {
            await this.loadLookupRbacManagerData();
            return;
        }

        // Show items section if it's a real lookup group
        const isLookupGroup = this.state.lookupGroups.some(g => g.code === groupCode);
        
        // Items section visibility
        if (itemsSection) itemsSection.classList.remove('hidden');
        
        if (isLookupGroup) {
            if (addBtn) addBtn.disabled = false;
            if (isPipelineStageGroup) {
                await this.ensureLookupStageUserOptionsLoaded();
            }
            await this.loadLookupItems(groupCode);
        } else {
            // For system fields, clear items but keep settings
            if (tbody) {
                tbody.innerHTML = `<tr><td colspan="${this.getLookupTableColumnCount()}" class="p-8 text-center text-gray-500 italic">این یک فیلد سیستمی است و گزینه‌های لیست ندارد. فقط تنظیمات بالا اعمال می‌شود.</td></tr>`;
            }
            if (addBtn) addBtn.disabled = true;
        }

        // Load settings for this groupCode/fieldName
        await this.loadFieldConfigurationForAdmin(groupCode);
    },

    async loadFieldConfigurationForAdmin(fieldName) {
        const advSettings = document.getElementById('field-advanced-settings');
        
        const setVal = (id, val, isCheck = false) => {
            const el = document.getElementById(id);
            if (!el) return;
            if (isCheck) el.checked = !!val;
            else el.value = val || '';
        };
        
        try {
            const res = await this.req('get_field_configuration', { field_name: fieldName });
            const config = (res.status === 'success' && res.configuration) ? res.configuration : null;
            
            // Populate all fields safely
            setVal('fc-field-label', config?.field_label);
            setVal('fc-is-required', config?.is_required === 1, true);
            setVal('fc-is-editable', config?.is_editable !== 0, true); // Default to true
            setVal('fc-is-editable-after-create', config?.is_editable_after_create !== 0, true); // Default to true
            setVal('fc-is-active', config?.is_active !== 0, true);     // Default to true
            setVal('fc-help-text', config?.help_text);
            setVal('fc-default-value', config?.default_value);
            setVal('fc-group-name', config?.group_name);
            setVal('fc-validation-pattern', config?.validation_pattern);
            
            // Parse and populate conditional logic UI
            const conditionalLogic = config?.conditional_logic;
            if (conditionalLogic && typeof conditionalLogic === 'object') {
                setVal('fc-conditional-field', conditionalLogic.field);
                setVal('fc-conditional-operator', conditionalLogic.operator || '=');
                setVal('fc-conditional-value', conditionalLogic.value);
                setVal('fc-conditional-logic', JSON.stringify(conditionalLogic));
            } else if (conditionalLogic && typeof conditionalLogic === 'string') {
                try {
                    const parsed = JSON.parse(conditionalLogic);
                    setVal('fc-conditional-field', parsed.field);
                    setVal('fc-conditional-operator', parsed.operator || '=');
                    setVal('fc-conditional-value', parsed.value);
                } catch (e) {}
                setVal('fc-conditional-logic', conditionalLogic);
            } else {
                setVal('fc-conditional-field', '');
                setVal('fc-conditional-operator', '=');
                setVal('fc-conditional-value', '');
                setVal('fc-conditional-logic', '');
            }

            // Set edit roles checkboxes
            const editRoles = config?.edit_allow_roles || [];
            document.querySelectorAll('.role-checkbox').forEach(cb => {
                cb.checked = editRoles.includes(cb.value);
            });

            // Set delete roles checkboxes
            const deleteRoles = config?.delete_allow_roles || [];
            document.querySelectorAll('.delete-role-checkbox').forEach(cb => {
                cb.checked = deleteRoles.includes(cb.value);
            });

            // Set view roles checkboxes
            const viewRoles = config?.view_allow_roles || [];
            document.querySelectorAll('.view-role-checkbox').forEach(cb => {
                cb.checked = viewRoles.includes(cb.value);
            });

            // Show the advanced settings panel
            if (advSettings) advSettings.classList.remove('hidden');
        } catch (error) {
            console.error('Error loading field config:', error);
        }
    },

    async saveFieldConfigurationMerged() {
        const fieldName = document.getElementById('lookup-group-select').value;
        if (!fieldName) return;

        // Collect edit roles
        const selectedRoles = Array.from(
            document.querySelectorAll('.role-checkbox:checked')
        ).map(cb => cb.value);

        // Collect delete roles
        const selectedDeleteRoles = Array.from(
            document.querySelectorAll('.delete-role-checkbox:checked')
        ).map(cb => cb.value);

        // Collect view roles
        const selectedViewRoles = Array.from(
            document.querySelectorAll('.view-role-checkbox:checked')
        ).map(cb => cb.value);

        const isLookupGroup = (this.state.lookupGroups || []).some(g => g.code === fieldName);

        const data = {
            field_name: fieldName,
            field_label: document.getElementById('fc-field-label')?.value || '',
            field_type: isLookupGroup ? 'select' : 'text',
            lookup_group_code: isLookupGroup ? fieldName : null,
            is_required: document.getElementById('fc-is-required')?.checked ? 1 : 0,
            is_editable: document.getElementById('fc-is-editable')?.checked ? 1 : 0,
            is_editable_after_create: document.getElementById('fc-is-editable-after-create')?.checked ? 1 : 0,
            is_active: document.getElementById('fc-is-active')?.checked ? 1 : 0,
            edit_allow_roles: JSON.stringify(selectedRoles),
            delete_allow_roles: JSON.stringify(selectedDeleteRoles),
            view_allow_roles: JSON.stringify(selectedViewRoles),
            help_text: document.getElementById('fc-help-text')?.value || '',
            default_value: document.getElementById('fc-default-value')?.value || '',
            group_name: document.getElementById('fc-group-name')?.value || '',
            validation_pattern: document.getElementById('fc-validation-pattern')?.value || '',
            conditional_logic: document.getElementById('fc-conditional-logic')?.value || ''
        };

        try {
            const res = await this.req('save_field_configuration', data);
            if (res.status === 'success') {
                // Toast success
                const Toast = Swal.mixin({
                    toast: true,
                    position: 'bottom-end',
                    showConfirmButton: false,
                    timer: 2000,
                    timerProgressBar: true
                });
                Toast.fire({
                    icon: 'success',
                    title: 'تنظیمات ذخیره شد'
                });
                
                // Update current label in UI
                const currentLabel = document.getElementById('fc-current-label');
                if (currentLabel && data.field_label) {
                    currentLabel.textContent = data.field_label;
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در ذخیره‌سازی', 'error');
            }
        } catch (error) {
            Swal.fire('خطا', 'خطا در ارتباط با سرور', 'error');
        }
    },

    // Apply conditional logic from UI fields
    applyConditionalLogicFromUI() {
        const field = document.getElementById('fc-conditional-field')?.value?.trim();
        const operator = document.getElementById('fc-conditional-operator')?.value || '=';
        const value = document.getElementById('fc-conditional-value')?.value?.trim();

        if (!field) {
            // Clear conditional logic
            document.getElementById('fc-conditional-logic').value = '';
            this.saveFieldConfigurationMerged();
            return;
        }

        const logic = JSON.stringify({
            field: field,
            operator: operator,
            value: value
        });

        document.getElementById('fc-conditional-logic').value = logic;
        this.saveFieldConfigurationMerged();

        Swal.fire({
            toast: true,
            position: 'bottom-end',
            icon: 'success',
            title: 'منطق شرطی ذخیره شد',
            showConfirmButton: false,
            timer: 2000
        });
    },

    async applyFieldConfigurations(entityType, containerSelector) {
        try {
            const res = await this.req('get_field_configurations', { entity_type: entityType });
            if (res.status !== 'success') return;
            
            const configs = res.configurations || [];
            const container = document.querySelector(containerSelector);
            if (!container) return;

            configs.forEach(config => {
                const fieldName = config.field_name;
                const input = container.querySelector(`[name="${fieldName}"], [name="${fieldName}[]"]`);
                if (!input) return;

                // 1. Tooltips (Help Text)
                if (config.help_text) {
                    const label = input.closest('div')?.querySelector('label');
                    if (label) {
                        label.title = config.help_text;
                        label.classList.add('cursor-help');
                        if (!label.querySelector('.fa-circle-info')) {
                            label.innerHTML += ` <i class="fa-solid fa-circle-info text-[9px] text-gray-500"></i>`;
                        }
                    }
                }

                // 2. Default Values (for new records)
                const dealIdInput = document.getElementById('deal-id');
                const isNew = !dealIdInput || !dealIdInput.value;
                if (isNew && config.default_value && (!input.value || input.value === '0')) {
                    input.value = config.default_value;
                    if (input.tagName === 'SELECT') input.dispatchEvent(new Event('change'));
                }

                // 3. Conditional Logic (support both legacy and new schema)
                if (config.conditional_logic) {
                    try {
                        const logic = typeof config.conditional_logic === 'string' ? JSON.parse(config.conditional_logic) : config.conditional_logic;
                        const wrapper = input.closest('.grid > div') || input.closest('div');
                        const evaluate = (actualValue, operator, expected) => {
                            switch (operator) {
                                case '=':
                                case '==':
                                    return actualValue == expected;
                                case '!=':
                                case '<>':
                                    return actualValue != expected;
                                case 'in':
                                    return Array.isArray(expected) && expected.includes(actualValue);
                                case 'not_in':
                                    return Array.isArray(expected) && !expected.includes(actualValue);
                                case 'empty':
                                    return !actualValue;
                                case 'not_empty':
                                    return !!actualValue;
                                case 'contains':
                                    return typeof actualValue === 'string' && actualValue.includes(expected);
                                default:
                                    return true;
                            }
                        };

                        // New schema: {field, operator, value}
                        if (logic.field) {
                            const dependInput = container.querySelector(`[name="${logic.field}"], [name="${logic.field}[]"]`);
                            if (dependInput) {
                                const checkVisibility = () => {
                                    const val = dependInput.value;
                                    const shouldShow = evaluate(val, logic.operator || '=', logic.value);
                                    if (wrapper) wrapper.classList.toggle('hidden', !shouldShow);
                                    if (input && config.is_required === 1) input.required = shouldShow;
                                };
                                dependInput.addEventListener('change', checkVisibility);
                                checkVisibility();
                            }
                        }

                        // Legacy schema: {depends_on, show_if}
                        if (logic.depends_on && logic.show_if) {
                            const dependInput = container.querySelector(`[name="${logic.depends_on}"], [name="${logic.depends_on}[]"]`);
                            if (dependInput) {
                                const checkVisibility = () => {
                                    const val = dependInput.value;
                                    const shouldShow = Array.isArray(logic.show_if) ? logic.show_if.includes(val) : val === logic.show_if;
                                    if (wrapper) wrapper.classList.toggle('hidden', !shouldShow);
                                    if (input && config.is_required === 1) input.required = shouldShow;
                                };
                                dependInput.addEventListener('change', checkVisibility);
                                checkVisibility();
                            }
                        }
                    } catch (e) {}
                }

                // 4. View permissions
                const userRole = (typeof window.APP_USER !== 'undefined' ? window.APP_USER.role : 'agent');
                const viewRoles = config.view_allow_roles || [];
                if (viewRoles.length && !viewRoles.includes(userRole)) {
                    const wrapper = input.closest('.grid > div') || input.closest('div');
                    if (wrapper) wrapper.classList.add('hidden');
                    input.readOnly = true;
                    if (input.tagName === 'SELECT') input.disabled = true;
                    input.required = false;
                }

                // 5. Role-based Access (Frontend UI)
                const allowedRoles = config.edit_allow_roles || [];
                const editable = config.is_editable !== 0;
                if (allowedRoles.length && !allowedRoles.includes(userRole) || !editable) {
                    input.readOnly = true;
                    if (input.tagName === 'SELECT') input.disabled = true;
                    input.classList.add('bg-gray-900/40', 'cursor-not-allowed', 'opacity-70');
                }
            });
        } catch (e) { console.error('Error applying field configs:', e); }
    },

    /**
     * Check if the current user can edit a field based on configuration
     * @param {string} fieldName - The field name to check
     * @param {boolean} isExisting - Whether the entity already exists (for editable_after_create check)
     * @returns {boolean} - Whether the user can edit this field
     */
    canUserEditFieldClient(fieldName, isExisting = false) {
        const role = (typeof window.APP_USER !== 'undefined' ? window.APP_USER.role : 'agent') || 'agent';
        if (role === 'admin') return true;
        
        // Define aliases for client-side check
        const aliases = {
            'payment_method': ['payment_methods'],
            'destination_bank': ['payment_destination_banks', 'bank_destination'],
            'payment_card_last4': ['card_last4', 'card_last_4', 'payment_card_last_4'],
            'deal_title': ['title']
        };

        const namesToCheck = [fieldName, ...(aliases[fieldName] || [])];
        
        // Find config among aliases (prioritize alias/group code first)
        let config = null;
        for (const name of namesToCheck) {
            config = (this.state.fieldConfigurations || []).find(c => c.field_name === name);
            if (config) break;
        }
        
        // No config = allow by default
        if (!config) return true;
        
        // Check if active
        if (config.is_active === 0) return false;
        
        // Check if editable at all
        if (config.is_editable === 0) return false;
        
        // Check if editable after create (for existing records)
        if (isExisting && config.is_editable_after_create === 0) {
            return false;
        }

        // Check view role permissions
        const viewRoles = config.view_allow_roles || [];
        if (viewRoles.length && !viewRoles.includes(role)) {
            return false;
        }
        
        // Check role permissions
        const roles = config.edit_allow_roles || [];
        if (!roles.length) return true;
        return roles.includes(role);
    },

    canUserViewFieldClient(fieldName) {
        const role = (typeof window.APP_USER !== 'undefined' ? window.APP_USER.role : 'agent') || 'agent';
        if (role === 'admin') return true;
        const config = (this.state.fieldConfigurations || []).find(c => c.field_name === fieldName);
        if (!config || config.is_active === 0) {
            return !['deal_acquaintance_source', 'deal_acquaintance_detail', 'deal_content_topic'].includes(fieldName);
        }
        const viewRoles = config.view_allow_roles || [];
        if (!viewRoles.length) return true;
        return viewRoles.includes(role);
    },

    updateDealMarketingUi(deal = null, isNewDeal = false, leadDefaults = null) {
        const fields = document.getElementById('deal-marketing-fields');
        const summary = document.getElementById('deal-marketing-summary');
        const source = document.getElementById('deal-source');
        const detail = document.getElementById('deal-source-detail');
        const topic = document.getElementById('deal-content-topic');
        const canViewSaved =
            this.canUserViewFieldClient('deal_acquaintance_source') ||
            this.canUserViewFieldClient('deal_acquaintance_detail') ||
            this.canUserViewFieldClient('deal_content_topic');

        if (fields) fields.classList.toggle('hidden', !isNewDeal && !canViewSaved);
        fields?.querySelectorAll('select, input, textarea').forEach((input) => {
            input.disabled = !isNewDeal && !canViewSaved;
        });
        if (source) {
            source.required = !!isNewDeal;
            source.disabled = !isNewDeal && !canViewSaved;
        }
        if (topic) {
            topic.required = !!isNewDeal;
            topic.disabled = !isNewDeal && !canViewSaved;
        }
        if (detail) {
            detail.required = false;
            detail.disabled = !isNewDeal && !canViewSaved;
        }

        if (deal) {
            if (source) source.value = deal.acquaintance_source || '';
            this.handleDealSourceChange();
            if (detail) detail.value = deal.acquaintance_detail || '';
            if (topic) topic.value = deal.content_topic || '';
        } else {
            if (source) source.value = '';
            this.handleDealSourceChange();
            if (detail) detail.value = '';
            if (topic) topic.value = '';
        }

        if (summary) summary.classList.toggle('hidden', isNewDeal || !canViewSaved);
        const setText = (id, value) => {
            const el = document.getElementById(id);
            if (el) el.textContent = value || '-';
        };
        if (deal && canViewSaved) {
            setText('deal-summary-source', this.getLookupLabel('lead_sources', deal.acquaintance_source || ''));
            setText('deal-summary-source-detail', this.getLookupLabel('lead_source_details', deal.acquaintance_detail || ''));
            setText('deal-summary-content-topic', this.getLookupLabel('content_topics', deal.content_topic || ''));
        } else {
            setText('deal-summary-source', '-');
            setText('deal-summary-source-detail', '-');
            setText('deal-summary-content-topic', '-');
        }
    },

    /**
     * Check if the current user can delete based on field configuration
     * @param {string} fieldName - The field name (usually a delete permission key like 'payment_delete')
     * @returns {boolean} - Whether the user can delete
     */
    canUserDeleteFieldClient(fieldName) {
        const role = (typeof window.APP_USER !== 'undefined' ? window.APP_USER.role : 'agent') || 'agent';
        if (role === 'admin') return true;
        
        // Define aliases for client-side check
        const aliases = {
            'payment_delete': ['payment_methods', 'payment_method'],
        };

        const namesToCheck = [fieldName, ...(aliases[fieldName] || [])];
        
        let config = null;
        for (const name of namesToCheck) {
            config = (this.state.fieldConfigurations || []).find(c => c.field_name === name);
            if (config) break;
        }
        
        // No config = allow by default
        if (!config) return true;
        
        // Check if active
        if (config.is_active === 0) return false;
        
        // Check delete role permissions
        const deleteRoles = config.delete_allow_roles || [];
        if (!deleteRoles.length) return true;
        return deleteRoles.includes(role);
    },

    async loadLookupItems(groupCode) {
        if (!groupCode || groupCode === '__products_manager__') return;
        const res = await this.req('get_lookup_items', { group: groupCode });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'خطا در بارگذاری گزینه‌ها', 'error');
            return;
        }
        this.state.lookupItems = res.items || [];
        this.renderLookupItemsTable(groupCode, this.state.lookupItems);
    },

    renderLookupItemsTable(groupCode, items) {
        const tbody = document.getElementById('lookup-items-table');
        if (!tbody) return;
        const parentOptions = groupCode === 'lead_source_details' ? this.getLookupOptions('lead_sources') : [];
        const isAllowedServicesGroup = groupCode === 'allowed_services';
        const isDestinationBankGroup = groupCode === 'payment_destination_banks';
        const isPipelineStageGroup = groupCode === 'pipeline_stages';
        if (!Array.isArray(items) || !items.length) {
            tbody.innerHTML = `<tr><td colspan="${this.getLookupTableColumnCount()}" class="p-6 text-center text-gray-500">موردی یافت نشد.</td></tr>`;
            this.toggleLookupServiceSpecificColumns(isAllowedServicesGroup);
            this.toggleLookupDestinationBankColumns(isDestinationBankGroup);
            this.toggleLookupPipelineStageUserColumns(isPipelineStageGroup);
            return;
        }
        tbody.innerHTML = (items || []).map(item => {
            const id = item.id || '';
            const title = this.escapeHtml(item.title || '');
            const value = this.escapeHtml(item.value || '');
            const code = this.escapeHtml(item.code || '');
            const metaJson = this.escapeHtml(item.meta_json || '');
            let servicePrice = '';
            let servicePriceListCode = '';
            let linkedProductIds = [];
            let destinationBankName = '';
            let destinationAccountType = '';
            let destinationCard = '';
            let destinationAccount = '';
            let destinationSheba = '';
            let destinationOwner = '';
            let destinationTafsili = '';
            let disabledStageUserIds = [];
            let parsedMeta = {};
            if (item.meta_json) {
                try {
                    const meta = JSON.parse(item.meta_json);
                    if (meta && typeof meta === 'object' && !Array.isArray(meta)) parsedMeta = meta;
                } catch (_) {}
            }
            if (isAllowedServicesGroup && item.meta_json) {
                if (parsedMeta.price !== undefined && parsedMeta.price !== null && parsedMeta.price !== '') {
                    servicePrice = formatMoneyString(parsedMeta.price);
                }
                if (parsedMeta.price_list_code !== undefined && parsedMeta.price_list_code !== null) {
                    servicePriceListCode = String(parsedMeta.price_list_code);
                }
            }
            if (isDestinationBankGroup) {
                destinationBankName = String(parsedMeta.bank_name ?? '');
                destinationAccountType = String(parsedMeta.account_type ?? '');
                destinationCard = String(parsedMeta.card_number ?? '');
                destinationAccount = String(parsedMeta.account_number ?? '');
                destinationSheba = String(parsedMeta.sheba ?? '');
                destinationOwner = String(parsedMeta.account_owner ?? '');
                destinationTafsili = String(parsedMeta.tafsili_code ?? '');
            }
            if (isPipelineStageGroup && Array.isArray(parsedMeta.disabled_user_didar_ids)) {
                disabledStageUserIds = parsedMeta.disabled_user_didar_ids.map(v => String(v));
            }
            if (isAllowedServicesGroup && item.id) {
                linkedProductIds = this.getProductsLinkedToServiceLookupItem(item.id).map(p => String(p.id));
            }
            const sortOrder = item.sort_order ?? 0;
            const isActive = String(item.is_active ?? 1) === '1' ? 'checked' : '';
            const isVisible = String(item.is_visible ?? 1) === '1' ? 'checked' : '';
            const isSystemItem = String(item.is_system ?? 0) === '1';
            const usageSummary = item.usage_summary || {};
            const usageTotal = Number(usageSummary.total || 0);
            const usageDetails = Array.isArray(usageSummary.details) ? usageSummary.details : [];
            const usageTitle = usageDetails.length
                ? usageDetails.map(detail => `${detail.label || 'استفاده'}: ${detail.count || 0}`).join(' | ')
                : '';
            const usageHtml = usageTotal > 0
                ? `<span class="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-amber-500/10 text-amber-200 border border-amber-500/30 text-[10px]" title="${this.escapeHtml(usageTitle)}"><i class="fa-solid fa-triangle-exclamation"></i>${usageTotal}</span>`
                : '<span class="text-[10px] text-gray-500">-</span>';
            const systemBadgeHtml = isSystemItem
                ? '<span class="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-blue-500/10 text-blue-200 border border-blue-500/30 text-[10px]" title="گزینه سیستمی قابل حذف نیست">سیستمی</span>'
                : '';
            const parentValue = item.parent_value || '';
            const parentSelect = parentOptions.length ? `
                <select class="input bg-gray-800 text-xs" name="parent_value">
                    <option value="">-</option>
                    ${parentOptions.map(opt => `<option value="${this.escapeHtml(opt.value)}" ${opt.value === parentValue ? 'selected' : ''}>${this.escapeHtml(opt.label || opt.value)}</option>`).join('')}
                </select>
            ` : '<span class="text-xs text-gray-500">-</span>';

            return `
                <tr data-id="${id}" data-group="${this.escapeHtml(groupCode)}" data-is-system="${isSystemItem ? '1' : '0'}" data-usage-total="${usageTotal}" data-usage-details="${this.escapeHtml(usageTitle)}">
                    <td class="p-3"><input class="input bg-gray-800 text-xs" name="title" value="${title}"></td>
                    <td class="p-3"><input class="input bg-gray-800 text-xs" name="value" value="${value}"></td>
                    <td class="p-3"><input class="input bg-gray-800 text-xs" name="code" value="${code}"></td>
                    <td class="p-3"><input class="input bg-gray-800 text-[10px] font-mono ${isDestinationBankGroup ? 'opacity-60 cursor-not-allowed' : ''}" name="meta_json" value="${metaJson}" ${isDestinationBankGroup ? 'readonly title="برای بانک‌های مقصد از ستون‌های اختصاصی استفاده کنید"' : ''}></td>
                    <td class="p-3 lookup-col-service-price ${isAllowedServicesGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs money-input ${isAllowedServicesGroup ? '' : 'opacity-60'}" name="service_price" value="${this.escapeHtml(servicePrice)}" ${isAllowedServicesGroup ? 'placeholder="قیمت خدمت"' : 'placeholder=\"-\" disabled'}></td>
                    <td class="p-3 lookup-col-service-price-list-code ${isAllowedServicesGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ${isAllowedServicesGroup ? '' : 'opacity-60'}" name="service_price_list_code" value="${this.escapeHtml(servicePriceListCode)}" ${isAllowedServicesGroup ? 'placeholder="کد لیست قیمت"' : 'placeholder=\"-\" disabled'}></td>
                    <td class="p-3 lookup-col-service-products ${isAllowedServicesGroup ? '' : 'hidden'}">${isAllowedServicesGroup ? this.buildProductsMultiSelectHtml(linkedProductIds) : '<span class="text-xs text-gray-500">-</span>'}</td>
                    <td class="p-3 lookup-col-destination-bank-name ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_bank_name" value="${this.escapeHtml(destinationBankName)}" ${isDestinationBankGroup ? 'placeholder="مثال: تجارت"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-account-type ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_account_type" value="${this.escapeHtml(destinationAccountType)}" ${isDestinationBankGroup ? 'placeholder="جاری / پس انداز"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-card ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_card" value="${this.escapeHtml(destinationCard)}" ${isDestinationBankGroup ? 'placeholder="شماره کارت"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-account ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_account" value="${this.escapeHtml(destinationAccount)}" ${isDestinationBankGroup ? 'placeholder="شماره حساب"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-sheba ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_sheba" value="${this.escapeHtml(destinationSheba)}" ${isDestinationBankGroup ? 'placeholder="IR..."' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-owner ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_owner" value="${this.escapeHtml(destinationOwner)}" ${isDestinationBankGroup ? 'placeholder="نام صاحب حساب"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-destination-tafsili ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_tafsili_code" value="${this.escapeHtml(destinationTafsili)}" ${isDestinationBankGroup ? 'placeholder="مثال: 101"' : 'disabled'}></td>
                    <td class="p-3 lookup-col-stage-disabled-users ${isPipelineStageGroup ? '' : 'hidden'}">${isPipelineStageGroup ? this.buildLookupStageDisabledUsersMultiSelectHtml(disabledStageUserIds) : '<span class="text-xs text-gray-500">-</span>'}</td>
                    <td class="p-3">${parentSelect}</td>
                    <td class="p-3"><input class="input bg-gray-800 text-xs" name="sort_order" value="${sortOrder}" type="number"></td>
                    <td class="p-3 text-center"><input type="checkbox" name="is_active" class="accent-yellow-500" ${isActive}></td>
                    <td class="p-3 text-center"><input type="checkbox" name="is_visible" class="accent-cyan-500" ${isVisible}></td>
                    <td class="p-3 text-center"><div class="flex flex-col items-center gap-1">${systemBadgeHtml}${usageHtml}</div></td>
                    <td class="p-3">
                        <div class="flex gap-2">
                            <button type="button" class="px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-xs" onclick="app.saveLookupRow(this)">Save</button>
                            <button type="button" class="px-3 py-1 rounded ${isSystemItem ? 'bg-gray-700 text-gray-400 cursor-not-allowed' : 'bg-red-600 hover:bg-red-500'} text-xs" ${isSystemItem ? 'disabled title="گزینه سیستمی قابل حذف نیست؛ برای خروج از فرم‌ها از فعال/نمایش استفاده کنید"' : ''} onclick="app.deleteLookupRow(this)">Delete</button>
                        </div>
                    </td>
                </tr>
            `;
        }).join('');
        this.toggleLookupServiceSpecificColumns(isAllowedServicesGroup);
        this.toggleLookupDestinationBankColumns(isDestinationBankGroup);
        this.toggleLookupPipelineStageUserColumns(isPipelineStageGroup);
    },

    addLookupRow() {
        const groupCode = this.state.lookupSelectedGroup;
        if (!groupCode || groupCode === '__products_manager__') {
            Swal.fire('خطا', 'ابتدا یک گروه را انتخاب کنید', 'error');
            return;
        }
        const tbody = document.getElementById('lookup-items-table');
        if (!tbody) return;
        const parentOptions = groupCode === 'lead_source_details' ? this.getLookupOptions('lead_sources') : [];
        const parentSelect = parentOptions.length ? `
            <select class="input bg-gray-800 text-xs" name="parent_value">
                <option value="">-</option>
                ${parentOptions.map(opt => `<option value="${this.escapeHtml(opt.value)}">${this.escapeHtml(opt.label || opt.value)}</option>`).join('')}
            </select>
        ` : '<span class="text-xs text-gray-500">-</span>';
        const row = document.createElement('tr');
        row.dataset.id = '';
        row.dataset.group = groupCode;
        const isAllowedServicesGroup = groupCode === 'allowed_services';
        const isDestinationBankGroup = groupCode === 'payment_destination_banks';
        const isPipelineStageGroup = groupCode === 'pipeline_stages';
        row.innerHTML = `
            <td class="p-3"><input class="input bg-gray-800 text-xs" name="title" value=""></td>
            <td class="p-3"><input class="input bg-gray-800 text-xs" name="value" value=""></td>
            <td class="p-3"><input class="input bg-gray-800 text-xs" name="code" value=""></td>
            <td class="p-3"><input class="input bg-gray-800 text-[10px] font-mono ${isDestinationBankGroup ? 'opacity-60 cursor-not-allowed' : ''}" name="meta_json" value="" ${isDestinationBankGroup ? 'readonly title="برای بانک‌های مقصد از ستون‌های اختصاصی استفاده کنید"' : ''}></td>
            <td class="p-3 lookup-col-service-price ${isAllowedServicesGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs money-input ${isAllowedServicesGroup ? '' : 'opacity-60'}" name="service_price" value="" ${isAllowedServicesGroup ? 'placeholder="قیمت خدمت"' : 'placeholder=\"-\" disabled'}></td>
            <td class="p-3 lookup-col-service-price-list-code ${isAllowedServicesGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ${isAllowedServicesGroup ? '' : 'opacity-60'}" name="service_price_list_code" value="" ${isAllowedServicesGroup ? 'placeholder="کد لیست قیمت"' : 'placeholder=\"-\" disabled'}></td>
            <td class="p-3 lookup-col-service-products ${isAllowedServicesGroup ? '' : 'hidden'}">${isAllowedServicesGroup ? this.buildProductsMultiSelectHtml([]) : '<span class="text-xs text-gray-500">-</span>'}</td>
            <td class="p-3 lookup-col-destination-bank-name ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_bank_name" value="" ${isDestinationBankGroup ? 'placeholder="مثال: تجارت"' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-account-type ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_account_type" value="" ${isDestinationBankGroup ? 'placeholder="جاری / پس انداز"' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-card ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_card" value="" ${isDestinationBankGroup ? 'placeholder="شماره کارت"' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-account ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_account" value="" ${isDestinationBankGroup ? 'placeholder="شماره حساب"' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-sheba ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_sheba" value="" ${isDestinationBankGroup ? 'placeholder="IR..."' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-owner ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs" name="destination_owner" value="" ${isDestinationBankGroup ? 'placeholder="نام صاحب حساب"' : 'disabled'}></td>
            <td class="p-3 lookup-col-destination-tafsili ${isDestinationBankGroup ? '' : 'hidden'}"><input class="input bg-gray-800 text-xs ltr" name="destination_tafsili_code" value="" ${isDestinationBankGroup ? 'placeholder="مثال: 101"' : 'disabled'}></td>
            <td class="p-3 lookup-col-stage-disabled-users ${isPipelineStageGroup ? '' : 'hidden'}">${isPipelineStageGroup ? this.buildLookupStageDisabledUsersMultiSelectHtml([]) : '<span class="text-xs text-gray-500">-</span>'}</td>
            <td class="p-3">${parentSelect}</td>
            <td class="p-3"><input class="input bg-gray-800 text-xs" name="sort_order" value="0" type="number"></td>
            <td class="p-3 text-center"><input type="checkbox" name="is_active" class="accent-yellow-500" checked></td>
            <td class="p-3 text-center"><input type="checkbox" name="is_visible" class="accent-cyan-500" checked></td>
            <td class="p-3 text-center"><span class="text-[10px] text-gray-500">جدید</span></td>
            <td class="p-3">
                <div class="flex gap-2">
                    <button type="button" class="px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-xs" onclick="app.saveLookupRow(this)">Save</button>
                    <button type="button" class="px-3 py-1 rounded bg-red-600 hover:bg-red-500 text-xs" onclick="app.deleteLookupRow(this)">Delete</button>
                </div>
            </td>
        `;
        tbody.appendChild(row);
        this.toggleLookupServiceSpecificColumns(isAllowedServicesGroup);
        this.toggleLookupDestinationBankColumns(isDestinationBankGroup);
        this.toggleLookupPipelineStageUserColumns(isPipelineStageGroup);
    },

    async saveLookupRow(button) {
        const row = button.closest('tr');
        if (!row) return;
        const groupCode = row.dataset.group;
        const id = row.dataset.id || '';
        const wasNewRow = !id;
        const title = row.querySelector('[name="title"]')?.value?.trim() || '';
        const value = row.querySelector('[name="value"]')?.value?.trim() || '';
        const code = row.querySelector('[name="code"]')?.value?.trim() || '';
        const metaJson = row.querySelector('[name="meta_json"]')?.value?.trim() || '';
        const servicePriceRaw = row.querySelector('[name="service_price"]')?.value?.trim() || '';
        const servicePriceListCodeRaw = row.querySelector('[name="service_price_list_code"]')?.value?.trim() || '';
        const linkedProductIds = Array.from(row.querySelector('[name="service_linked_product_ids"]')?.selectedOptions || []).map(opt => opt.value);
        const destinationBankNameRaw = row.querySelector('[name="destination_bank_name"]')?.value?.trim() || '';
        const destinationAccountTypeRaw = row.querySelector('[name="destination_account_type"]')?.value?.trim() || '';
        const destinationCardRaw = row.querySelector('[name="destination_card"]')?.value?.trim() || '';
        const destinationAccountRaw = row.querySelector('[name="destination_account"]')?.value?.trim() || '';
        const destinationShebaRaw = row.querySelector('[name="destination_sheba"]')?.value?.trim() || '';
        const destinationOwnerRaw = row.querySelector('[name="destination_owner"]')?.value?.trim() || '';
        const destinationTafsiliRaw = row.querySelector('[name="destination_tafsili_code"]')?.value?.trim() || '';
        const disabledStageUserIds = Array.from(row.querySelector('[name="stage_disabled_user_ids"]')?.selectedOptions || []).map(opt => String(opt.value || '').trim()).filter(Boolean);
        const sortOrder = row.querySelector('[name="sort_order"]')?.value || '0';
        const isActive = row.querySelector('[name="is_active"]')?.checked ? '1' : '0';
        const isVisible = row.querySelector('[name="is_visible"]')?.checked ? '1' : '0';
        const parentValue = row.querySelector('[name="parent_value"]')?.value || '';

        if (!title) {
            Swal.fire('خطا', 'عنوان باید وارد شود', 'error');
            return;
        }
        // For "structured" groups the friendly columns are authoritative and meta_json is
        // rebuilt from them on save, so a malformed/raw meta_json must not block the save.
        const structuredMetaGroup = (groupCode === 'allowed_services' || groupCode === 'payment_destination_banks' || groupCode === 'pipeline_stages');
        let parsedMetaJson = null;
        if (metaJson) {
            try {
                parsedMetaJson = JSON.parse(metaJson);
            } catch (e) {
                if (!structuredMetaGroup) {
                    Swal.fire('خطا', 'فرمت JSON معتبر نیست', 'error');
                    return;
                }
                parsedMetaJson = null; // discard the unparseable base; columns below rebuild it
            }
        }
        let finalMetaJson = metaJson;
        if (groupCode === 'allowed_services') {
            let metaObject = {};
            if (parsedMetaJson !== null) {
                if (!parsedMetaJson || typeof parsedMetaJson !== 'object' || Array.isArray(parsedMetaJson)) {
                    Swal.fire('خطا', 'برای خدمات، Meta JSON بای�������� یک شیء JSON باشد', 'error');
                    return;
                }
                metaObject = { ...parsedMetaJson };
            }

            const priceNumber = parseMoneyValue(servicePriceRaw);
            if (servicePriceRaw !== '') {
                if (!Number.isFinite(priceNumber) || priceNumber <= 0) {
                    Swal.fire('خطا', 'قیمت خدمت باید عددی بزرگ‌تر از صفر باشد', 'error');
                    return;
                }
                metaObject.price = priceNumber;
            } else {
                delete metaObject.price;
            }
            if (servicePriceListCodeRaw !== '') {
                metaObject.price_list_code = servicePriceListCodeRaw;
            } else {
                delete metaObject.price_list_code;
            }
            finalMetaJson = Object.keys(metaObject).length ? JSON.stringify(metaObject) : '';
        }
        if (groupCode === 'payment_destination_banks') {
            let metaObject = {};
            if (parsedMetaJson !== null) {
                if (!parsedMetaJson || typeof parsedMetaJson !== 'object' || Array.isArray(parsedMetaJson)) {
                    Swal.fire('خطا', 'برای بانک مقصد، Meta JSON باید یک شیء JSON باشد', 'error');
                    return;
                }
                metaObject = { ...parsedMetaJson };
            }
            const setOrDelete = (key, val) => {
                if (val !== '') metaObject[key] = val;
                else delete metaObject[key];
            };
            setOrDelete('bank_name', destinationBankNameRaw);
            setOrDelete('account_type', destinationAccountTypeRaw);
            setOrDelete('card_number', destinationCardRaw.replace(/\D/g, ''));
            setOrDelete('account_number', destinationAccountRaw.replace(/\s+/g, ''));
            setOrDelete('sheba', destinationShebaRaw.replace(/\s+/g, '').toUpperCase());
            setOrDelete('account_owner', destinationOwnerRaw);
            setOrDelete('tafsili_code', destinationTafsiliRaw);
            finalMetaJson = Object.keys(metaObject).length ? JSON.stringify(metaObject) : '';
        }
        if (groupCode === 'pipeline_stages') {
            let metaObject = {};
            if (parsedMetaJson !== null) {
                if (!parsedMetaJson || typeof parsedMetaJson !== 'object' || Array.isArray(parsedMetaJson)) {
                    Swal.fire('خطا', 'برای مرحله کاریز، Meta JSON باید یک شیء JSON باشد', 'error');
                    return;
                }
                metaObject = { ...parsedMetaJson };
            }
            if (disabledStageUserIds.length) {
                metaObject.disabled_user_didar_ids = Array.from(new Set(disabledStageUserIds));
            } else {
                delete metaObject.disabled_user_didar_ids;
            }
            finalMetaJson = Object.keys(metaObject).length ? JSON.stringify(metaObject) : '';
        }

        const res = await this.req('save_lookup_item', {
            id,
            group_code: groupCode,
            title,
            value,
            code,
            meta_json: finalMetaJson,
            sort_order: sortOrder,
            is_active: isActive,
            is_visible: isVisible,
            parent_value: parentValue
        });
        if (res.status === 'success') {
            const savedId = String(res.id || row.dataset.id || '');
            let effectiveLinkedProductIds = [...linkedProductIds];
            if (res.id) {
                row.dataset.id = res.id;
            }
            if (groupCode === 'allowed_services' && savedId && wasNewRow && effectiveLinkedProductIds.length === 0) {
                const autoProductName = (value || title || '').trim();
                if (autoProductName) {
                    const createProductRes = await this.req('save_product', {
                        name: autoProductName,
                        service_lookup_item_id: savedId
                    });
                    if (createProductRes.status !== 'success') {
                        Swal.fire('خطا', createProductRes.message || 'ساخت خودکار محصول برای خدمت ناموفق بود', 'error');
                        return;
                    }
                    const autoProductId = String(createProductRes.id || '').trim();
                    if (autoProductId) {
                        effectiveLinkedProductIds = [autoProductId];
                    }
                }
            }
            if (groupCode === 'allowed_services' && savedId) {
                const linkRes = await this.req('save_service_product_links', {
                    service_lookup_item_id: savedId,
                    product_ids: effectiveLinkedProductIds
                });
                if (linkRes.status !== 'success') {
                    Swal.fire('خطا', linkRes.message || 'ذخیره لینک محصولات ناموفق بود', 'error');
                    return;
                }
            }
            Swal.fire('انجام شد', 'ذخیره شد', 'success');
            await this.loadLookups();
            await this.loadProducts();
            await this.loadLookupItems(this.state.lookupSelectedGroup);
        } else {
            Swal.fire('خطا', res.message || 'خطا در ذخیره', 'error');
        }
    },

    async deleteLookupRow(button) {
        const row = button.closest('tr');
        if (!row) return;
        const id = row.dataset.id || '';
        if (!id) {
            row.remove();
            return;
        }
        if (String(row.dataset.isSystem || '0') === '1') {
            Swal.fire('قابل حذف نیست', 'این گزینه سیستمی است. برای خارج کردن آن از فرم‌ها از غیرفعال یا عدم نمایش استفاده کنید.', 'warning');
            return;
        }
        const usageTotal = Number(row.dataset.usageTotal || 0);
        const usageDetails = row.dataset.usageDetails || '';
        const result = await Swal.fire({
            title: 'حذف گزینه؟',
            html: usageTotal > 0
                ? `این گزینه حذف خواهد شد.<br><span class="text-amber-300 text-xs">استفاده شناخته‌شده: ${this.escapeHtml(String(usageTotal))}${usageDetails ? ` (${this.escapeHtml(usageDetails)})` : ''}</span>`
                : 'این گزینه حذف خواهد شد',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف',
            cancelButtonText: 'انصراف'
        });
        if (!result.isConfirmed) return;
        if ((row.dataset.group || '') === 'allowed_services') {
            const clearLinksRes = await this.req('save_service_product_links', {
                service_lookup_item_id: id,
                product_ids: []
            });
            if (clearLinksRes.status !== 'success') {
                Swal.fire('خطا', clearLinksRes.message || 'خطا در پاکسازی لینک محصولات', 'error');
                return;
            }
        }
        const res = await this.req('delete_lookup_item', { id });
        if (res.status === 'success') {
            row.remove();
            await this.loadLookups();
            await this.loadProducts();
            await this.loadLookupItems(this.state.lookupSelectedGroup);
        } else {
            Swal.fire('خطا', res.message || 'خطا در حذف', 'error');
        }
    },
    // UI Helpers
    setView(id, options = {}) {
        const opts = options || {};
        const target = this.normalizeView(id);
        const currentActive = document.querySelector('.sidebar-item.active');
        const fromView = currentActive ? currentActive.id.replace('menu-', '') : '';
        this.logTelemetry('route_transition', { from: fromView || '', to: target || '', source: String(opts.source || 'setView') }, 'debug');

        if (!this.canAccessView(target, { showWarning: !opts.silentDenied })) {
            return false;
        }

	        document.querySelectorAll('.view-section').forEach(e => {
                e.classList.add('hidden');
                e.classList.remove('active');
            });
        this.cleanupPersianDatepickers(document, { removeOrphans: true });
	        const viewEl = document.getElementById('view-'+target);
	        if (!viewEl) {
                this.logTelemetry('route_missing_view', { view: target }, 'warn');
                return false;
            }
            viewEl.classList.remove('hidden');
            viewEl.classList.add('active');
        
        document.querySelectorAll('.sidebar-item').forEach(e => e.classList.remove('active', 'text-yellow-500'));
        const menuEl = document.getElementById('menu-'+target);
        if(menuEl) menuEl.classList.add('active', 'text-yellow-500');
        
	        const titles = {
	                    'dashboard': 'داشبورد',
                        'performance-dashboard': 'داشبورد عملکرد',
                        'kpi-dashboard': 'داشبورد KPI',
	                    'leads': 'لیدها',
	                    'operation-manager': 'مدیریت عملیات',
	                    'blataklif-leads': 'لیدهای بلاتکلیف',
	                    'deals': 'معاملات',
	                    'legacy-deals': 'معاملات گذشته CRM خارجی',
	                    'referrals': 'ارجاعات',
	                    'persons': 'اشخاص',
	                    'settings': 'تنظیمات',
	                    'exports': 'خروجی‌ها',
	                    'audit-logs': 'لاگ‌ها',
                    'phone-logs': 'لاگ تماس‌ها',
                    'users': 'کاربران',
                    'maintenance': 'نگهداری دیتابیس',
                    'commissions': 'پورسانت فروش',
                    'finance-inbox': 'کارتابل مالی پورسانت',
                    'commission-calc': 'محاسبه پورسانت',
                    'lookups': 'مدیریت لیست‌ها',
                    'reminders': 'یادآوری‌ها',
                    'customer-access': 'درخواست‌های دسترسی مشتری'
                };
	        const titleEl = document.getElementById('page-title');
	        if(titleEl) titleEl.textContent = titles[target] || target;
	        
	        if(target === 'dashboard') this.loadDashboard();
        if(target === 'performance-dashboard') this.loadPerformanceDashboard({ useState: true });
        if(target === 'kpi-dashboard') {
            setTimeout(() => this.loadKPIDashboard({ useState: true }), 0);
        }
            if(target === 'reminders') this.loadReminders();
            if(target === 'customer-access') {
                this.state.accessInboxTab = 'pending';
                this.loadAccessRequestInbox();
            }
	        if(target === 'leads') {
	            this.loadProducts();
	            // Skip the auto-load when a caller (e.g. goToLeadsWithFilter) sets filters and
	            // loads once itself — avoids a double-load race that renders stale results.
	            if (!opts.skipInitialLoad) this.loadLeads();
	            this.initLeadFiltersDatePickers();
	        }
            if (target === 'operation-manager') {
                this.loadOperationManager();
            }
	        if (target === 'blataklif-leads') {
	            this.loadBlataklifLeads(1);
	        }
	        if(target === 'deals') {
	            this.loadProducts(); // ensure products loaded for deal modal
	            // Skip the auto-load when a caller (e.g. goToDealsWithFilter) sets filters and
	            // loads once itself — avoids a double-load race that renders stale results.
	            if (!opts.skipInitialLoad) this.loadDeals();
	            this.initLeadFiltersDatePickers();
	        }
        if (target === 'legacy-deals') {
            this.loadLegacyDeals();
        }
	        if(target === 'referrals' && this.canCurrentUserCapabilityClient('referrals.view')) this.loadReferrals();
	        if(target === 'persons') this.loadPersons();
	        if(target === 'settings') this.loadSettings();
	        if(target === 'users') this.loadUsers();
            if(target === 'maintenance') this.loadMaintenanceDashboard();
            if(target === 'commissions') {
                Swal.fire({
                    icon: 'info',
                    title: 'پورسانت فروش غیرفعال شد',
                    text: 'برای محاسبه و بررسی پورسانت از ماژول جدید «محاسبه پورسانت» استفاده کنید.',
                    confirmButtonText: 'رفتن به محاسبه پورسانت'
                });
                return this.navigateToView('commission-calc', { source: 'legacy_commission_redirect' });
            }
            if(target === 'finance-inbox') {
                setTimeout(() => this.loadFinanceChequeInbox(), 0);
            }
            if(target === 'commission-calc') {
                setTimeout(() => this.loadCommissionCalc(), 0);
            }
            if(target === 'lookups' && this.canCurrentUserCapabilityClient('lookups.view')) this.loadLookupAdmin(true);
	        if(target === 'exports' && (this.canCurrentUserCapabilityClient('menu.exports') || this.canCurrentUserCapabilityClient('export.catalog'))) this.loadExportCatalog();
	        if(target === 'cheques' && this.canCurrentUserCapabilityClient('menu.cheques')) this.switchFinancialTab(this.resolveAllowedFinancialTab(this.state.activeFinancialTab));
	        if(target === 'audit-logs' && this.canCurrentUserCapabilityClient('audit_logs.view')) this.loadAuditLogs(true);
	        if(target === 'phone-logs' && this.isAdmin()) this.loadPhoneLogs();

            if (this.featureFlags.historyV1 && !this._isApplyingHistoryState) {
                setTimeout(() => this.replaceCurrentHistoryUiState('set_view_render'), 0);
            }
            return true;
	    },

    // ===== Commission Calc module (محاسبه پورسانت) — additive, reuses existing endpoints =====
    loadCommissionCalc() {
        this._ccState = this._ccState || { pane: 'ops', sub: 'rules' };
        this.ccInstallDrilldownClickHandlers();
        this.ccInitDateRange();
        this.ccSwitchPane(this._ccState.pane || 'ops');
    },
    ccSwitchPane(pane) {
        this._ccState = this._ccState || {};
        this._ccState.pane = pane;
        const ops = document.getElementById('cc-pane-ops');
        const set = document.getElementById('cc-pane-settings');
        const bOps = document.getElementById('cc-seg-ops');
        const bSet = document.getElementById('cc-seg-settings');
        if (ops) ops.classList.toggle('hidden', pane !== 'ops');
        if (set) set.classList.toggle('hidden', pane !== 'settings');
        if (bOps) bOps.classList.toggle('active', pane === 'ops');
        if (bSet) bSet.classList.toggle('active', pane === 'settings');
        if (pane === 'ops') {
            this.ccLoadOpsKpis();
            this.ccLoadQueue();
            this.ccLoadTrend();
        } else {
            this.ccSwitchSub(this._ccState.sub || 'rules');
        }
    },
    ccSwitchSub(sub) {
        this._ccState = this._ccState || {};
        this._ccState.sub = sub;
        ['rules', 'teams', 'assign', 'history'].forEach(s => {
            const pane = document.getElementById('cc-sub-' + s);
            const tab = document.getElementById('cc-sub-tab-' + s);
            if (pane) pane.classList.toggle('hidden', s !== sub);
            if (tab) tab.classList.toggle('active', s === sub);
        });
        if (sub === 'rules') this.ccLoadRules();
        if (sub === 'teams') this.ccLoadTeams();
        if (sub === 'assign') this.ccLoadAssignments();
        if (sub === 'history') this.ccLoadHistory();
    },
    async ccLoadHistory() {
        // Read-only timeline. Reuse the data already fetched for the teams/rules views;
        // only fetch if it isn't loaded yet.
        if (!Array.isArray(this._ccHierAssigns) || !this._ccUsersById) {
            try { await this.ccLoadTeams(); } catch (e) {}
        }
        this.ccRenderHistory();
    },
    ccRenderHistory() {
        const box = document.getElementById('cc-sub-history');
        if (!box) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const hasVal = (v) => v !== null && v !== undefined && String(v).trim() !== '';
        const nameById = (id) => {
            const u = this._ccUsersById && this._ccUsersById[Number(id)];
            return u ? this.ccUserName(u) : ('کاربر #' + id);
        };
        const HIST_STYLE = '<style>'
            + '.cc-hist-card{border:1px solid rgba(148,163,184,.12);border-radius:18px;overflow:hidden;background:linear-gradient(180deg,rgba(30,41,59,.45),rgba(15,23,42,.35));margin-bottom:18px;box-shadow:0 10px 34px rgba(0,0,0,.28)}'
            + '.cc-hist-head{display:flex;align-items:center;gap:11px;padding:15px 20px;border-bottom:1px solid rgba(148,163,184,.1);background:rgba(251,191,36,.05)}'
            + '.cc-hist-head .ic{width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:rgba(251,191,36,.14);color:#fbbf24;font-size:14px}'
            + '.cc-hist-head h4{color:#fcd34d;font-weight:700;font-size:14px;margin:0}'
            + '.cc-hist-head small{color:#b4c0ce;font-size:11px}'
            + '.cc-hist-wrap-tbl{overflow-x:auto}'
            + '.cc-hist-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;text-align:right}'
            + '.cc-hist-table th{padding:11px 16px;color:#b4c0ce;font-weight:600;font-size:11px;white-space:nowrap;border-bottom:1px solid rgba(148,163,184,.12)}'
            + '.cc-hist-table td{padding:11px 16px;border-bottom:1px solid rgba(148,163,184,.06);color:#e2e8f0;vertical-align:middle;white-space:nowrap}'
            + '.cc-hist-table td.cc-hist-wrap{white-space:normal}'
            + '.cc-hist-table tbody tr{transition:background .15s}.cc-hist-table tbody tr:hover{background:rgba(148,163,184,.06)}'
            + '.cc-hist-table tbody tr:last-child td{border-bottom:0}'
            + '.cc-hist-user{display:flex;align-items:center;gap:10px}'
            + '.cc-hist-av{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex:0 0 auto;box-shadow:inset 0 0 0 1px rgba(255,255,255,.12)}'
            + '.cc-hist-uname{font-weight:700;color:#f1f5f9;line-height:1.5}'
            + '.cc-hist-badge{display:inline-flex;align-items:center;gap:5px;padding:3px 11px;border-radius:999px;font-size:11px;font-weight:600;white-space:nowrap;margin-top:3px}'
            + '.cc-hist-badge i{font-size:10px}'
            + '.cc-hist-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;font-size:11px;background:rgba(148,163,184,.12);color:#dbe2ea;border:1px solid rgba(148,163,184,.16)}'
            + '.cc-hist-chip .av{width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#fff}'
            + '.cc-hist-chips{display:flex;flex-wrap:wrap;gap:6px;max-width:360px}'
            + '.cc-hist-rate{font-weight:700;color:#fcd34d}.cc-hist-rate.muted{color:#9aa7b8;font-weight:500}'
            + '.cc-hist-date{color:#d3dbe4;white-space:nowrap}.cc-hist-date i{color:#94a3b8;margin-left:6px;font-size:10px}'
            + '.cc-hist-muted{color:#9aa7b8}'
            + '.cc-hist-dot{width:6px;height:6px;border-radius:50%;display:inline-block}'
            + '</style>';
        const TIERS = {
            manager: { label: 'مدیر', color: '#d8b4fe', bg: 'rgba(168,85,247,.16)', av: 'linear-gradient(135deg,#a855f7,#7c3aed)', icon: 'fa-crown' },
            supervisor: { label: 'سرپرست', color: '#5eead4', bg: 'rgba(20,184,166,.16)', av: 'linear-gradient(135deg,#14b8a6,#0d9488)', icon: 'fa-user-shield' },
            agent: { label: 'کارشناس', color: '#93c5fd', bg: 'rgba(59,130,246,.16)', av: 'linear-gradient(135deg,#3b82f6,#2563eb)', icon: 'fa-user' }
        };
        const firstChar = (name) => { const s = String(name || '').trim(); return s ? s.charAt(0) : '؟'; };
        const tierBadge = (t) => '<span class="cc-hist-badge" style="background:' + t.bg + ';color:' + t.color + '"><i class="fa-solid ' + t.icon + '"></i>' + t.label + '</span>';
        const avatar = (name, t) => '<span class="cc-hist-av" style="background:' + t.av + ';color:#fff">' + esc(firstChar(name)) + '</span>';
        const tierFor = (subs) => subs.some(s => s.kind === 'مدیریتی') ? TIERS.manager : (subs.some(s => s.kind === 'سرپرستی') ? TIERS.supervisor : TIERS.agent);
        const activeBadge = '<span class="cc-hist-badge" style="margin-top:0;background:rgba(34,197,94,.16);color:#86efac"><span class="cc-hist-dot" style="background:#4ade80"></span>فعال</span>';
        const endedBadge = (label) => '<span class="cc-hist-badge" style="margin-top:0;background:rgba(100,116,139,.18);color:#94a3b8"><span class="cc-hist-dot" style="background:#94a3b8"></span>' + esc(label) + '</span>';
        const th = (t) => '<th>' + esc(t) + '</th>';
        const td = (html) => '<td>' + html + '</td>';
        // Rate a person earns in their own role: supervisor rate from their team, or
        // manager rate from their supervisors. (Looked up from active assignment rows.)
        const distinctRates = (predicate, field) => {
            const set = {};
            (this._ccHierAssigns || []).forEach(a => {
                if (!a.effective_to && predicate(a) && hasVal(a[field])) set[String(a[field])] = true;
            });
            return Object.keys(set);
        };
        const rateText = (label, rates) => label + ' ' + (rates.length === 1 ? this.ccFormatPercentValue(rates[0]) + '٪' : 'چند نرخ');
        // A plain salesperson's own (direct, rule-based) commission, for rows that are
        // neither a supervisor nor a manager.
        const ruleById = {};
        (this._ccRules || []).forEach(r => { ruleById[Number(r.id)] = r; });
        const directRateLabel = (uid) => {
            const seen = {};
            const labels = [];
            (this._ccRuleAssigns || []).forEach(a => {
                if (a.disabled_at || Number(a.salesperson_user_id || 0) !== Number(uid)) return;
                const r = ruleById[Number(a.rule_id)];
                if (!r || seen[Number(r.id)]) return;
                seen[Number(r.id)] = true;
                if (String(r.rule_type || '').toUpperCase() === 'FIXED_RULE') {
                    const rate = Number(r.reward_percentage || r.base_rate_percent || 0);
                    labels.push(rate > 0 ? (this.ccFormatPercentValue(rate) + '٪ ثابت') : (r.title || 'ثابت'));
                } else {
                    labels.push((r.title || 'پلکانی') + ' (پلکانی)');
                }
            });
            return labels.join(' · ');
        };

        // --- Hierarchy / teams timeline (who reports to whom, their team, rates & dates) ---
        const tdWrap = (html) => '<td class="cc-hist-wrap">' + html + '</td>';
        // Direct subordinates of a person (active assignments where they are the parent).
        const subordinatesOf = (pid) => {
            const seen = {}; const subs = [];
            (this._ccHierAssigns || []).forEach(a => {
                if (a.effective_to) return;
                const asSup = Number(a.supervisor_user_id || 0) === Number(pid);
                // A direct managerial report is a supervisor reporting straight to this
                // manager (no supervisor of their own). Rows where this person is only the
                // grand-manager above an intermediate supervisor are NOT direct reports.
                const asMgr = Number(a.manager_user_id || 0) === Number(pid) && Number(a.supervisor_user_id || 0) === 0;
                if (!asSup && !asMgr) return;
                const uid = Number(a.user_id || 0);
                if (!uid || seen[uid]) return;
                seen[uid] = true;
                subs.push({ uid: uid, name: a.user_name || nameById(uid), kind: asSup ? 'سرپرستی' : 'مدیریتی' });
            });
            return subs;
        };
        const subsLabel = (subs) => {
            if (!subs.length) return '<span class="cc-hist-muted">—</span>';
            return '<div class="cc-hist-chips">' + subs.map(s => {
                const isMgr = s.kind === 'مدیریتی';
                const col = isMgr ? '#d8b4fe' : '#5eead4';
                const bg = isMgr ? 'rgba(168,85,247,.14)' : 'rgba(20,184,166,.14)';
                return '<span class="cc-hist-chip" style="color:' + col + ';background:' + bg + ';border-color:' + bg + '">' + esc(s.name) + '</span>';
            }).join('') + '</div>';
        };
        const roleRate = (userId, subs) => {
            const supEarned = distinctRates(x => Number(x.supervisor_user_id || 0) === Number(userId), 'supervisor_commission_rate_percent');
            const mgrEarned = distinctRates(x => Number(x.manager_user_id || 0) === Number(userId) && Number(x.supervisor_user_id || 0) === 0, 'manager_commission_rate_percent');
            const parts = [];
            if (subs.some(s => s.kind === 'سرپرستی')) parts.push(supEarned.length ? rateText('سهم سرپرست', supEarned) : 'سهم سرپرست —');
            if (subs.some(s => s.kind === 'مدیریتی')) parts.push(mgrEarned.length ? rateText('سهم مدیر', mgrEarned) : 'سهم مدیر —');
            if (!parts.length) { const dr = directRateLabel(userId); parts.push(dr ? ('سهم کارشناس: ' + dr) : 'سهم کارشناس (مستقیم)'); }
            return parts.join(' · ');
        };
        const makeRow = (userId, userName, role, parentText, rangeText, statusHtml) => {
            const subs = subordinatesOf(userId);
            const tier = tierFor(subs);
            const userCell = '<div><div class="cc-hist-uname">' + esc(userName) + '</div>' + tierBadge(tier) + '</div>';
            const parentCell = (parentText === '—' || !parentText) ? '<span class="cc-hist-muted">—</span>' : ('<span class="cc-hist-chip"><i class="fa-solid fa-arrow-up-long" style="font-size:9px;color:#64748b"></i>' + esc(parentText) + '</span>');
            const rateRaw = roleRate(userId, subs);
            const rateCell = '<span class="cc-hist-rate' + (/—/.test(rateRaw) ? ' muted' : '') + '">' + esc(rateRaw) + '</span>';
            const dateCell = '<span class="cc-hist-date"><i class="fa-regular fa-calendar"></i>' + esc(rangeText) + '</span>';
            return '<tr>' + td(userCell) + td('<span class="cc-hist-muted">' + esc(role) + '</span>') + td(parentCell) + tdWrap(subsLabel(subs)) + td(rateCell) + td(dateCell) + td(statusHtml) + '</tr>';
        };

        // Top-level people (have reports but aren't anyone's subordinate, e.g. the top
        // manager) don't have a membership row of their own — synthesize one so they appear.
        const activeMembers = {}; const parentsActive = {};
        (this._ccHierAssigns || []).forEach(a => {
            if (a.effective_to) return;
            if (Number(a.supervisor_user_id || 0) || Number(a.manager_user_id || 0)) activeMembers[Number(a.user_id)] = true;
            if (Number(a.supervisor_user_id || 0)) parentsActive[Number(a.supervisor_user_id)] = true;
            if (Number(a.manager_user_id || 0)) parentsActive[Number(a.manager_user_id)] = true;
        });
        const topRows = Object.keys(parentsActive).map(Number).filter(pid => !activeMembers[pid]).map(pid => {
            const subs = subordinatesOf(pid);
            const minFrom = (this._ccHierAssigns || [])
                .filter(a => !a.effective_to && (Number(a.supervisor_user_id || 0) === pid || Number(a.manager_user_id || 0) === pid))
                .map(a => a.effective_from).filter(Boolean).sort()[0] || '';
            const role = subs.some(s => s.kind === 'مدیریتی') ? 'مدیر (بالاترین سطح)' : 'سرپرست (بالاترین سطح)';
            const range = minFrom ? this.ccDateRangeLabel(minFrom, '', 'تاکنون') : '—';
            return makeRow(pid, nameById(pid), role, '—', range, activeBadge);
        }).join('');

        const hier = (this._ccHierAssigns || [])
            .filter(a => Number(a.supervisor_user_id || 0) || Number(a.manager_user_id || 0))
            .slice()
            .sort((a, b) => String(b.effective_from || '').localeCompare(String(a.effective_from || '')) || (Number(b.id || 0) - Number(a.id || 0)));
        const hierRows = (topRows + hier.map(a => {
            const isSup = !!Number(a.supervisor_user_id || 0);
            const role = isSup ? 'زیرمجموعهٔ سرپرست' : 'زیرمجموعهٔ مدیر';
            const parent = isSup ? (a.supervisor_name || nameById(a.supervisor_user_id)) : (a.manager_name || nameById(a.manager_user_id));
            const range = this.ccDateRangeLabel(a.effective_from || '', a.effective_to || '', 'تاکنون');
            const status = a.effective_to ? endedBadge('پایان‌یافته') : activeBadge;
            return makeRow(a.user_id, a.user_name || nameById(a.user_id), role, parent, range, status);
        }).join('')) || '<tr><td colspan="7" class="px-3 py-6 text-center text-gray-500">موردی ثبت نشده است.</td></tr>';

        // --- Commission rule assignment timeline ---
        const ruleTitleById = {};
        (this._ccRules || []).forEach(r => { ruleTitleById[Number(r.id)] = r.title; });
        const asn = (this._ccRuleAssigns || [])
            .slice()
            .sort((a, b) => String(b.assigned_at || b.valid_from || '').localeCompare(String(a.assigned_at || a.valid_from || '')) || (Number(b.id || 0) - Number(a.id || 0)));
        const emptyRow = (n) => '<tr><td colspan="' + n + '" style="padding:26px;text-align:center;color:#94a3b8">موردی ثبت نشده است.</td></tr>';
        const asnRows = asn.map(a => {
            const rule = a.rule_title || ruleTitleById[Number(a.rule_id)] || ('قانون #' + (a.rule_id || '-'));
            const ruleObj = ruleById[Number(a.rule_id)];
            const ruleType = ruleObj ? (String(ruleObj.rule_type || '').toUpperCase() === 'FIXED_RULE' ? 'ثابت' : 'پلکانی') : '';
            const who = a.salesperson_name || (Number(a.salesperson_user_id || 0) ? nameById(a.salesperson_user_id) : 'همه کارشناسان');
            const isTeam = String(a.source_type || '').toUpperCase() === 'TEAM';
            const teamName = a.source_team_supervisor_name || (Number(a.source_team_supervisor_user_id || 0) ? nameById(a.source_team_supervisor_user_id) : '');
            const srcChip = isTeam
                ? '<span class="cc-hist-chip"><i class="fa-solid fa-users" style="font-size:9px;color:#5eead4"></i>تیم' + (teamName ? ' ' + esc(teamName) : '') + '</span>'
                : '<span class="cc-hist-chip"><i class="fa-solid fa-user" style="font-size:9px;color:#93c5fd"></i>انفرادی</span>';
            const ruleCell = '<div><div class="cc-hist-uname">' + esc(rule) + '</div>' + (ruleType ? '<span class="cc-hist-badge" style="background:rgba(245,158,11,.14);color:#fcd34d">' + esc(ruleType) + '</span>' : '') + '</div>';
            const assignedAt = a.assigned_at ? this.ccDateLabel(a.assigned_at) : '—';
            const validity = this.ccDateRangeLabel(a.valid_from || '', a.valid_to || '', 'بدون پایان');
            const status = a.disabled_at ? endedBadge('غیرفعال') : activeBadge;
            return '<tr>' + td(ruleCell) + td('<span class="cc-hist-uname" style="font-weight:600">' + esc(who) + '</span>') + td(srcChip) + td('<span class="cc-hist-date"><i class="fa-regular fa-calendar"></i>' + esc(assignedAt) + '</span>') + td('<span class="cc-hist-date">' + esc(validity) + '</span>') + td(status) + '</tr>';
        }).join('') || emptyRow(6);

        box.innerHTML = HIST_STYLE
            + '<div class="cc-team-note mb-4">این بخش فقط برای نمایش تاریخچه است و هیچ تغییری روی پورسانت‌ها ایجاد نمی‌کند.</div>'
            + '<div class="cc-hist-card">'
            + '<div class="cc-hist-head"><span class="ic"><i class="fa-solid fa-sitemap"></i></span><div><h4>تاریخچهٔ سلسله‌مراتب و تیم‌ها</h4><small>چه کسی زیرمجموعهٔ چه کسی بوده و سهم هر نقش</small></div></div>'
            + '<div class="cc-hist-wrap-tbl"><table class="cc-hist-table"><thead><tr>'
            + th('کاربر') + th('نقش') + th('سرپرست / مدیر') + th('زیرمجموعه‌ها') + th('نرخ سهم') + th('بازهٔ زمانی') + th('وضعیت')
            + '</tr></thead><tbody>' + hierRows + '</tbody></table></div>'
            + '</div>'
            + '<div class="cc-hist-card">'
            + '<div class="cc-hist-head"><span class="ic"><i class="fa-solid fa-file-invoice-dollar"></i></span><div><h4>تاریخچهٔ تخصیص قوانین پورسانت</h4><small>هر قانون به چه کسی و از چه طریق تخصیص یافته</small></div></div>'
            + '<div class="cc-hist-wrap-tbl"><table class="cc-hist-table"><thead><tr>'
            + th('قانون') + th('هدف') + th('منبع') + th('تاریخ تخصیص') + th('اعتبار') + th('وضعیت')
            + '</tr></thead><tbody>' + asnRows + '</tbody></table></div>'
            + '</div>';
    },
    ccToggleAll(box) {
        document.querySelectorAll('#cc-queue-body input.cc-q-row').forEach(c => { c.checked = box.checked; });
    },
    // --- Operations: shared fetch (single source for KPIs, queue, trend) ---
    ccInitDateRange() {
        const fromEl = document.getElementById('cc-date-from');
        const toEl = document.getElementById('cc-date-to');
        const todayG = this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10);
        const todayJ = typeof toPersianDate === 'function' ? (toPersianDate(todayG) || todayG) : todayG;
        const currentJYear = (/^\d{4}/.exec(String(todayJ)) || [String(new Date().getFullYear())])[0];
        if (fromEl && !fromEl.value) fromEl.value = currentJYear + '/01/01';
        if (toEl && !toEl.value) toEl.value = todayJ;
        if (!this._ccDatePickersReady && typeof this.initJalaliDatePickers === 'function') {
            this.initJalaliDatePickers([fromEl, toEl].filter(Boolean), { allowFuture: false, linkRange: true });
            this._ccDatePickersReady = true;
        }
    },
    ccToGregorianDate(value) {
        const raw = typeof normalizeDate === 'function' ? normalizeDate(value || '') : String(value || '').trim();
        if (!raw) return '';
        const year = Number(String(raw).split(/[/-]/)[0] || 0);
        if (year > 1600) return String(raw).replace(/\//g, '-');
        if (typeof persianToGregorian === 'function') {
            const converted = persianToGregorian(raw, '00:00');
            return converted ? String(converted).slice(0, 10) : raw;
        }
        return raw;
    },
    ccDateRange() {
        this.ccInitDateRange();
        const current = this.getCurrentMonthRangeGregorian ? this.getCurrentMonthRangeGregorian() : { date_from: new Date().toISOString().slice(0, 7) + '-01', date_to: new Date().toISOString().slice(0, 10) };
        const fromEl = document.getElementById('cc-date-from');
        const toEl = document.getElementById('cc-date-to');
        const from = this.ccToGregorianDate((fromEl?.value || '').trim()) || current.date_from;
        let to = this.ccToGregorianDate((toEl?.value || '').trim()) || (this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : current.date_to);
        if (from && to && from > to) {
            to = from;
            if (toEl) {
                toEl.value = typeof toPersianDate === 'function' ? (toPersianDate(from) || toEl.value) : toEl.value;
                toEl.dispatchEvent(new Event('input', { bubbles: true }));
                toEl.dispatchEvent(new Event('change', { bubbles: true }));
                if (typeof $ !== 'undefined' && typeof $.fn?.persianDatepicker === 'function') {
                    $(toEl).persianDatepicker({
                        minDate: from,
                        maxDate: this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : null
                    });
                }
            }
        }
        return { date_from: from, date_to: to, invalid: false };
    },
    ccMarkDateRangeInvalid(invalid) {
        ['cc-date-from', 'cc-date-to'].forEach((id) => {
            const el = document.getElementById(id);
            if (!el) return;
            el.classList.toggle('border-red-500', !!invalid);
            el.classList.toggle('ring-1', !!invalid);
            el.classList.toggle('ring-red-500', !!invalid);
        });
    },
    ccRequireValidDateRange(range, showMessage = true) {
        const invalid = !!(range && range.invalid);
        this.ccMarkDateRangeInvalid(invalid);
        if (invalid && showMessage) {
            Swal.fire('بازه تاریخ نامعتبر است', 'تاریخ شروع نباید بعد از تاریخ پایان باشد.', 'error');
        }
        return !invalid;
    },
    ccMonthsInRange(dateFrom, dateTo) {
        const months = [];
        if (!/^\d{4}-\d{2}-\d{2}$/.test(dateFrom) || !/^\d{4}-\d{2}-\d{2}$/.test(dateTo)) return [this.ccBillingMonth()];
        const cursor = new Date(Number(dateFrom.slice(0, 4)), Number(dateFrom.slice(5, 7)) - 1, 1);
        const end = new Date(Number(dateTo.slice(0, 4)), Number(dateTo.slice(5, 7)) - 1, 1);
        while (cursor <= end) {
            months.push(String(cursor.getFullYear()).padStart(4, '0') + '-' + String(cursor.getMonth() + 1).padStart(2, '0'));
            cursor.setMonth(cursor.getMonth() + 1);
        }
        return months;
    },
    ccJalaliMonthsInRange(dateFrom, dateTo) {
        const parse = (value) => {
            const m = String(value || '').match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
            return m ? { year: Number(m[1]), month: Number(m[2]) } : null;
        };
        const from = parse(toPersianDate(dateFrom));
        const to = parse(toPersianDate(dateTo));
        if (!from || !to) return this.ccMonthsInRange(dateFrom, dateTo);
        const months = [];
        let y = from.year;
        let m = from.month;
        while (y < to.year || (y === to.year && m <= to.month)) {
            months.push(`${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}`);
            m++;
            if (m > 12) {
                m = 1;
                y++;
            }
        }
        return months;
    },
    ccBillingMonth() {
        const explicit = (document.getElementById('commission-cycle-billing-month')?.value || '').trim();
        if (/^\d{4}-\d{2}$/.test(explicit)) return explicit;
        const range = this.ccDateRange ? this.ccDateRange() : null;
        const jalaliMonths = range ? this.ccJalaliMonthsInRange(range.date_from, range.date_to) : [];
        if (jalaliMonths.length === 1) return jalaliMonths[0];
        const current = this.getCurrentJalaliMonthKey ? this.getCurrentJalaliMonthKey() : '';
        return /^\d{4}-\d{2}$/.test(current) ? current : new Date().toISOString().slice(0, 7);
    },
    async ccEnsureOpsData(force) {
        if (force) { this._ccRows = null; }
        if (Array.isArray(this._ccRows)) return this._ccRows;
        if (this._ccOpsPromise) return this._ccOpsPromise;
        this._ccOpsPromise = (async () => {
            const range = this.ccDateRange();
            if (!this.ccRequireValidDateRange(range, false)) {
                this._ccRows = [];
                return [];
            }
            // Match strictly on the Jalali month(s) of the selected range. The cycle's
            // billing_month can be stored in either calendar, so normalize each to its
            // canonical Jalali month key (the same value the row displays) before comparing.
            // Mixing raw Gregorian months into the set used to leak adjacent Jalali months
            // (e.g. Ordibehesht/اردیبهشت rows appearing under a Khordad/خرداد filter).
            const months = new Set(this.ccJalaliMonthsInRange(range.date_from, range.date_to));
            const res = await this.req('get_commission_payout_queue', {
                billing_month: '',
                status: '',
                salesperson_name: '',
                amount_min: '',
                amount_max: '',
                apply_sla: '0',
                include_lines: '1'
            });
            const rows = (res && res.status === 'success' && Array.isArray(res.cycles)) ? res.cycles.filter(r => months.has(this.ccJalaliMonthKey(r.billing_month))) : [];
            this._ccRows = rows;
            return rows;
        })();
        try { return await this._ccOpsPromise; } finally { this._ccOpsPromise = null; }
    },
    async ccRefreshOps() {
        const rows = await this.ccEnsureOpsData(true);
        this.ccRenderKpis(rows); this.ccRenderQueue(rows); this.ccRenderTrend(rows);
    },
    ccStatusBucket(s) {
        s = String(s || '').toUpperCase();
        if (['SETTLED', 'PAID', 'TRANSFERRED_TO_PAYROLL'].includes(s)) return 'paid';
        if (['APPROVED', 'OVERRIDDEN', 'FINANCE_APPROVED', 'READY_FOR_PAYOUT'].includes(s)) return 'approved';
        if (s === 'REJECTED') return 'rejected';
        return 'pending';
    },
    ccStatusLabel(s) {
        return ({
            PENDING_APPROVAL: 'در انتظار تأیید',
            OVERRIDDEN: 'اصلاح‌شده',
            FINANCE_APPROVED: 'تأیید مالی',
            CALCULATED: 'محاسبه‌شده', PENDING_MANAGER_REVIEW: 'در انتظار تایید مدیر',
            PENDING_FINANCE: 'در انتظار مالی', PENDING_FINANCE_APPROVAL: 'در انتظار تایید مالی',
            READY_FOR_PAYOUT: 'آماده پرداخت', APPROVED: 'تایید شده', SETTLED: 'تسویه شده',
            PAID: 'پرداخت‌شده', TRANSFERRED_TO_PAYROLL: 'منتقل به حقوق', REJECTED: 'رد شده'
        }[String(s || '').toUpperCase()] || String(s || '-'));
    },
    ccRoleLabel(row) {
        const t = String(row.beneficiary_type || '').toUpperCase();
        if (t.indexOf('SUPERVISOR') === 0) return 'سرپرست';
        if (t.indexOf('MANAGER') === 0) return 'مدیر';
        if (t === 'CLAWBACK') return 'عودت';
        if (String(row.assignment_source_type || '').toUpperCase() === 'TEAM') return 'تیم';
        return 'مستقیم';
    },
    ccIsActionable(row) {
        const status = String(row.status || '').toUpperCase();
        return Number(row.id || 0) > 0 && status === 'PENDING_APPROVAL';
    },
    ccRowAmount(row) {
        return Number(row.final_payable_amount || row.calculated_amount || row.calculated_commission || 0);
    },
    ccIsPayableCandidate(row) {
        const bucket = this.ccStatusBucket(row?.status);
        return bucket !== 'rejected' && this.ccRowAmount(row) > 0;
    },
    ccCycleRoleLabel(row) {
        const lines = Array.isArray(row.lines) ? row.lines : [];
        const types = Array.from(new Set(lines.map(line => String(line.beneficiary_type || '').toUpperCase()).filter(Boolean)));
        if (types.length === 1) return this.ccRoleLabel(lines[0] || { beneficiary_type: types[0] });
        if (types.length > 1) return 'چند نقش';
        return 'مستقیم';
    },
    ccCycleRateLabel(row) {
        const lines = Array.isArray(row.lines) ? row.lines : [];
        const rates = Array.from(new Set(lines.map(line => Number(line.applied_rate_percent || 0)).filter(rate => Number.isFinite(rate) && rate > 0)));
        if (rates.length === 1) return this.ccPercentLabel(rates[0]);
        if (rates.length > 1) return 'چند نرخ';
        return '—';
    },
    ccPercentLabel(value) {
        const num = Number(value || 0);
        if (!Number.isFinite(num)) return '—';
        return num.toLocaleString('en-US', { maximumFractionDigits: 2 }) + '%';
    },
    ccDateLabel(value) {
        const raw = String(value || '').trim();
        if (!raw) return '—';
        const compact = raw.replace(/\//g, '-');
        const jalaliDate = compact.match(/^(1[34]\d{2})-(\d{1,2})-(\d{1,2})/);
        if (jalaliDate) return jalaliDate[1] + '/' + String(jalaliDate[2]).padStart(2, '0') + '/' + String(jalaliDate[3]).padStart(2, '0');
        const gregorianYmd = compact.match(/^(19\d{2}|20\d{2})-(\d{1,2})-(\d{1,2})/);
        const gregorianDmy = compact.match(/^(\d{1,2})-(\d{1,2})-(19\d{2}|20\d{2})/);
        let dateOnly = '';
        if (gregorianYmd) {
            dateOnly = gregorianYmd[1] + '-' + String(gregorianYmd[2]).padStart(2, '0') + '-' + String(gregorianYmd[3]).padStart(2, '0');
        } else if (gregorianDmy) {
            dateOnly = gregorianDmy[3] + '-' + String(gregorianDmy[2]).padStart(2, '0') + '-' + String(gregorianDmy[1]).padStart(2, '0');
        } else if (/^(19\d{2}|20\d{2})-\d{1,2}$/.test(compact)) {
            const parts = compact.split('-');
            dateOnly = parts[0] + '-' + String(parts[1]).padStart(2, '0') + '-01';
        }
        if (dateOnly && typeof toPersianDate === 'function') return toPersianDate(dateOnly) || raw;
        return raw;
    },
    ccMonthLabel(value) {
        const raw = String(value || '').trim();
        if (!raw) return '—';
        if (!/^\d{4}[-/]\d{1,2}$/.test(raw)) return this.ccDateLabel(raw);
        const label = this.ccDateLabel(raw);
        const match = /^(\d{4})[/-](\d{2})/.exec(String(label || ''));
        return match ? (match[1] + '/' + match[2]) : label;
    },
    ccJalaliMonthKey(value) {
        // Normalize a billing_month (stored as either Jalali "1405-03" or Gregorian "2026-05")
        // to a canonical Jalali "YYYY-MM" key, matching exactly how ccMonthLabel displays it.
        const label = this.ccMonthLabel(value);
        const match = /^(\d{4})[/-](\d{1,2})/.exec(String(label || ''));
        return match ? (match[1] + '-' + String(match[2]).padStart(2, '0')) : '';
    },
    ccPeriodLabel(value) {
        const raw = String(value || '').trim();
        if (!raw) return '—';
        if (/^\d{4}[-/]\d{1,2}$/.test(raw)) return this.ccMonthLabel(raw);
        if (/^((19|20)\d{2}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}[-/]\d{1,2}[-/](19|20)\d{2}|1[34]\d{2}[-/]\d{1,2}[-/]\d{1,2})/.test(raw)) return this.ccDateLabel(raw);
        return raw.replace(/((19|20)\d{2}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}[-/]\d{1,2}[-/](19|20)\d{2}|1[34]\d{2}[-/]\d{1,2}[-/]\d{1,2})/g, (date) => this.ccDateLabel(date));
    },
    ccDateRangeLabel(from, to, openEndedLabel = 'نامحدود') {
        const fromLabel = from ? this.ccDateLabel(from) : '—';
        const toLabel = to ? this.ccDateLabel(to) : openEndedLabel;
        return from || to ? (fromLabel + ' تا ' + toLabel) : 'بدون محدودیت تاریخ';
    },
    ccAttr(value) {
        return this.escapeHtml(String(value == null ? '' : value));
    },
    ccInstallDrilldownClickHandlers() {
        if (this._ccDrilldownClickHandlersInstalled) return;
        this._ccDrilldownClickHandlersInstalled = true;
        document.addEventListener('click', (event) => {
            const btn = event.target && event.target.closest ? event.target.closest('[data-cc-action]') : null;
            if (!btn) return;
            const root = btn.closest('#view-commission-calc, .cc-drill-modal, .cc-commission-build-modal');
            if (!root) return;
            event.preventDefault();
            event.stopPropagation();
            const action = String(btn.dataset.ccAction || '');
            const closesModal = btn.dataset.closeModal === '1';
            const updateCurrentDrilldown = !!btn.closest('.cc-drill-modal') && !closesModal;
            if (closesModal && typeof Swal !== 'undefined') Swal.close();
            if (action === 'open-deal') return this.ccOpenDealLink(btn.dataset.id || '');
            if (action === 'open-lead') return this.ccOpenLeadLink(btn.dataset.id || '');
            if (action === 'cycle-drill') return this.ccOpenCycleDrilldown(Number(btn.dataset.cycleId || 0), btn.dataset.metric || 'all', { updateOnly: updateCurrentDrilldown });
            if (action === 'preview-drill') return this.ccOpenPreviewDrilldown(btn.dataset.metric || 'all', btn.dataset.ownerKey || '', btn.dataset.reason || '', { updateOnly: updateCurrentDrilldown });
            if (action === 'rule-products-modal') return this.ccShowRuleProductsModal(Number(btn.dataset.ruleId || 0));
            if (action === 'toggle-preview-person') return this.ccTogglePreviewPersonDetails(btn.dataset.panelId || '', btn);
            if (action === 'approve-cycle') return this.ccApprove(Number(btn.dataset.cycleId || 0));
            if (action === 'reject-cycle') return this.ccReject(Number(btn.dataset.cycleId || 0));
        });
    },
    ccLineStatusLabel(status) {
        return this.ccStatusLabel(status || 'CALCULATED');
    },
    ccBeneficiaryTypeLabel(type) {
        return this.ccRoleLabel({ beneficiary_type: type || '' });
    },
    ccPaymentKindLabel(kind, isCheque) {
        if (String(kind || '') === 'balance') return 'مانده حساب';
        return 'فروش';
    },
    ccMetricLabel(metric) {
        return ({
            all: 'همه ردیف‌ها',
            positive: 'پورسانت مثبت',
            sales: 'فروش‌ها',
            balance: 'مانده حساب',
            cheque: 'چک‌ها',
            deposit: 'واریزی‌ها',
            reversal: 'عودت‌ها'
        }[String(metric || 'all')] || String(metric || 'همه'));
    },
    ccOpenDealLink(dealId) {
        const safeDealId = String(dealId || '').trim();
        if (!safeDealId) {
            Swal.fire('', 'شناسه معامله برای این ردیف پیدا نشد.', 'info');
            return;
        }
        this.closeAllCustomMultiSelectDropdowns?.();
        if (typeof Swal !== 'undefined') Swal.close();
        setTimeout(async () => {
            try {
                await this.openDealModal(safeDealId, { initialTab: 'info' });
            } catch (e) {
                console.warn('ccOpenDealLink failed', e);
            }
        }, 60);
    },
    ccOpenLeadLink(contactId) {
        const safeContactId = String(contactId || '').trim();
        if (!safeContactId) {
            Swal.fire('', 'شناسه لید برای این ردیف پیدا نشد.', 'info');
            return;
        }
        this.closeAllCustomMultiSelectDropdowns?.();
        if (typeof Swal !== 'undefined') Swal.close();
        setTimeout(async () => {
            try {
                await this.openPersonProfile(safeContactId);
            } catch (e) {
                console.warn('ccOpenLeadLink failed', e);
            }
        }, 60);
    },
    ccResolveDealLinkId(row) {
        return String(
            row?.deal_didar_id ||
            row?.didar_deal_id ||
            row?.local_deal_didar_id ||
            row?.deal_id ||
            row?.local_deal_id ||
            ''
        ).trim();
    },
    ccResolveLeadLinkId(row) {
        return String(
            row?.contact_didar_id ||
            row?.deal_contact_didar_id ||
            row?.contact_id ||
            row?.local_person_didar_id ||
            row?.person_didar_id ||
            ''
        ).trim();
    },
    ccCycleSourceLabel(row) {
        const lines = Array.isArray(row.lines) ? row.lines : [];
        const ids = new Set(lines.map(line => Number(line.payment_record_id || 0)).filter(Boolean));
        const count = ids.size || Number(row.commission_line_count || row.line_count || 0);
        return count ? this.formatNumber(count) + ' پرداخت' : '—';
    },
    ccAssignmentSourceLabel(line) {
        const sourceType = String(line?.assignment_source_type || '').trim().toUpperCase();
        const teamName = String(line?.assignment_source_team_supervisor_name || '').trim();
        const teamId = Number(line?.assignment_source_team_supervisor_user_id || 0);
        if (sourceType === 'TEAM') {
            return teamName ? ('از طریق تیم ' + teamName) : (teamId > 0 ? ('از طریق تیم #' + teamId) : 'از طریق تیم');
        }
        if (sourceType === 'INDIVIDUAL') return 'انفرادی';
        const hasAssignment = Number(line?.assignment_id || line?.commission_rule_assignment_id || 0) > 0 || String(line?.rule_title || '').trim() !== '';
        return hasAssignment ? 'انفرادی' : 'نامشخص';
    },
    ccCycleAssignmentSourceLabel(row) {
        const lines = Array.isArray(row.lines) ? row.lines : [];
        const labels = Array.from(new Set(lines.map(line => this.ccAssignmentSourceLabel(line)).filter(Boolean)));
        if (labels.length === 1) return labels[0];
        if (labels.length > 1) return 'چند منبع: ' + labels.slice(0, 2).join('، ') + (labels.length > 2 ? '…' : '');
        return '';
    },
    ccRenderKpis(rows) {
        this.ccInstallDrilldownClickHandlers();
        let pend = 0, payable = 0, approvedSum = 0;
        (rows || []).forEach(r => {
            const b = this.ccStatusBucket(r.status); const c = this.ccRowAmount(r);
            if (b === 'pending') { pend++; }
            if (this.ccIsPayableCandidate(r)) { payable += c; }
            if (b === 'approved') { approvedSum += c; }
        });
        const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
        set('cc-kpi-pending', this.formatNumber(pend) + ' مورد');
        set('cc-kpi-payable', this.formatMoney(payable));
        set('cc-kpi-approved', this.formatMoney(approvedSum));
        const bind = (id, status) => {
            const card = document.getElementById(id)?.closest('.cc-kpi');
            if (!card) return;
            card.classList.add('cc-clickable-card');
            card.onclick = () => this.ccOpenQueueDrilldownByStatus(status);
        };
        bind('cc-kpi-pending', 'pending');
        bind('cc-kpi-payable', 'payable');
        bind('cc-kpi-approved', 'approved');
    },
    ccOpenQueueDrilldownByStatus(statusBucket) {
        const rows = Array.isArray(this._ccRows) ? this._ccRows : [];
        const matches = rows.filter(r => {
            if (statusBucket === 'payable') return this.ccIsPayableCandidate(r);
            return !statusBucket || this.ccStatusBucket(r.status) === statusBucket;
        });
        if (!matches.length) {
            Swal.fire('', 'برای این کارت چرخه‌ای در بازه فعلی وجود ندارد.', 'info');
            return;
        }
        this.ccShowQueueDrilldownPicker(matches, statusBucket || 'all');
    },
    ccShowQueueDrilldownPicker(rows, statusBucket) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const title = ({
            pending: 'چرخه‌های در انتظار تأیید',
            payable: 'پورسانت‌های قابل پرداخت',
            approved: 'چرخه‌های تأییدشده / آماده پرداخت'
        }[String(statusBucket || '')] || 'چرخه‌های قابل پرداخت');
        const total = rows.reduce((sum, row) => sum + this.ccRowAmount(row), 0);
        const body = rows.map(row => {
            const id = Number(row.id || 0);
            return '<div class="cc-cycle-picker-row">'
                + '<div><strong>' + esc(row.salesperson_name || ('کاربر #' + (row.user_id || '-'))) + '</strong><small>' + esc(this.ccMonthLabel(row.billing_month || '')) + ' · ' + esc(this.ccStatusLabel(row.status || '')) + '</small></div>'
                + '<div><b>' + this.formatMoney(this.ccRowAmount(row)) + '</b><button type="button" data-cc-action="cycle-drill" data-close-modal="1" data-cycle-id="' + id + '" data-metric="all">جزئیات</button></div>'
                + '</div>';
        }).join('');
        Swal.fire({
            title,
            html: '<div class="cc-cycle-picker" dir="rtl"><div class="cc-cycle-picker-summary"><span>' + esc(this.formatNumber(rows.length)) + ' چرخه</span><strong>' + this.formatMoney(total) + '</strong></div>' + body + '</div>',
            width: 760,
            showCloseButton: true,
            showConfirmButton: false,
            customClass: { popup: 'cc-drill-modal' }
        });
    },
    async ccLoadOpsKpis() { this.ccRenderKpis(await this.ccEnsureOpsData()); },
    async ccLoadQueue() { this.ccRenderQueue(await this.ccEnsureOpsData()); },
    async ccLoadTrend() {
        try { if (typeof ApexCharts === 'undefined' && typeof this.ensureVendor === 'function') await this.ensureVendor('apexcharts'); } catch (_) {}
        this.ccRenderTrend(await this.ccEnsureOpsData());
    },
    async ccPreviewLedger(evt) {
        const range = this.ccDateRange();
        if (!this.ccRequireValidDateRange(range, true)) return;
        const btn = evt && evt.currentTarget ? evt.currentTarget : null;
        const oldHtml = btn ? btn.innerHTML : '';
        const panel = document.getElementById('cc-preview-panel');
        const body = document.getElementById('cc-preview-body');
        if (panel) panel.classList.remove('hidden');
        if (body) body.innerHTML = '<div class="text-gray-400 text-sm py-4"><i class="fa-solid fa-spinner fa-spin ml-1"></i>در حال ساخت پیش‌نمایش پرداخت‌ها…</div>';
        if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin ml-1"></i>در حال بررسی'; }
        try {
            const res = await this.req('preview_commission_ledger', { date_from: range.date_from, date_to: range.date_to });
            if (!res || res.status !== 'success') {
                if (body) body.innerHTML = '<div class="text-red-300 text-sm py-4">' + this.escapeHtml((res && res.message) || 'خطا در پیش‌نمایش محاسبه') + '</div>';
                return;
            }
            this.ccRenderLedgerPreview(res.preview || {});
        } finally {
            if (btn) { btn.disabled = false; btn.innerHTML = oldHtml; }
        }
    },
    ccRenderLedgerPreview(preview) {
        this.ccInstallDrilldownClickHandlers();
        const panel = document.getElementById('cc-preview-panel');
        const body = document.getElementById('cc-preview-body');
        if (panel) panel.classList.remove('hidden');
        if (!body) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const summary = preview.summary || {};
        const rows = Array.isArray(preview.payments) ? preview.payments : [];
        const people = Array.isArray(preview.people) ? preview.people : [];
            const cards = [
                ['کل پرداخت‌های بازه', summary.total_payments || 0],
                ['قابل محاسبه', summary.included_payments || 0],
                ['جا مانده', summary.excluded_payments || 0],
                ['پورسانت تخمینی', this.formatMoney(summary.estimated_commission || 0)],
                ['پوشش قوانین', (summary.coverage_percent || 0) + '%']
            ];
        this._ccPreview = preview;
        const cardMetrics = ['all', 'included', 'excluded', 'included', 'excluded'];
        const cardsHtml = cards.map((item, idx) => '<button type="button" class="cc-preview-summary-card" data-cc-action="preview-drill" data-metric="' + this.ccAttr(cardMetrics[idx]) + '"><small>' + esc(item[0]) + '</small><strong>' + esc(typeof item[1] === 'number' ? this.formatNumber(item[1]) : item[1]) + '</strong></button>').join('');
        const renderPaymentRows = (items) => items.length ? items.map(row => {
            const ok = String(row.status || '') === 'included';
            const dealId = this.ccResolveDealLinkId(row);
            const contactId = this.ccResolveLeadLinkId(row);
            const links = '<div class="cc-nested-links">'
                + (dealId ? '<button type="button" data-cc-action="open-deal" data-id="' + this.ccAttr(dealId) + '">معامله</button>' : '')
                + (contactId ? '<button type="button" data-cc-action="open-lead" data-id="' + this.ccAttr(contactId) + '">لید</button>' : '')
                + '</div>';
            return '<tr class="' + (ok ? 'ok' : '') + '">'
                + '<td><span class="cc-preview-status ' + (ok ? 'ok' : 'skip') + '">' + esc(row.status_label || (ok ? 'محاسبه می‌شود' : 'نمی‌آید')) + '</span></td>'
                + '<td><strong>' + esc(row.payment_label || ('پرداخت #' + (row.payment_id || '-'))) + '</strong><div class="text-[10px] text-gray-500">' + esc(row.payment_kind_label || '-') + '</div>' + links + '</td>'
                + '<td>' + esc(row.effective_date_jalali || this.ccDateLabel(row.effective_date || '')) + '</td>'
                + '<td class="font-bold">' + this.formatMoney(row.amount || 0) + '</td>'
                + '<td>' + esc(row.product_names || 'بدون محصول') + '</td>'
                + '<td>' + esc(row.rule_title || '—') + '</td>'
                + '<td class="' + (ok ? 'text-gray-500' : 'text-red-300') + '">' + (ok ? '—' : esc(row.reason_label || '—')) + '</td>'
                + '<td>' + (ok ? this.formatMoney(row.estimated_commission || 0) : '—') + '</td>'
                + '</tr>';
        }).join('') : '<tr><td colspan="8" class="text-center text-gray-500 py-5">پرداختی در این بازه برای بررسی پیدا نشد.</td></tr>';
        const personHtml = people.length ? '<div class="cc-preview-people">' + people.map(person => {
            const ownerKey = String(person.owner_key || '');
            const personRows = rows.filter(row => String(row.owner_key || '') === ownerKey);
            const panelId = 'cc-preview-person-detail-' + ownerKey.replace(/[^a-zA-Z0-9_-]/g, '-');
            const topReason = String(person.top_exclusion_reason || '');
            const includedCount = Number(person.included_payments || 0);
            const totalPersonPayments = Number(person.total_payments || 0);
            const excludedCount = Number(person.excluded_payments || Math.max(0, totalPersonPayments - includedCount));
            const topReasonCode = (Array.isArray(person.reason_summary) && person.reason_summary[0]) ? String(person.reason_summary[0].reason || '') : '';
            const statItems = [
                ['کل معامله‌ها', this.formatNumber(person.total_deals || 0) + ' معامله'],
                ['فروش‌ها', this.formatNumber(person.sale_payment_count || 0) + ' پرداخت اول از ' + this.formatNumber(person.sale_deals || 0) + ' معامله'],
                ['مانده حساب', this.formatNumber(person.balance_payment_count || 0) + ' پرداخت از ' + this.formatNumber(person.balance_deals || 0) + ' معامله'],
                ['نوع مانده', this.formatNumber(person.balance_deposit_count || 0) + ' واریزی، ' + this.formatNumber(person.balance_cheque_count || 0) + ' چک'],
                ['نتیجه محاسبه', this.formatNumber(includedCount) + ' محاسبه، ' + this.formatNumber(excludedCount) + ' جا مانده'],
                ['دلیل غالب', topReason ? topReason + (person.top_exclusion_reason_count ? ' (' + this.formatNumber(person.top_exclusion_reason_count) + ')' : '') : 'همه وارد محاسبه شده‌اند']
            ];
            const statFilters = [
                { metric: 'all', reason: '' },
                { metric: 'sales', reason: '' },
                { metric: 'balance', reason: '' },
                { metric: 'balance', reason: '' },
                { metric: 'included', reason: '' },
                { metric: 'excluded', reason: topReasonCode }
            ];
            const stats = statItems.map((item, idx) => '<button type="button" data-cc-action="preview-drill" data-metric="' + this.ccAttr(statFilters[idx].metric) + '" data-owner-key="' + this.ccAttr(ownerKey) + '" data-reason="' + this.ccAttr(statFilters[idx].reason) + '"><small>' + esc(item[0]) + '</small><strong>' + esc(typeof item[1] === 'number' ? this.formatNumber(item[1]) : item[1]) + '</strong></button>').join('');
            const reasonChips = Array.isArray(person.reason_summary) && person.reason_summary.length
                ? '<div class="cc-preview-reasons">' + person.reason_summary.map(reason => '<button type="button" data-cc-action="preview-drill" data-metric="excluded" data-owner-key="' + this.ccAttr(ownerKey) + '" data-reason="' + this.ccAttr(reason.reason || '') + '"><span>' + esc(reason.label || reason.reason || '-') + '</span><b>' + esc(this.formatNumber(reason.count || 0)) + '</b></button>').join('') + '</div>'
                : '';
            return '<section class="cc-preview-person">'
                + '<div class="cc-preview-person-head"><div><strong>' + esc(person.owner_name || 'بدون مسئول') + '</strong><small>پورسانت تخمینی: ' + this.formatMoney(person.estimated_commission || 0) + '</small></div>'
                + '<button type="button" class="cc-preview-person-toggle" data-cc-action="toggle-preview-person" data-panel-id="' + this.ccAttr(panelId) + '"><i class="fa-solid fa-chevron-down"></i><span>نمایش جزئیات</span></button></div>'
                + '<div class="cc-preview-person-stats">' + stats + '</div>'
                + reasonChips
                + '<div class="cc-preview-person-note">فروش یعنی پرداخت اول معامله؛ مانده حساب یعنی پرداخت دوم به بعد. واریزی یعنی هر پرداخت غیر از چک. تاریخ پرداخت/وصول همان تاریخی است که سیستم برای سنجش بازه انتخابی و اعتبار قانون استفاده می‌کند.</div>'
                + '<div id="' + panelId + '" class="overflow-x-auto hidden"><table class="cc-preview-table"><thead><tr>'
                + '<th>وضعیت</th><th>پرداخت</th><th>تاریخ پرداخت/وصول</th><th>مبلغ</th><th>محصول معامله</th><th>قانون</th><th>دلیل جا ماندن</th><th>پورسانت</th>'
                + '</tr></thead><tbody>' + renderPaymentRows(personRows) + '</tbody></table></div>'
                + '</section>';
        }).join('') + '</div>' : '<div class="text-center text-gray-500 py-5">پرداختی در این بازه برای بررسی پیدا نشد.</div>';
        body.innerHTML = '<div class="cc-preview-head">'
            + '<div><h5>پیش‌نمایش محاسبه پرداخت‌ها</h5><p>برای هر کارشناس تعداد معامله، فروش، مانده حساب و دلیل جا ماندن پرداخت‌ها را نشان می‌دهد؛ این بخش چیزی در صف نمی‌سازد.</p></div>'
            + '<button type="button" class="cc-preview-detail-btn" onclick="document.getElementById(\'cc-preview-panel\').classList.add(\'hidden\')">بستن</button>'
            + '</div>'
            + '<div class="cc-preview-summary">' + cardsHtml + '</div>'
            + personHtml;
    },
    ccTogglePreviewPersonDetails(panelId, btn) {
        const panel = document.getElementById(panelId);
        if (!panel) return;
        const willOpen = panel.classList.contains('hidden');
        panel.classList.toggle('hidden', !willOpen);
        if (btn) {
            const label = btn.querySelector('span');
            const icon = btn.querySelector('i');
            if (label) label.textContent = willOpen ? 'بستن جزئیات' : 'نمایش جزئیات';
            if (icon) {
                icon.classList.toggle('fa-chevron-up', willOpen);
                icon.classList.toggle('fa-chevron-down', !willOpen);
            }
        }
    },
    ccRenderQueue(rows) {
        this.ccInstallDrilldownClickHandlers();
        const body = document.getElementById('cc-queue-body'); if (!body) return;
        const filter = (document.getElementById('cc-q-status') || {}).value || '';
        let list = (rows || []).slice();
        if (filter) list = list.filter(r => this.ccStatusBucket(r.status) === filter);
        if (!list.length) { body.innerHTML = '<tr><td colspan="9" class="text-center text-gray-500 py-8">برای بازه و وضعیت انتخاب‌شده چرخه‌ای در صف تأیید وجود ندارد.</td></tr>'; return; }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const out = [];
        list.forEach((r) => {
            const id = Number(r.id || 0);
            const b = this.ccStatusBucket(r.status);
            const amount = this.ccRowAmount(r);
            const rowClass = 'cc-cycle-row cc-cycle-parent-' + id;
            const beneficiary = r.salesperson_name || ('کاربر #' + (r.salesperson_user_id || '-'));
            const assignmentSource = this.ccCycleAssignmentSourceLabel(r);
            out.push('<tr class="' + rowClass + '">'
                + '<td>' + (this.ccIsActionable(r) ? '<input type="checkbox" class="cc-q-row" value="' + id + '">' : '') + '</td>'
                + '<td><button type="button" class="cc-queue-person cc-queue-person-link" data-cc-action="cycle-drill" data-cycle-id="' + id + '" data-metric="all"><strong>' + esc(beneficiary) + '</strong><small>' + esc(this.ccMonthLabel(r.billing_month || '')) + '</small>' + (assignmentSource ? '<small>' + esc(assignmentSource) + '</small>' : '') + '</button></td>'
                + '<td><span class="cc-badge cc-b-approved">' + esc(this.ccCycleRoleLabel(r)) + '</span></td>'
                + '<td>' + esc(this.ccCycleSourceLabel(r)) + '</td>'
                + '<td><strong>' + esc(this.ccCycleRateLabel(r)) + '</strong></td>'
                + '<td>' + esc(this.ccPeriodLabel(r.billing_month || r.approval_deadline || '-')) + '</td>'
                + '<td class="cc-queue-money">' + this.formatMoney(amount) + '</td>'
                + '<td><span class="cc-badge cc-b-' + b + '">' + esc(this.ccStatusLabel(r.status)) + '</span></td>'
                + '<td>' + this.ccRowActions(r) + '</td>'
                + '</tr>');
        });
        body.innerHTML = out.join('');
    },
    ccRowActions(r) {
        const s = String(r.status || '').toUpperCase(); const id = Number(r.id);
        let h = '<div class="cc-queue-actions"><button class="cc-detail-toggle" data-cc-action="cycle-drill" data-cycle-id="' + id + '" data-metric="all"><i class="fa-solid fa-chart-network ml-1"></i>Drill-down</button>';
        if (s === 'PENDING_APPROVAL') h += '<button class="cc-queue-action approve" data-cc-action="approve-cycle" data-cycle-id="' + id + '"><i class="fa-solid fa-check"></i>تأیید</button>';
        if (s === 'PENDING_APPROVAL') h += '<button class="cc-queue-action reject" data-cc-action="reject-cycle" data-cycle-id="' + id + '"><i class="fa-solid fa-xmark"></i>رد</button>';
        return h + '</div>';
    },
    async ccOpenCycleDrilldown(cycleId, metric = 'all', options = {}) {
        cycleId = Number(cycleId || 0);
        if (cycleId <= 0) return;
        const updateOnly = !!options.updateOnly;
        if (!updateOnly) {
            Swal.fire({
                title: 'جزئیات محاسبه پورسانت',
                html: '<div class="cc-drill-loading"><i class="fa-solid fa-spinner fa-spin"></i><span>در حال بارگذاری جزئیات...</span></div>',
                showConfirmButton: false,
                width: '96%',
                customClass: { popup: 'cc-drill-modal' }
            });
        }
        const res = await this.req('get_commission_drilldown', { cycle_id: cycleId, metric: metric || 'all' });
        if (!res || res.status !== 'success') {
            Swal.fire('خطا', (res && res.message) || 'خطا در دریافت جزئیات پورسانت', 'error');
            return;
        }
        this._ccDrilldown = res.drilldown || {};
        if (updateOnly && this.ccUpdateDrilldownModal(this._ccDrilldown, 'cycle')) return;
        this.ccShowDrilldownModal(this._ccDrilldown, 'cycle');
    },
    ccOpenPreviewDrilldown(metric = 'all', ownerKey = '', reason = '', options = {}) {
        const preview = this._ccPreview || {};
        let rows = Array.isArray(preview.payments) ? preview.payments.slice() : [];
        const normalizedMetric = String(metric || 'all');
        const owner = String(ownerKey || '');
        const reasonCode = String(reason || '');
        if (owner) rows = rows.filter(row => String(row.owner_key || '') === owner);
        if (normalizedMetric === 'included') rows = rows.filter(row => String(row.status || '') === 'included');
        if (normalizedMetric === 'excluded') rows = rows.filter(row => String(row.status || '') !== 'included');
        if (normalizedMetric === 'sales') rows = rows.filter(row => String(row.payment_kind || '') === 'sale');
        if (normalizedMetric === 'balance') rows = rows.filter(row => String(row.payment_kind || '') === 'balance');
        if (normalizedMetric === 'cheque') rows = rows.filter(row => Number(row.is_cheque || 0) === 1);
        if (normalizedMetric === 'deposit') rows = rows.filter(row => Number(row.is_cheque || 0) !== 1);
        if (reasonCode) rows = rows.filter(row => String(row.reason || '') === reasonCode);
        const mapped = {
            cycle: {
                salesperson_name: owner ? (rows[0]?.owner_name || this.ccPreviewOwnerName(owner, preview)) : 'پیش‌نمایش محاسبه',
                billing_month: (preview.date_from || '') + ' تا ' + (preview.date_to || ''),
                status: 'PREVIEW'
            },
            summary: this.ccSummarizePreviewRows(rows, Array.isArray(preview.payments) ? preview.payments : []),
            rules: this.ccSummarizePreviewRules(rows),
            lines: rows.map(row => this.ccPreviewRowToDrillLine(row)),
            filters: { metric: normalizedMetric, owner_key: owner, reason: reasonCode }
        };
        this._ccDrilldown = mapped;
        if (options.updateOnly && this.ccUpdateDrilldownModal(mapped, 'preview')) return;
        this.ccShowDrilldownModal(mapped, 'preview');
    },
    ccPreviewOwnerName(ownerKey, preview) {
        const people = Array.isArray(preview.people) ? preview.people : [];
        const person = people.find(item => String(item.owner_key || '') === String(ownerKey || ''));
        return person ? String(person.owner_name || '') : '';
    },
    ccPreviewRowToDrillLine(row) {
        const amount = Number(row.amount || 0);
        const commission = Number(row.estimated_commission || 0);
        const rate = amount > 0 && commission > 0 ? (commission / amount * 100) : 0;
        return {
            id: row.payment_id || 0,
            payment_record_id: row.payment_id || 0,
            payment_label: row.payment_label || ('پرداخت #' + (row.payment_id || '-')),
            payment_kind: row.payment_kind || '',
            payment_kind_label: row.payment_kind_label || '',
            is_cheque: Number(row.is_cheque || 0),
            status: row.status === 'included' ? 'PREVIEW_INCLUDED' : 'PREVIEW_EXCLUDED',
            reason_label: row.reason_label || '',
            effective_date: row.effective_date || '',
            payment_effective_date: row.effective_date || '',
            payment_amount: amount,
            commission_base_amount: amount,
            applied_rate_percent: rate,
            calculated_commission: commission,
            calculation_formula: amount > 0 && rate > 0 ? this.formatMoney(amount) + ' × ' + this.ccPercentLabel(rate) + ' = ' + this.formatMoney(commission) : '',
            customer_name: row.customer_name || row.deal_title || '',
            customer_mobile: row.customer_mobile || '',
            contact_didar_id: this.ccResolveLeadLinkId(row),
            deal_didar_id: this.ccResolveDealLinkId(row),
            local_deal_id: row.deal_id || 0,
            deal_title: row.deal_title || '',
            payment_owner_name: row.owner_name || '',
            current_lead_owner_name: row.current_lead_owner_name || '',
            product_name: row.product_names || '',
            rule_title: row.rule_title || '',
            commission_rule_id: row.rule_id || 0,
            commission_rule_assignment_id: row.assignment_id || 0,
            beneficiary_type: 'DIRECT_PAYMENT_OWNER'
        };
    },
    ccSummarizePreviewRows(rows, allRows) {
        const dealKeys = new Set();
        let paymentTotal = 0, commissionTotal = 0, sales = 0, balance = 0, cheques = 0;
        rows.forEach(row => {
            paymentTotal += Number(row.amount || 0);
            commissionTotal += Number(row.estimated_commission || 0);
            const dealKey = row.deal_id || row.deal_didar_id || row.payment_id || '';
            if (dealKey) dealKeys.add(String(dealKey));
            if (String(row.payment_kind || '') === 'balance') balance++; else sales++;
            if (Number(row.is_cheque || 0) === 1) cheques++;
        });
        return {
            line_count: rows.length,
            all_line_count: allRows.length,
            payment_count: rows.length,
            deal_count: dealKeys.size,
            sale_payment_count: sales,
            balance_payment_count: balance,
            cheque_line_count: cheques,
            payment_total: paymentTotal,
            commission_total: commissionTotal
        };
    },
    ccSummarizeDrillLines(rows, allRows) {
        const dealKeys = new Set();
        const paymentAmounts = {};
        let commissionTotal = 0, sales = 0, balance = 0, cheques = 0;
        rows.forEach(line => {
            const paymentId = Number(line.payment_record_id || 0);
            const amount = Number(line.payment_amount || line.payment_amount_snapshot || 0);
            if (paymentId > 0 && paymentAmounts[paymentId] === undefined) paymentAmounts[paymentId] = amount;
            if (paymentId <= 0) paymentAmounts['line:' + (line.id || Math.random())] = amount;
            const dealKey = line.local_deal_id || line.deal_didar_id || line.payment_record_id || '';
            if (dealKey) dealKeys.add(String(dealKey));
            if (String(line.payment_kind || '') === 'balance') balance++; else sales++;
            if (Number(line.is_cheque || 0) === 1) cheques++;
            commissionTotal += Number(line.calculated_commission || 0);
        });
        return {
            line_count: rows.length,
            all_line_count: Array.isArray(allRows) ? allRows.length : rows.length,
            payment_count: Object.keys(paymentAmounts).length,
            deal_count: dealKeys.size,
            sale_payment_count: sales,
            balance_payment_count: balance,
            cheque_line_count: cheques,
            payment_total: Object.values(paymentAmounts).reduce((sum, value) => sum + Number(value || 0), 0),
            commission_total: commissionTotal
        };
    },
    ccSummarizeDrillRules(rows) {
        const map = {};
        rows.forEach(line => {
            const key = String(line.commission_rule_id || 'none');
            if (!map[key]) {
                map[key] = {
                    rule_id: Number(line.commission_rule_id || 0),
                    rule_title: line.rule_title || 'بدون قانون',
                    rule_type: line.rule_type || '',
                    valid_from: line.rule_valid_from || '',
                    valid_to: line.rule_valid_to || '',
                    line_count: 0,
                    commission_total: 0,
                    payment_total: 0,
                    rates: []
                };
            }
            map[key].line_count++;
            map[key].commission_total += Number(line.calculated_commission || 0);
            map[key].payment_total += Number(line.payment_amount || line.payment_amount_snapshot || 0);
            const rate = Number(line.applied_rate_percent || 0);
            if (rate > 0 && !map[key].rates.includes(String(rate))) map[key].rates.push(String(rate));
        });
        return Object.values(map);
    },
    ccSummarizePreviewRules(rows) {
        const map = {};
        rows.forEach(row => {
            const key = String(row.rule_id || 'none');
            if (!map[key]) map[key] = { rule_id: Number(row.rule_id || 0), rule_title: row.rule_title || (row.status === 'included' ? 'بدون عنوان' : 'بدون قانون'), rule_type: 'preview', valid_from: '', valid_to: '', line_count: 0, commission_total: 0, payment_total: 0, rates: [] };
            map[key].line_count++;
            map[key].commission_total += Number(row.estimated_commission || 0);
            map[key].payment_total += Number(row.amount || 0);
        });
        return Object.values(map);
    },
    ccShowDrilldownModal(drilldown, source = 'cycle') {
        this.ccInstallDrilldownClickHandlers();
        this._ccDrilldown = drilldown || {};
        this._ccDrilldownSource = source;
        const html = this.ccRenderDrilldownHtml(drilldown || {}, source);
        Swal.fire({
            title: 'جزئیات محاسبه پورسانت',
            html,
            width: '96%',
            showConfirmButton: false,
            showCloseButton: true,
            customClass: { popup: 'cc-drill-modal' }
        });
    },
    ccUpdateDrilldownModal(drilldown, source = 'cycle') {
        this._ccDrilldown = drilldown || {};
        this._ccDrilldownSource = source;
        if (typeof Swal === 'undefined') return false;
        const isVisible = typeof Swal.isVisible === 'function' ? Swal.isVisible() : !!document.querySelector('.cc-drill-modal');
        if (!isVisible) return false;
        const container = (typeof Swal.getHtmlContainer === 'function' ? Swal.getHtmlContainer() : null)
            || document.querySelector('.cc-drill-modal .swal2-html-container')
            || document.querySelector('.swal2-html-container');
        if (!container) return false;
        container.innerHTML = this.ccRenderDrilldownHtml(this._ccDrilldown, source);
        return true;
    },
    ccRenderDrilldownHtml(drilldown, source) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const cycle = drilldown.cycle || {};
        const summary = drilldown.summary || {};
        const lines = Array.isArray(drilldown.lines) ? drilldown.lines : [];
        const cycleId = Number(cycle.id || drilldown.filters?.cycle_id || 0);
        const metric = String(drilldown.filters?.metric || 'all');
        const reason = String(drilldown.filters?.reason || '');
        const ownerKey = String(drilldown.filters?.owner_key || '');
        const metricButtons = ['all', 'positive', 'sales', 'balance', 'cheque', 'deposit', 'reversal'].map(m => {
            if (source === 'preview' && ['positive', 'reversal'].includes(m)) return '';
            const active = metric === m ? ' active' : '';
            if (source === 'cycle') {
                return '<button type="button" class="' + active + '" data-cc-action="cycle-drill" data-cycle-id="' + cycleId + '" data-metric="' + this.ccAttr(m) + '">' + esc(this.ccMetricLabel(m)) + '</button>';
            }
            return '<button type="button" class="' + active + '" data-cc-action="preview-drill" data-metric="' + this.ccAttr(m) + '" data-owner-key="' + this.ccAttr(ownerKey) + '" data-reason="' + this.ccAttr(reason) + '">' + esc(this.ccMetricLabel(m)) + '</button>';
        }).join('');
        const cards = [
            ['ردیف پورسانت', summary.line_count || 0],
            ['پرداخت', summary.payment_count || 0],
            ['معامله', summary.deal_count || 0],
            ['فروش / مانده حساب', (summary.sale_payment_count || 0) + ' / ' + (summary.balance_payment_count || 0)],
            ['مبلغ پرداخت', this.formatMoney(summary.payment_total || 0)],
            ['پورسانت', this.formatMoney(summary.commission_total || 0)]
        ].map(item => '<div><small>' + esc(item[0]) + '</small><strong>' + esc(typeof item[1] === 'number' ? this.formatNumber(item[1]) : item[1]) + '</strong></div>').join('');
        const rowsHtml = lines.length ? lines.map(line => this.ccRenderDrilldownLine(line)).join('') : '<tr><td colspan="10" class="cc-drill-empty">ردیفی برای این فیلتر وجود ندارد.</td></tr>';
        const filterNote = reason ? '<p>فیلتر دلیل: ' + esc(this.ccPreviewReasonLabel(reason)) + '</p>' : '';
        return '<div class="cc-drill" dir="rtl">'
            + '<div class="cc-drill-head"><div><h3>' + esc(cycle.salesperson_name || 'پیش‌نمایش محاسبه') + '</h3><p>' + esc(this.ccPeriodLabel(cycle.billing_month || '')) + ' · ' + esc(this.ccStatusLabel(cycle.status || '')) + '</p>' + filterNote + '</div><div class="cc-drill-tabs">' + metricButtons + '</div></div>'
            + '<div class="cc-drill-cards">' + cards + '</div>'
            + '<div class="cc-drill-table-wrap"><table class="cc-drill-table"><thead><tr>'
            + '<th>لید / معامله</th><th>پرداخت</th><th>مالک پرداخت</th><th>تاریخ پرداخت/وصول</th><th>مبلغ</th><th>نرخ</th><th>پورسانت</th><th>قانون</th><th>وضعیت</th><th>فرمول</th>'
            + '</tr></thead><tbody>' + rowsHtml + '</tbody></table></div>'
            + '</div>';
    },
    ccPreviewReasonLabel(reason) {
        const preview = this._ccPreview || {};
        const globalReasons = Array.isArray(preview.reason_summary) ? preview.reason_summary : [];
        for (const item of globalReasons) {
            if (String(item.reason || '') === String(reason || '')) return String(item.label || item.reason || reason);
        }
        const people = Array.isArray(preview.people) ? preview.people : [];
        for (const person of people) {
            const reasons = Array.isArray(person.reason_summary) ? person.reason_summary : [];
            for (const item of reasons) {
                if (String(item.reason || '') === String(reason || '')) return String(item.label || item.reason || reason);
            }
        }
        return String(reason || '');
    },
    ccRenderDrilldownLine(line) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const dealId = this.ccResolveDealLinkId(line);
        const contactId = this.ccResolveLeadLinkId(line);
        const title = line.customer_name || line.source_title || line.deal_title || line.deal_didar_id || '-';
        const links = '<div class="cc-drill-links">'
            + (dealId ? '<button type="button" data-cc-action="open-deal" data-id="' + this.ccAttr(dealId) + '"><i class="fa-solid fa-arrow-up-right-from-square"></i>معامله</button>' : '')
            + (contactId ? '<button type="button" data-cc-action="open-lead" data-id="' + this.ccAttr(contactId) + '"><i class="fa-solid fa-user"></i>لید</button>' : '')
            + '</div>';
        const status = line.reason_label || this.ccLineStatusLabel(line.status || '');
        const ownerMeta = line.current_lead_owner_name ? '<small>مالک فعلی لید: ' + esc(line.current_lead_owner_name) + '</small>' : '';
        const assignmentSource = this.ccAssignmentSourceLabel(line);
        return '<tr>'
            + '<td><strong>' + esc(title) + '</strong><small>' + esc(line.customer_mobile || line.deal_title || line.deal_code || '') + '</small>' + links + '</td>'
            + '<td><strong>' + esc(line.payment_label || ('پرداخت #' + (line.payment_record_id || '-'))) + '</strong><small>' + esc(this.ccPaymentKindLabel(line.payment_kind, line.is_cheque)) + '</small></td>'
            + '<td><strong>' + esc(line.payment_owner_name || '-') + '</strong>' + ownerMeta + '</td>'
            + '<td>' + esc(this.ccDateLabel(line.effective_date || line.payment_effective_date || line.deposit_date || line.payment_date || '')) + '</td>'
            + '<td>' + this.formatMoney(line.payment_amount || line.payment_amount_snapshot || 0) + '</td>'
            + '<td>' + esc(this.ccPercentLabel(line.applied_rate_percent || 0)) + '</td>'
            + '<td class="cc-drill-money">' + this.formatMoney(line.calculated_commission || 0) + '</td>'
            + '<td><strong>' + esc(line.rule_title || '—') + '</strong><small>' + esc((line.product_name || '') + (line.rule_valid_from ? ' · ' + this.ccDateLabel(line.rule_valid_from) : '')) + '</small><small>' + esc('منبع تخصیص: ' + assignmentSource) + '</small></td>'
            + '<td><span class="cc-preview-status ' + (String(line.status || '').indexOf('EXCLUDED') > -1 ? 'skip' : 'ok') + '">' + esc(status || '-') + '</span></td>'
            + '<td><code>' + esc(line.calculation_formula || '—') + '</code></td>'
            + '</tr>';
    },
    ccToggleDrill(gid) {
        document.querySelectorAll('#cc-queue-body tr.cc-drow.' + gid).forEach(tr => tr.classList.toggle('hidden'));
    },
    ccToggleGroup(gid, checked) {
        document.querySelectorAll('#cc-queue-body input.cc-q-row.' + gid).forEach(c => { c.checked = checked; });
    },
    async ccDecidePayout(id, action, reason) {
        const res = await this.req('decide_commission_payout', { cycle_id: id, action, override_reason: reason || '' });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در ثبت تصمیم', 'error'); return false; }
        return true;
    },
    async ccApprove(id) {
        if (await this.ccDecidePayout(id, 'APPROVE')) await this.ccRefreshOps();
    },
    async ccReject(id) {
        const ask = await Swal.fire({ title: 'دلیل رد', input: 'text', inputPlaceholder: 'مثلاً مغایرت مبلغ', showCancelButton: true, confirmButtonText: 'ثبت رد', cancelButtonText: 'انصراف' });
        const reason = String(ask.value || '').trim();
        if (!ask.isConfirmed || !reason) return;
        if (await this.ccDecidePayout(id, 'REJECT', reason)) await this.ccRefreshOps();
    },
    async ccApproveMany(ids) {
        let ok = 0;
        for (const id of ids) {
            const r = (this._ccRows || []).find(x => Number(x.id) === Number(id));
            if (!r || !this.ccIsActionable(r)) continue;
            if (await this.ccDecidePayout(id, 'APPROVE')) ok++;
        }
        Swal.fire('انجام شد', this.formatNumber(ok) + ' مورد بروزرسانی شد', 'success');
        await this.ccRefreshOps();
    },
    async ccBulkApprove() {
        const ids = Array.from(document.querySelectorAll('#cc-queue-body input.cc-q-row:checked')).map(c => Number(c.value));
        if (!ids.length) { Swal.fire('', 'ردیفی انتخاب نشده است', 'info'); return; }
        await this.ccApproveMany(ids);
    },
    async ccCalculateLedger(evt) {
        const range = this.ccDateRange();
        if (!this.ccRequireValidDateRange(range, true)) return;
        const months = this.ccJalaliMonthsInRange(range.date_from, range.date_to);
        const aggregateMonths = months;
        const btn = evt && evt.currentTarget ? evt.currentTarget : ((typeof event !== 'undefined' && event && event.currentTarget) ? event.currentTarget : null);
        const oldHtml = btn ? btn.innerHTML : '';
        if (!months.length) {
            Swal.fire('خطا', 'بازه تاریخ برای محاسبه معتبر نیست', 'error');
            return;
        }
        if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin ml-1"></i>در حال ساخت صف'; }
        try {
            let lineCount = 0;
            let directLineCount = 0;
            let scannedCount = 0;
            let withRuleCount = 0;
            let cycleCount = 0;
            const zeroDiagnostics = [];
            for (const month of months) {
                const calc = await this.req('calculate_commission_ledger', { billing_month: month });
                if (!calc || calc.status !== 'success') {
                    Swal.fire('خطا', (calc && calc.message) || 'خطا در محاسبه پورسانت', 'error');
                    return;
                }
                const calcResult = calc.result || {};
                lineCount += Number(calcResult.commission_lines_count || 0);
                directLineCount += Number(calcResult.direct_lines_count || 0);
                scannedCount += Number(calcResult.payments_scanned || 0);
                withRuleCount += Number(calcResult.payments_with_rule || 0);
                if (Number(calcResult.commission_lines_count || 0) === 0 && calcResult.diagnostics) {
                    zeroDiagnostics.push({ month, diagnostics: calcResult.diagnostics });
                }
            }
            for (const month of aggregateMonths) {
                const agg = await this.req('aggregate_commission_payout_cycles', { billing_month: month, payroll_day: 30 });
                if (!agg || agg.status !== 'success') {
                    Swal.fire('خطا', (agg && agg.message) || 'خطا در ساخت صف تایید', 'error');
                    return;
                }
                cycleCount += Array.isArray(agg.cycles) ? agg.cycles.length : 0;
            }
            await this.ccRefreshOps();
            // Distinguish "nothing qualifies" from "this range was already built".
            // calculate_commission_ledger is idempotent: an already-calculated month
            // produces 0 NEW lines even though it has existing cycles/lines. Surface
            // that so the zero cards aren't mistaken for an empty month.
            const builtMonthKeys = new Set(months);
            const existingRows = Array.isArray(this._ccRows)
                ? this._ccRows.filter(r => builtMonthKeys.has(this.ccJalaliMonthKey(r.billing_month)))
                : [];
            const existingCycleCount = existingRows.length;
            const existingLineCount = existingRows.reduce((sum, r) => sum + Number(r.commission_line_count || 0), 0);
            const alreadyBuilt = lineCount === 0 && existingCycleCount > 0;
            const emptyHint = lineCount > 0
                ? ''
                : (alreadyBuilt
                    ? '<p class="text-xs text-slate-400 mt-3 leading-7">این بازه از قبل دارای ' + this.formatNumber(existingCycleCount) + ' چرخه و ' + this.formatNumber(existingLineCount) + ' خط پورسانت است. محاسبه idempotent است؛ اجرای دوباره برای ماهی که قبلاً ساخته شده، خط جدیدی نمی‌سازد و عددهای صفر بالا یعنی «بدون مورد جدید» — نه خالی بودن ماه. پنل پایین فقط پرداخت‌های واجدشرایط‌نشده را نشان می‌دهد.</p>'
                    : '<p class="text-xs text-slate-400 mt-3 leading-7">اگر خطی ساخته نشد، معمولاً یعنی پرداخت‌ها هنوز واجد شرایط نیستند، قانون فعال ندارند، تاریخ قانون با پرداخت نمی‌خواند، یا دامنه محصول قانون شامل محصول پرداخت نیست.</p>');
            const rangeLabel = this.ccDateLabel(range.date_from) + ' تا ' + this.ccDateLabel(range.date_to);
            const buildCards = [
                ['پرداخت‌های پیدا شده از قوانین', this.formatNumber(scannedCount), 'پرداخت‌هایی که با یک قانون/تخصیص فعال برای بررسی محاسبه پیدا شده‌اند.'],
                ['پرداخت‌های دارای پورسانت', this.formatNumber(withRuleCount), 'پرداخت‌هایی که بعد از محاسبه، خروجی پورسانت مثبت ساخته‌اند.'],
                ['خط پورسانت ساخته‌شده', this.formatNumber(lineCount), 'هر پرداخت ممکن است بیش از یک خط بسازد؛ مثل سهم کارشناس، سرپرست یا مدیر.'],
                ['چرخه صف تأیید', this.formatNumber(cycleCount), 'گروه‌های قابل تأیید در صف پرداخت پورسانت.']
            ].map(item => '<div><small>' + this.escapeHtml(item[0]) + '</small><strong>' + this.escapeHtml(item[1]) + '</strong><span>' + this.escapeHtml(item[2]) + '</span></div>').join('');
            const diagnosticHtml = lineCount > 0 ? '' : this.ccRenderBuildDiagnostics(zeroDiagnostics);
            Swal.fire({
                icon: lineCount > 0 ? 'success' : (alreadyBuilt ? 'success' : 'info'),
                title: lineCount > 0
                    ? 'صف تأیید ساخته شد'
                    : (alreadyBuilt ? 'این بازه قبلاً محاسبه شده است' : 'چیزی برای ساخت صف پیدا نشد'),
                html: '<div class="cc-build-summary">' + buildCards + '</div>'
                    + '<p class="cc-build-note">این گزارش کل پرداخت‌های بازه را نمی‌گوید؛ فقط خروجی پرداخت‌هایی است که واقعاً وارد محاسبه شده‌اند. برای دیدن پرداخت‌های جا مانده و دلیل هرکدام، از «پیش‌نمایش محاسبه» استفاده کن.</p>'
                    + '<p class="cc-build-meta">بازه انتخابی: ' + this.escapeHtml(rangeLabel) + ' | خط مستقیم کارشناس: ' + this.formatNumber(directLineCount) + '</p>'
                    + diagnosticHtml
                    + emptyHint,
                customClass: { popup: 'cc-commission-build-modal' },
                confirmButtonText: 'باشه',
            });
        } finally {
            if (btn) { btn.disabled = false; btn.innerHTML = oldHtml; }
        }
    },
    ccRenderBuildDiagnostics(items) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const blocks = (items || []).filter(item => item && item.diagnostics).slice(0, 4).map(item => {
            const d = item.diagnostics || {};
            const summary = d.summary || {};
            const reasons = Array.isArray(d.reason_summary) ? d.reason_summary.slice(0, 5) : [];
            const people = Array.isArray(d.people) ? d.people.slice(0, 5) : [];
            const reasonHtml = reasons.length ? reasons.map(reason => '<li><span>' + esc(reason.label || reason.reason || '-') + '</span><b>' + esc(this.formatNumber(reason.count || 0)) + '</b></li>').join('') : '<li><span>دلیلی گزارش نشده</span><b>0</b></li>';
            const peopleHtml = people.length ? people.map(person => '<li><span>' + esc(person.owner_name || ('کاربر #' + (person.owner_user_id || '-'))) + '</span><small>' + esc(person.top_exclusion_reason || '-') + '</small><b>' + esc(this.formatNumber(person.top_exclusion_reason_count || 0)) + '</b></li>').join('') : '';
            return '<section class="cc-build-diagnostic">'
                + '<h4>' + esc(item.month || '') + '</h4>'
                + '<div class="cc-build-diagnostic-summary"><span>کل پرداخت: ' + esc(this.formatNumber(summary.total_payments || 0)) + '</span><span>قابل محاسبه: ' + esc(this.formatNumber(summary.included_payments || 0)) + '</span><span>جا مانده: ' + esc(this.formatNumber(summary.excluded_payments || 0)) + '</span></div>'
                + '<ul class="cc-build-diagnostic-list">' + reasonHtml + '</ul>'
                + (peopleHtml ? '<ul class="cc-build-diagnostic-people">' + peopleHtml + '</ul>' : '')
                + '</section>';
        }).join('');
        return blocks ? '<div class="cc-build-diagnostics">' + blocks + '</div>' : '';
    },
    ccRenderTrend(rows) {
        const el = document.getElementById('cc-trend-chart'); if (!el) return;
        const range = parseInt((document.getElementById('cc-trend-range') || {}).value || '12', 10) || 12;
        const map = {};
        (rows || []).forEach(r => {
            if (this.ccStatusBucket(r.status) === 'rejected') return;
            const m = String(r.billing_month || r.deposit_date || r.effective_date || '').slice(0, 7);
            if (!/^\d{4}-\d{2}$/.test(m)) return;
            map[m] = (map[m] || 0) + this.ccRowAmount(r);
        });
        const today = this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10);
        const endKey = Object.keys(map).sort().pop() || today.slice(0, 7);
        const cursor = new Date(Number(endKey.slice(0, 4)), Number(endKey.slice(5, 7)) - range, 1);
        const months = [];
        for (let i = 0; i < range; i++) {
            cursor.setMonth(cursor.getMonth() + 1);
            months.push(String(cursor.getFullYear()).padStart(4, '0') + '-' + String(cursor.getMonth() + 1).padStart(2, '0'));
        }
        const data = months.map(m => Math.round(map[m] || 0));
        const monthLabels = months.map(m => this.ccTrendMonthLabel(m));
        if (this._ccTrendChart) { try { this._ccTrendChart.destroy(); } catch (e) {} this._ccTrendChart = null; }
        if (!data.some(v => v > 0)) { el.innerHTML = '<div class="text-gray-500 text-sm py-8 text-center">برای بازه انتخاب‌شده داده پورسانت وجود ندارد.</div>'; return; }
        if (typeof ApexCharts === 'undefined') {
            const max = Math.max.apply(null, data.concat([1]));
            el.innerHTML = '<div class="cc-trend-fallback">' + months.map((m, idx) => {
                const v = data[idx] || 0;
                const h = Math.max(8, Math.round((v / max) * 100));
                return '<div class="bar"><div class="value">' + this.escapeHtml(this.formatMoney(v).replace(' تومان', '')) + '</div><div class="fill" style="height:' + h + '%"></div><div class="month">' + this.escapeHtml(monthLabels[idx] || m) + '</div></div>';
            }).join('') + '</div>';
            return;
        }
        el.innerHTML = '';
        this._ccTrendChart = new ApexCharts(el, {
            chart: { type: 'bar', height: 240, fontFamily: 'Vazirmatn, sans-serif', toolbar: { show: false }, background: 'transparent' },
            theme: { mode: 'dark' },
            series: [{ name: 'پورسانت', data }],
            xaxis: { categories: monthLabels, labels: { style: { colors: '#94a3b8' } } },
            yaxis: { labels: { style: { colors: '#94a3b8' }, formatter: (v) => this.formatNumber(Math.round(v)) } },
            colors: ['#eab308'],
            plotOptions: { bar: { borderRadius: 6, columnWidth: '45%' } },
            dataLabels: { enabled: false },
            grid: { borderColor: '#1f2937' },
            tooltip: { theme: 'dark', y: { formatter: (v) => this.formatMoney(v) } }
        });
        this._ccTrendChart.render();
    },
    ccTrendMonthLabel(monthKey) {
        const raw = String(monthKey || '').trim();
        if (!/^\d{4}-\d{2}$/.test(raw)) return raw;
        const date = raw + '-01';
        const jalali = typeof toPersianDate === 'function' ? (toPersianDate(date) || '') : '';
        if (/^\d{4}\/\d{2}\/\d{2}$/.test(jalali)) {
            return jalali.slice(0, 7);
        }
        return raw;
    },
    async ccEnsureSetup(force) {
        if (!force && this._ccSetup) return this._ccSetup;
        const [uRes, sRes, pRes] = await Promise.all([
            this.req('get_commission_setup_data', {}),
            this.req('get_allowed_services', {}),
            this.req('get_products', {})
        ]);
        const users = (uRes && uRes.status === 'success' && Array.isArray(uRes.users)) ? uRes.users : [];
        const servicesRaw = (sRes && sRes.status === 'success') ? sRes.services : [];
        const services = Array.isArray(servicesRaw)
            ? servicesRaw
            : Object.keys(servicesRaw || {}).map(key => ({ id: key, value: key, title: servicesRaw[key] }));
        const products = (pRes && pRes.status === 'success' && Array.isArray(pRes.products)) ? pRes.products : [];
        this._ccSetup = { users, services, products };
        return this._ccSetup;
    },
    ccAgents() {
        return (this._ccSetup && this._ccSetup.users ? this._ccSetup.users : [])
            .filter(u => !('is_active' in u) || Number(u.is_active) !== 0);
    },
    ccUserName(u) {
        return String(u.display_name || (`${u.first_name || ''} ${u.last_name || ''}`).trim() || ('کاربر #' + u.id));
    },
    ccAgentOptions(selectedIds) {
        const sel = new Set((selectedIds || []).map(Number));
        return this.ccAgents().map(u => `<option value="${u.id}" ${sel.has(Number(u.id)) ? 'selected' : ''}>${this.escapeHtml(this.ccUserName(u))}</option>`).join('');
    },
    ccServiceOptions() {
        return (this._ccSetup && this._ccSetup.services ? this._ccSetup.services : []).map((srv, idx) => {
            if (typeof srv === 'string') return `<option value="service_${idx + 1}">${this.escapeHtml(srv)}</option>`;
            const id = String(srv.value || srv.id || srv.title || ('service_' + (idx + 1)));
            const title = String(srv.title || srv.name || srv.label || srv.value || ('خدمت ' + (idx + 1)));
            const pids = String(srv.product_ids || srv.product_id || srv.id || '');
            return `<option value="${this.escapeHtml(id)}" data-product-ids="${this.escapeHtml(pids)}">${this.escapeHtml(title)}</option>`;
        }).join('');
    },
    ccProductOptions() {
        const products = (this._ccSetup && Array.isArray(this._ccSetup.products)) ? this._ccSetup.products : [];
        return products
            .filter(p => Number(p && p.id || 0) > 0)
            .map(p => `<option value="${Number(p.id)}">${this.escapeHtml(String(p.name || p.title || ('محصول #' + p.id)))}</option>`)
            .join('');
    },
    ccProductData() {
        return ((this._ccSetup && Array.isArray(this._ccSetup.products)) ? this._ccSetup.products : [])
            .filter(p => Number(p && p.id || 0) > 0)
            .map(p => ({ id: Number(p.id), name: String(p.name || p.title || ('محصول #' + p.id)) }));
    },
    ccColorFor(seed) {
        const p = ['#eab308', '#14b8a6', '#10b981', '#3b82f6', '#f59e0b', '#22d3ee', '#a3e635', '#fb923c'];
        let n = Number(seed);
        if (!Number.isFinite(n)) n = String(seed).split('').reduce((a, c) => a + c.charCodeAt(0), 0);
        return p[Math.abs(n) % p.length];
    },
    ccAvatar(u, lg) {
        const nm = this.ccUserName(u); const ini = (nm.trim()[0] || '?');
        return '<span class="cc-av' + (lg ? ' lg' : '') + '" style="background:' + this.ccColorFor(u.id || nm) + '">' + this.escapeHtml(ini) + '</span>';
    },
    ccRenderTierChart() {
        const el = document.getElementById('cc-tier-chart'); if (!el) return;
        const tiers = this.ccCollectTiers();
        const countEl = document.getElementById('cc-tier-count');
        if (countEl) countEl.textContent = (tiers.length ? this.formatNumber(tiers.length) : '0') + ' پله';
        if (!tiers.length) {
            el.innerHTML = '<div class="cc-tier-empty">هنوز پله‌ای برای نمایش تعریف نشده است.</div>';
            return;
        }
        const max = Math.max.apply(null, tiers.map(t => Number(t.commission_percent) || 0).concat([1]));
        el.innerHTML = tiers.map(t => {
            const pct = Number(t.commission_percent) || 0;
            const h = Math.max(6, Math.round(pct / max * 100));
            const min = this.formatNumber(Number(t.min_amount || 0));
            const cap = t.max_amount !== '' ? this.formatNumber(Number(t.max_amount || 0)) : 'بی سقف';
            const rg = min + ' - ' + cap;
            return '<div class="col"><div class="bk" style="height:' + h + '%"></div><div class="rt">' + this.escapeHtml(String(pct)) + '٪</div><div class="rg">' + this.escapeHtml(rg) + '</div></div>';
        }).join('');
    },
    ccComputeCommission(amount) {
        const type = (document.getElementById('cc-rule-type') || {}).value || 'FIXED_RULE';
        amount = Number(String(amount == null ? '' : amount).replace(/,/g, '')) || 0;
        if (amount <= 0) return 0;
        if (type === 'FIXED_RULE') {
            const value = this.ccParseDecimal((document.getElementById('cc-rule-rate') || {}).value || 0);
            return this.ccFixedValueMode() === 'amount' ? value : (amount * value / 100);
        }
        const tiers = this.ccCollectTiers().map(t => ({ min: Number(t.min_amount) || 0, max: t.max_amount === '' ? Infinity : (Number(t.max_amount) || Infinity), pct: Number(t.commission_percent) || 0 })).sort((a, b) => a.min - b.min);
        if (!tiers.length) return 0;
        const mode = this._ccTierMode || 'marginal';
        if (mode === 'retroactive') { let r = 0; for (const t of tiers) { if (amount >= t.min) r = t.pct; } return amount * r / 100; }
        let total = 0;
        for (const t of tiers) { if (amount > t.min) { const upper = Math.min(amount, t.max); const band = upper - t.min; if (band > 0) total += band * t.pct / 100; } }
        return total;
    },
    ccWhatIf() {
        if (typeof this.ccRefreshTierRows === 'function') this.ccRefreshTierRows();
        this.ccRenderTierChart();
        const res = document.getElementById('cc-whatif-res'); if (!res) return;
        const inp = document.getElementById('cc-whatif-amount');
        const c = this.ccComputeCommission(inp ? inp.value : 0);
        res.textContent = c > 0 ? (this.formatMoney(Math.round(c)) + ' تومان') : '—';
    },
    ccSetTierMode(m) {
        this._ccTierMode = m;
        document.querySelectorAll('#cc-tier-mode span').forEach(s => s.classList.toggle('active', s.getAttribute('data-mode') === m));
        this.ccWhatIf();
    },
    ccAddAgentToTeam(supId) {
        const sel = document.getElementById('cc-sup-new'); if (sel) sel.value = String(supId > 0 ? supId : '');
        const u = document.getElementById('cc-sup-user'); if (u) { try { u.focus(); } catch (e) {} u.scrollIntoView({ behavior: 'smooth', block: 'center' }); }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'info', title: 'کارشناس را از فهرست انتخاب و ذخیره کنید', showConfirmButton: false, timer: 2400 });
    },
    ccAddTeamHint() {
        Swal.fire('ساخت تیم', 'برای ساخت تیم جدید، در پنل «تغییر سرپرست» یک کارشناس را به یک سرپرستِ جدید منتسب کنید؛ تیم به‌صورت خودکار ساخته می‌شود.', 'info');
    },
    ccCollectTiers() {
        return Array.from(document.querySelectorAll('#cc-tier-rows .cc-tier-row')).map(r => ({
            min_amount: (r.querySelector('.cc-tier-min') ? r.querySelector('.cc-tier-min').value : '').replace(/,/g, '').trim(),
            max_amount: (r.querySelector('.cc-tier-max') ? r.querySelector('.cc-tier-max').value : '').replace(/,/g, '').trim(),
            commission_percent: (r.querySelector('.cc-tier-pct') ? r.querySelector('.cc-tier-pct').value : '').trim()
        })).filter(t => t.min_amount !== '' && t.commission_percent !== '');
    },
    ccRenderRuleProductPicker() {
        const select = document.getElementById('cc-rule-products');
        const picker = document.getElementById('cc-rule-product-picker');
        if (!select || !picker) return;
        if (!select.options.length) select.innerHTML = this.ccProductOptions();
        const allBox = document.getElementById('cc-rule-products-all');
        const all = allBox ? !!allBox.checked : true;
        const selected = new Set(Array.from(select.selectedOptions || []).map(o => Number(o.value || 0)).filter(Boolean));
        const options = Array.from(select.options || []);
        if (!options.length) {
            picker.innerHTML = '<div class="text-gray-500 text-xs py-2">محصولی برای انتخاب ثبت نشده است.</div>';
            return;
        }
        picker.innerHTML = options.map(option => {
            const id = Number(option.value || 0);
            const checked = all || selected.has(id);
            return '<label class="cc-product-choice">'
                + '<span>' + this.escapeHtml(option.textContent || option.value || '') + '</span>'
                + '<input type="checkbox" value="' + id + '" ' + (checked ? 'checked' : '') + ' onchange="app.ccSyncRuleProductsFromPicker()">'
                + '</label>';
        }).join('');
    },
    ccToggleAllRuleProducts(checked) {
        const select = document.getElementById('cc-rule-products');
        if (checked && select) Array.from(select.options || []).forEach(o => { o.selected = false; });
        this.ccRenderRuleProductPicker();
    },
    ccSyncRuleProductsFromPicker() {
        const select = document.getElementById('cc-rule-products');
        if (!select) return;
        const picked = new Set(Array.from(document.querySelectorAll('#cc-rule-product-picker input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean));
        Array.from(select.options || []).forEach(option => { option.selected = picked.has(Number(option.value || 0)); });
        const allBox = document.getElementById('cc-rule-products-all');
        if (allBox) allBox.checked = picked.size === 0;
        this.ccRenderRuleProductPicker();
    },
    ccSelectedRuleProductIds() {
        const allBox = document.getElementById('cc-rule-products-all');
        if (allBox && allBox.checked) return [];
        this.ccSyncRuleProductsFromPicker();
        return Array.from(document.getElementById('cc-rule-products') ? document.getElementById('cc-rule-products').selectedOptions : [])
            .map(o => Number(o.value || 0)).filter(v => v > 0);
    },
    ccSetRuleProductIds(ids) {
        const selected = new Set((ids || []).map(Number).filter(v => v > 0));
        const select = document.getElementById('cc-rule-products');
        if (select) Array.from(select.options || []).forEach(option => { option.selected = selected.has(Number(option.value || 0)); });
        const allBox = document.getElementById('cc-rule-products-all');
        if (allBox) allBox.checked = selected.size === 0;
        this.ccRenderRuleProductPicker();
    },
    ccRuleProductIds(rule) {
        if (!rule) return [];
        const raw = rule.requested_services;
        let services = [];
        if (Array.isArray(raw)) services = raw;
        else {
            try { services = JSON.parse(raw || '[]'); } catch (_) { services = []; }
        }
        return services.flatMap(item => {
            if (item == null || String(item).toUpperCase() === 'ALL') return [];
            if (typeof item === 'object') {
                const ids = [];
                ['product_id', 'id'].forEach(key => { if (Number(item[key] || 0) > 0) ids.push(Number(item[key])); });
                String(item.product_ids || '').split(',').forEach(v => { if (Number(v || 0) > 0) ids.push(Number(v)); });
                return ids;
            }
            return Number(item || 0) > 0 ? [Number(item)] : [];
        }).filter(v => v > 0);
    },
    ccRenderRuleProductScope(rule, ruleId, limit = 3) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const scope = this.getCommissionProductScopeItems(rule);
        if (scope.all) {
            return '<div class="cc-rule-scope"><i class="fa-solid fa-boxes-stacked"></i><span class="cc-rule-scope-chip all">' + esc(scope.label) + '</span></div>';
        }
        const items = Array.isArray(scope.items) ? scope.items : [];
        if (!items.length) {
            return '<div class="cc-rule-scope"><i class="fa-solid fa-boxes-stacked"></i><span class="cc-rule-scope-chip">' + esc(scope.label || 'بدون محصول') + '</span></div>';
        }
        const visible = items.slice(0, limit).map(item => '<span class="cc-rule-scope-chip">' + esc(item) + '</span>').join('');
        const more = Math.max(0, items.length - limit);
        const moreBtn = more > 0
            ? '<button type="button" class="cc-rule-scope-more" data-cc-action="rule-products-modal" data-rule-id="' + Number(ruleId || 0) + '">+' + this.formatNumber(more) + '</button>'
            : '';
        return '<div class="cc-rule-scope"><i class="fa-solid fa-boxes-stacked"></i>' + visible + moreBtn + '</div>';
    },
    ccShowRuleProductsModal(ruleId) {
        const rule = (this._ccRules || []).find(item => Number(item.id || 0) === Number(ruleId || 0));
        if (!rule) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const scope = this.getCommissionProductScopeItems(rule);
        const title = String(rule.title || this.getCommissionRuleAmountLabel(rule) || 'محصولات قانون');
        const body = scope.all
            ? '<div class="cc-rule-products-empty">این قانون روی همه محصولات اعمال می‌شود.</div>'
            : '<div class="cc-rule-products-modal-list">' + (scope.items || []).map(item => '<span>' + esc(item) + '</span>').join('') + '</div>';
        Swal.fire({
            title: 'محصولات قانون',
            html: '<div class="cc-rule-products-modal-body" dir="rtl"><h4>' + esc(title) + '</h4>' + body + '</div>',
            width: 680,
            showConfirmButton: false,
            showCloseButton: true,
            customClass: { popup: 'cc-rule-products-modal' }
        });
    },
    async ccLoadRules() {
        await this.ccEnsureSetup();
        const prodSel = document.getElementById('cc-rule-products');
        if (prodSel && !prodSel.options.length) prodSel.innerHTML = this.ccProductOptions();
        this.ccRenderRuleProductPicker();
        if (typeof this.initJalaliRangeFilter === 'function') this.initJalaliRangeFilter('cc-rule-from', 'cc-rule-to', { allowFuture: true });
        this.ccOnRuleTypeChange();
        const res = await this.req('get_commission_rules', {});
        const rules = (res && res.status === 'success' && Array.isArray(res.rules)) ? res.rules : [];
        this._ccRules = rules;
        const list = document.getElementById('cc-rules-list'); if (!list) return;
        const ov = document.getElementById('cc-rules-overlap');
        if (ov) {
            const byScope = {};
            rules.forEach(r => { const s = this.getCommissionProductScopeLabel(r); (byScope[s] = byScope[s] || []).push(String(r.title || this.getCommissionRuleAmountLabel(r)).replace(/\n/g, ' ')); });
            const dup = Object.keys(byScope).filter(s => byScope[s].length >= 2);
            if (dup.length) { ov.innerHTML = dup.map(s => '<div class="cc-overlap">⚠️ تداخل احتمالی روی «' + this.escapeHtml(s) + '»: ' + this.escapeHtml(byScope[s].join('، ')) + ' — یکی را غیرفعال یا محصولی کن.</div>').join(''); ov.classList.remove('hidden'); }
            else { ov.innerHTML = ''; ov.classList.add('hidden'); }
        }
        if (!rules.length) { list.innerHTML = '<div class="text-gray-500 text-sm">قانونی ثبت نشده است.</div>'; return; }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        list.innerHTML = rules.map(r => {
            const isTiered = String(r.rule_type || '').toUpperCase() !== 'FIXED_RULE';
            const name = String(r.title || (this.getCommissionRuleTypeLabel(r.rule_type) + ' - ' + this.getCommissionRuleAmountLabel(r)));
            const validity = this.ccDateRangeLabel(r.valid_from || '', r.valid_to || '');
            const tiers = Array.isArray(r.tiers) ? r.tiers : [];
            let viz;
            if (isTiered && tiers.length) {
                const mx = Math.max.apply(null, tiers.map(t => Number(t.commission_percent != null ? t.commission_percent : t.allocated_commission_percent) || 0).concat([1]));
                viz = '<div class="cc-card-bars">' + tiers.map(t => { const p = Number(t.commission_percent != null ? t.commission_percent : t.allocated_commission_percent) || 0; return '<i style="height:' + Math.max(10, Math.round(p / mx * 100)) + '%"></i>'; }).join('') + '</div>';
            } else {
                const fixedAmount = Number(r.base_amount || r.fixed_budget_amount || 0);
                const rate = Number(r.reward_percentage || r.base_rate_percent || 0);
                viz = fixedAmount > 0
                    ? '<span class="cc-rule-rate-badge">' + esc(this.formatMoney(fixedAmount)) + '</span>'
                    : '<span class="cc-rule-rate-badge">' + rate.toFixed(2) + '٪</span>';
            }
            const typeBadge = isTiered ? '<span class="cc-badge cc-b-tiered">پلکانی</span>' : '<span class="cc-badge cc-b-pending">ثابت</span>';
            const rid = Number(r.id || 0);
            const productScopeHtml = this.ccRenderRuleProductScope(r, rid);
            const ruleActive = Number(r.is_active != null ? r.is_active : 1) !== 0 && String(r.status || 'active').toLowerCase() !== 'inactive';
            const rates = tiers.map(t => Number(t.commission_percent != null ? t.commission_percent : t.allocated_commission_percent) || 0).filter(v => v > 0);
            const fixedAmount = Number(r.base_amount || r.fixed_budget_amount || 0);
            const minRate = rates.length ? Math.min.apply(null, rates) : Number(r.reward_percentage || r.base_rate_percent || 0);
            const maxRate = rates.length ? Math.max.apply(null, rates) : minRate;
            const rateLabel = isTiered ? (minRate.toFixed(2) + '٪ تا ' + maxRate.toFixed(2) + '٪') : (fixedAmount > 0 ? this.formatMoney(fixedAmount) : (maxRate.toFixed(2) + '٪'));
            const tierLabel = isTiered ? (this.formatNumber(tiers.length) + ' پله') : (fixedAmount > 0 ? 'مبلغ ثابت' : 'درصد ثابت');
            return '<article class="cc-rule-card' + (ruleActive ? '' : ' is-inactive') + '">'
                + '<div class="cc-rule-identity">'
                + '<div class="cc-rule-viz">' + viz + '</div>'
                + '<div class="cc-rule-name-wrap">'
                + '<div class="cc-rule-name">' + esc(name) + '</div>'
                + '<div class="cc-rule-meta">' + typeBadge + productScopeHtml + '<span><i class="fa-regular fa-calendar"></i>' + esc(validity) + '</span></div>'
                + '</div>'
                + '</div>'
                + '<div class="cc-rule-stats">'
                + '<div class="cc-rule-stat"><small>ساختار</small><strong>' + esc(tierLabel) + '</strong></div>'
                + '<div class="cc-rule-stat"><small>نرخ</small><strong>' + esc(rateLabel) + '</strong></div>'
                + '</div>'
                + '<div class="cc-rule-actions"><button type="button" class="edit" title="ویرایش" onclick="app.ccEditRule(' + rid + ')"><i class="fa-solid fa-pen-to-square"></i></button>'
                + '<button type="button" class="delete" title="حذف" onclick="app.ccDeleteRule(' + rid + ')"><i class="fa-solid fa-trash"></i></button></div>'
                + '<div class="cc-rule-state"><span class="cc-switch' + (ruleActive ? ' on' : '') + '" title="فعال/غیرفعال" onclick="app.ccToggleRule(' + rid + ',' + (ruleActive ? 0 : 1) + ')"></span><span class="cc-badge ' + (ruleActive ? 'cc-b-approved' : 'cc-b-rejected') + '">' + (ruleActive ? 'فعال' : 'غیرفعال') + '</span></div>'
                + '</article>';
        }).join('');
    },
    async ccEditRule(id) {
        const r = (this._ccRules || []).find(x => Number(x.id) === Number(id)); if (!r) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const isFixed = String(r.rule_type || '').toUpperCase() === 'FIXED_RULE';
        const fixedAmount = Number(r.base_amount || r.fixed_budget_amount || 0);
        const productIds = new Set(this.ccRuleProductIds(r));
        const products = this.ccProductData();
        const allProducts = productIds.size === 0;
        const productPicker = products.length ? products.map(product => {
            const checked = allProducts || productIds.has(Number(product.id));
            return '<label class="cc-product-choice"><span>' + esc(product.name) + '</span>'
                + '<input type="checkbox" value="' + Number(product.id) + '" ' + (checked ? 'checked' : '') + ' onchange="app.ccSyncEditRuleProducts()">'
                + '</label>';
        }).join('') : '<div class="text-gray-500 text-xs py-2">محصولی برای انتخاب ثبت نشده است.</div>';
        const fixedMode = fixedAmount > 0 ? 'amount' : 'percent';
        const fixedValue = fixedAmount > 0 ? this.ccFormatPlainAmount(fixedAmount) : String(Number(r.reward_percentage || r.base_rate_percent || 0) || '');
        const tiers = Array.isArray(r.tiers) ? r.tiers : [];
        const html = '<div class="cc-rule-edit-body" dir="rtl">'
            + '<div class="cc-team-modal-field"><label>عنوان قانون</label><input id="cc-edit-rule-title" type="text" value="' + esc(r.title || '') + '"></div>'
            + '<div class="cc-team-modal-grid">'
            + '<div class="cc-team-modal-field"><label>نوع قانون</label><select id="cc-edit-rule-type" onchange="app.ccRefreshEditRuleTypeUi()"><option value="TIERED_RULE"' + (!isFixed ? ' selected' : '') + '>پلکانی</option><option value="FIXED_RULE"' + (isFixed ? ' selected' : '') + '>ثابت</option></select></div>'
            + '<div id="cc-edit-rule-fixed-mode-wrap" class="cc-team-modal-field"><label>حالت قانون ثابت</label><select id="cc-edit-rule-fixed-mode"><option value="percent"' + (fixedMode === 'percent' ? ' selected' : '') + '>درصد ثابت</option><option value="amount"' + (fixedMode === 'amount' ? ' selected' : '') + '>مبلغ ثابت</option></select></div>'
            + '</div>'
            + '<div class="cc-team-modal-grid">'
            + '<div class="cc-team-modal-field"><label>معتبر از</label><input id="cc-edit-rule-from" type="text" value="' + esc(r.valid_from ? this.ccDateLabel(r.valid_from) : '') + '" placeholder="بدون محدودیت"></div>'
            + '<div class="cc-team-modal-field"><label>معتبر تا</label><input id="cc-edit-rule-to" type="text" value="' + esc(r.valid_to ? this.ccDateLabel(r.valid_to) : '') + '" placeholder="بدون پایان"></div>'
            + '</div>'
            + '<div id="cc-edit-rule-fixed-wrap" class="cc-team-modal-field"><label>مقدار قانون ثابت</label><input id="cc-edit-rule-rate" type="text" inputmode="decimal" value="' + esc(fixedValue) + '" oninput="app.ccFormatEditRuleRate(this)" placeholder="مثلا 4"></div>'
            + '<div id="cc-edit-rule-tier-wrap">'
            + '<div class="cc-tier-builder-head"><div><div class="cc-tier-kicker">تعریف پلکان</div><span class="cc-tier-title">پله‌های مبلغ و نرخ پورسانت</span><p>حد شروع پله بعدی به صورت خودکار فقط یک تومان بعد از سقف پله قبلی می‌شود.</p></div><button type="button" class="cc-tier-add" onclick="app.ccAddEditRuleTierRow()"><i class="fa-solid fa-plus"></i> افزودن پله</button></div>'
            + '<div class="cc-tier-column-head"><span>بازه مبلغ</span><span>نرخ</span><span></span></div>'
            + '<div id="cc-edit-tier-rows"></div>'
            + '</div>'
            + '<div class="cc-team-modal-field"><label>محصولات قانون</label>'
            + '<label class="cc-product-all"><span>همه محصولات</span><input id="cc-edit-rule-products-all" type="checkbox" ' + (allProducts ? 'checked' : '') + ' onchange="app.ccToggleEditRuleProductsAll(this.checked)"></label>'
            + '<div id="cc-edit-rule-products" class="cc-product-picker">' + productPicker + '</div>'
            + '</div>'
            + '</div>';
        const result = await Swal.fire({
            title: 'ویرایش قانون پورسانت',
            html,
            width: 980,
            showCancelButton: true,
            showCloseButton: true,
            confirmButtonText: 'ذخیره قانون',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal cc-rule-edit-modal' },
            didOpen: () => {
                if (typeof this.initJalaliDatePickers === 'function') this.initJalaliDatePickers([
                    document.getElementById('cc-edit-rule-from'),
                    document.getElementById('cc-edit-rule-to')
                ].filter(Boolean), { allowFuture: true, linkRange: true });
                const rows = document.getElementById('cc-edit-tier-rows');
                if (rows) rows.innerHTML = '';
                tiers.forEach((t, idx) => this.ccAddEditRuleTierRow((t.min_amount === null || t.min_amount === undefined || String(t.min_amount).trim() === '') && idx === 0 ? '0' : t.min_amount, t.max_amount, (t.commission_percent != null ? t.commission_percent : t.allocated_commission_percent), { quiet: true }));
                if (!tiers.length) {
                    this.ccAddEditRuleTierRow('', '', '', { quiet: true });
                    this.ccAddEditRuleTierRow('', '', '', { quiet: true });
                }
                this.ccToggleEditRuleProductsAll(allProducts);
                this.ccRefreshEditRuleTypeUi();
                this.ccRefreshEditRuleTierRows();
            },
            preConfirm: () => {
                const type = String((document.getElementById('cc-edit-rule-type') || {}).value || 'TIERED_RULE');
                const title = String((document.getElementById('cc-edit-rule-title') || {}).value || '').trim();
                const from = String((document.getElementById('cc-edit-rule-from') || {}).value || '').trim();
                const to = String((document.getElementById('cc-edit-rule-to') || {}).value || '').trim();
                const fromG = from ? this.ccToGregorianDate(from) : '';
                const toG = to ? this.ccToGregorianDate(to) : '';
                if ((from && !fromG) || (to && !toG)) {
                    Swal.showValidationMessage('تاریخ اعتبار را به شکل شمسی معتبر وارد کنید');
                    return false;
                }
                if (fromG && toG && toG < fromG) {
                    Swal.showValidationMessage('«معتبر تا» نمی‌تواند قبل از «معتبر از» باشد');
                    return false;
                }
                const fixedModeValue = String((document.getElementById('cc-edit-rule-fixed-mode') || {}).value || 'percent');
                const fixedValueRaw = this.ccNormalizeDecimalInput((document.getElementById('cc-edit-rule-rate') || {}).value || '');
                const editTiers = this.ccCollectEditRuleTiers();
                if (type === 'TIERED_RULE' && editTiers.length < 2) {
                    Swal.showValidationMessage('برای قانون پلکانی حداقل دو پله تعریف کنید');
                    return false;
                }
                if (type === 'FIXED_RULE' && this.ccParseDecimal(fixedValueRaw || 0) <= 0) {
                    Swal.showValidationMessage('مقدار قانون ثابت را وارد کنید');
                    return false;
                }
                return { type, title, from, to, fixedModeValue, fixedValueRaw, editTiers, productIds: this.ccSelectedEditRuleProductIds() };
            }
        });
        if (!result.isConfirmed || !result.value) return;
        const v = result.value;
        const payload = {
            id: Number(r.id || 0),
            rule_type: v.type,
            salesperson_user_id: '',
            target_users: JSON.stringify([]),
            requested_services: JSON.stringify(Array.from(new Set(v.productIds || []))),
            base_amount: v.type === 'FIXED_RULE' && v.fixedModeValue === 'amount' ? String(v.fixedValueRaw) : '0',
            reward_percentage: v.type === 'TIERED_RULE' ? String((v.editTiers[0] && v.editTiers[0].commission_percent) || '0') : (v.fixedModeValue === 'percent' ? String(v.fixedValueRaw) : '0'),
            tiers: JSON.stringify(v.type === 'TIERED_RULE' ? v.editTiers : []),
            title: v.title,
            valid_from: v.from,
            valid_to: v.to
        };
        const res = await this.req('store_commission_rule', payload);
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در ذخیره قانون', 'error'); return; }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون ذخیره شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadRules();
    },
    async ccDeleteRule(id) {
        const c = await Swal.fire({ title: 'حذف قانون', text: 'این قانون حذف شود؟', icon: 'warning', showCancelButton: true, confirmButtonText: 'حذف', cancelButtonText: 'لغو' });
        if (!c.isConfirmed) return;
        const res = await this.req('delete_commission_rule', { rule_id: Number(id) });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در حذف قانون', 'error'); return; }
        await this.ccLoadRules();
    },
    async ccToggleRule(id, active) {
        const res = await this.req('toggle_commission_rule', { rule_id: Number(id), is_active: active ? 1 : 0 });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تغییر وضعیت قانون', 'error'); return; }
        await this.ccLoadRules();
    },
    ccActiveAssignment(uid) {
        const list = (this._ccHierAssigns || []).filter(a => Number(a.user_id) === Number(uid) && !a.effective_to);
        if (!list.length) return null;
        return list.slice().sort((a, b) => {
            const dateCmp = String(b.effective_from || '').localeCompare(String(a.effective_from || ''));
            return dateCmp || (Number(b.id || 0) - Number(a.id || 0));
        })[0];
    },
    ccTeamSupervisorRate(supId) {
        supId = Number(supId || 0);
        if (!supId) return '';
        const rows = (this._ccHierAssigns || [])
            .filter(a => !a.effective_to && Number(a.supervisor_user_id || 0) === supId && a.supervisor_commission_rate_percent !== null && a.supervisor_commission_rate_percent !== '');
        if (!rows.length) return '';
        const row = rows.slice().sort((a, b) => {
            const dateCmp = String(b.effective_from || '').localeCompare(String(a.effective_from || ''));
            return dateCmp || (Number(b.id || 0) - Number(a.id || 0));
        })[0];
        return row ? String(row.supervisor_commission_rate_percent || '') : '';
    },
    ccTeamManagerRate(managerId) {
        managerId = Number(managerId || 0);
        if (!managerId) return '';
        const rows = (this._ccHierAssigns || [])
            .filter(a => !a.effective_to && Number(a.manager_user_id || 0) === managerId && a.manager_commission_rate_percent !== null && a.manager_commission_rate_percent !== '');
        if (!rows.length) return '';
        const row = rows.slice().sort((a, b) => {
            const dateCmp = String(b.effective_from || '').localeCompare(String(a.effective_from || ''));
            return dateCmp || (Number(b.id || 0) - Number(a.id || 0));
        })[0];
        return row ? String(row.manager_commission_rate_percent || '') : '';
    },
    ccFormatPercentValue(value) {
        const n = Number(String(value == null ? '' : value).replace(',', '.'));
        if (!Number.isFinite(n)) return '';
        return n.toLocaleString('fa-IR', { maximumFractionDigits: 4 });
    },
    async ccLoadTeams() {
        const res = await this.req('get_users_list', {});
        const users = (res && res.status === 'success' && Array.isArray(res.users)) ? res.users : [];
        const assigns = (res && Array.isArray(res.hierarchy_assignments)) ? res.hierarchy_assignments : [];
        this._ccUsers = users; this._ccHierAssigns = assigns;
        const byId = {}; users.forEach(u => { byId[Number(u.id)] = u; }); this._ccUsersById = byId;
        let ruleAssigns = [], rules = [];
        try { const rr = await this.req('get_commission_rules', {}); ruleAssigns = (rr && Array.isArray(rr.assignments)) ? rr.assignments : []; rules = (rr && Array.isArray(rr.rules)) ? rr.rules : []; } catch (e) {}
        this._ccRules = rules; this._ccRuleAssigns = ruleAssigns;
        this._ccRuleTitle = {}; rules.forEach(r => { this._ccRuleTitle[Number(r.id)] = String(r.title || this.getCommissionRuleAmountLabel(r)).replace(/\n/g, ' '); });
        const members = users.filter(u => !('is_active' in u) || Number(u.is_active) !== 0);
        this._ccTeamMembers = members;
        await this.ccRepairTeamRuleAssignments();
        this.ccHydrateTeamControls();
        this.ccRenderTeamBoard();
        const eff = document.getElementById('cc-team-eff');
        if (eff && !eff.value) eff.value = typeof toPersianDate === 'function' ? (toPersianDate(this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10)) || '') : '';
        if (eff && typeof this.initJalaliDatePickers === 'function') this.initJalaliDatePickers([eff], { allowFuture: true });
    },
    ccHydrateTeamControls() {
        const allActive = this._ccTeamMembers || [];
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const sup = document.getElementById('cc-team-supervisor');
        const members = document.getElementById('cc-team-members');
        const userOptions = allActive.map(u => '<option value="' + Number(u.id) + '">' + esc(this.ccUserName(u)) + '</option>').join('');
        if (sup) sup.innerHTML = '<option value="">انتخاب سرپرست…</option>' + userOptions;
        if (members) members.innerHTML = userOptions;
        this.ccRenderTeamMemberPicker();
    },
    ccRenderTeamMemberPicker() {
        const wrap = document.getElementById('cc-team-members-picker');
        const select = document.getElementById('cc-team-members');
        if (!wrap) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const selected = new Set(Array.from(select ? select.selectedOptions : []).map(o => Number(o.value || 0)));
        const q = String((document.getElementById('cc-team-member-filter') || {}).value || '').trim();
        const supId = Number((document.getElementById('cc-team-supervisor') || {}).value || 0);
        if (select && supId) Array.from(select.options || []).forEach(option => { if (Number(option.value || 0) === supId) option.selected = false; });
        const users = (this._ccTeamMembers || []).filter(u => Number(u.id || 0) !== supId).filter(u => !q || this.ccUserName(u).indexOf(q) !== -1);
        if (!users.length) {
            wrap.innerHTML = '<div class="text-gray-500 text-xs p-2">کارشناسی برای نمایش وجود ندارد.</div>';
            this.ccUpdateTeamMemberPickerCount();
            return;
        }
        wrap.innerHTML = users.map(u => {
            const id = Number(u.id || 0);
            return '<label class="cc-team-member-choice"><input type="checkbox" value="' + id + '" ' + (selected.has(id) ? 'checked' : '') + ' onchange="app.ccSyncTeamMembersFromPicker()"><span>' + esc(this.ccUserName(u)) + '</span></label>';
        }).join('');
        this.ccUpdateTeamMemberPickerCount();
    },
    ccSyncTeamMembersFromPicker() {
        const select = document.getElementById('cc-team-members');
        if (!select) return;
        const visibleInputs = Array.from(document.querySelectorAll('#cc-team-members-picker input[type="checkbox"]'));
        const visibleIds = new Set(visibleInputs.map(input => Number(input.value || 0)));
        const pickedVisible = new Set(visibleInputs.filter(input => input.checked).map(input => Number(input.value || 0)));
        Array.from(select.options || []).forEach(option => {
            const id = Number(option.value || 0);
            if (visibleIds.has(id)) option.selected = pickedVisible.has(id);
        });
        this.ccUpdateTeamMemberPickerCount();
    },
    ccUpdateTeamMemberPickerCount() {
        const count = document.getElementById('cc-team-members-count');
        const selected = Array.from(document.getElementById('cc-team-members') ? document.getElementById('cc-team-members').selectedOptions : []).length;
        if (count) count.textContent = this.formatNumber(selected) + ' انتخاب';
    },
    ccSelectedTeamMemberIds() {
        this.ccSyncTeamMembersFromPicker();
        return Array.from(document.getElementById('cc-team-members') ? document.getElementById('cc-team-members').selectedOptions : []).map(o => Number(o.value || 0)).filter(Boolean);
    },
    ccSelectAllTeamMembers() {
        Array.from(document.querySelectorAll('#cc-team-members-picker input[type="checkbox"]')).forEach(input => { input.checked = true; });
        this.ccSyncTeamMembersFromPicker();
    },
    ccClearTeamMembers() {
        const select = document.getElementById('cc-team-members');
        if (select) Array.from(select.options || []).forEach(option => { option.selected = false; });
        Array.from(document.querySelectorAll('#cc-team-members-picker input[type="checkbox"]')).forEach(input => { input.checked = false; });
        this.ccUpdateTeamMemberPickerCount();
    },
    ccRuleName(rule) {
        return String(rule && (rule.title || this.getCommissionRuleAmountLabel(rule)) || '').replace(/\n/g, ' ');
    },
    ccUserRuleLabels(uid) {
        const ruleTitle = this._ccRuleTitle || {};
        return (this._ccRuleAssigns || [])
            .filter(a => !a.disabled_at && Number(a.salesperson_user_id || 0) === Number(uid))
            .map(a => ruleTitle[Number(a.rule_id)] || ('#' + a.rule_id));
    },
    ccActiveRuleAssignmentsForUser(uid) {
        uid = Number(uid || 0);
        if (!uid) return [];
        return (this._ccRuleAssigns || []).filter(a => !a.disabled_at && Number(a.salesperson_user_id || 0) === uid);
    },
    ccAssignmentIsTeamSource(assignment, supervisorId = 0) {
        if (!assignment || assignment.disabled_at) return false;
        const sourceType = String(assignment.source_type || '').toUpperCase();
        const sourceSupervisor = Number(assignment.source_team_supervisor_user_id || 0);
        if (sourceType !== 'TEAM') return false;
        return !supervisorId || sourceSupervisor === Number(supervisorId || 0);
    },
    ccTeamRuleIds(team) {
        const supId = Number(team && team.supervisor_id || 0);
        const memberIds = (team && Array.isArray(team.members) ? team.members : [])
            .map(member => Number(member.id || 0))
            .filter(uid => uid > 0 && uid !== supId);
        if (!supId || !memberIds.length) return [];
        const teamSourceIds = (this._ccRuleAssigns || [])
            .filter(a => this.ccAssignmentIsTeamSource(a, supId) && memberIds.includes(Number(a.salesperson_user_id || 0)))
            .map(a => Number(a.rule_id || 0))
            .filter(Boolean);
        if (teamSourceIds.length) return Array.from(new Set(teamSourceIds));
        const [firstId, ...restIds] = memberIds;
        let common = new Set(this.ccActiveRuleAssignmentsForUser(firstId).map(a => Number(a.rule_id || 0)).filter(Boolean));
        restIds.forEach(uid => {
            const userRules = new Set(this.ccActiveRuleAssignmentsForUser(uid).map(a => Number(a.rule_id || 0)).filter(Boolean));
            common = new Set(Array.from(common).filter(ruleId => userRules.has(ruleId)));
        });
        const commonIds = Array.from(common);
        if (commonIds.length) return commonIds;
        const historicalIds = this.ccHistoricalSalesTeamMemberIds(supId);
        const currentSet = new Set(memberIds);
        const counts = {};
        const currentCounts = {};
        (this._ccRuleAssigns || []).forEach(a => {
            if (a.disabled_at) return;
            const uid = Number(a.salesperson_user_id || 0);
            const ruleId = Number(a.rule_id || 0);
            if (!uid || !ruleId || !historicalIds.includes(uid)) return;
            counts[ruleId] = (counts[ruleId] || 0) + 1;
            if (currentSet.has(uid)) currentCounts[ruleId] = (currentCounts[ruleId] || 0) + 1;
        });
        return Object.keys(counts)
            .map(Number)
            .filter(ruleId => Number(currentCounts[ruleId] || 0) > 0 && Number(counts[ruleId] || 0) >= 2);
    },
    ccHistoricalSalesTeamMemberIds(supId) {
        supId = Number(supId || 0);
        if (!supId) return [];
        const ids = (this._ccHierAssigns || [])
            .filter(a => Number(a.supervisor_user_id || 0) === supId)
            .map(a => Number(a.user_id || 0))
            .filter(uid => uid > 0 && uid !== supId);
        return Array.from(new Set(ids));
    },
    ccTeamManualIds() {
        try { return JSON.parse(localStorage.getItem('cc_manual_team_supervisors') || '[]').map(Number).filter(Boolean); } catch (_) { return []; }
    },
    ccActiveSalesTeamForUser(userId) {
        const uid = Number(userId || 0);
        if (!uid) return null;
        return this.ccBuildTeams().find(team => {
            if (String(team.type || 'sales') === 'management' || Number(team.supervisor_id || 0) <= 0) return false;
            return (team.members || []).some(member => Number(member.id || 0) === uid);
        }) || null;
    },
    ccAssignmentOriginLabel(assignment, team) {
        const ruleId = Number(assignment?.rule_id || 0);
        const supervisorId = Number(team && team.supervisor_id || 0);
        const sourceType = String(assignment?.source_type || '').trim().toUpperCase();
        if (this.ccAssignmentIsTeamSource(assignment, supervisorId)) {
            return 'از طریق تیم';
        }
        if (sourceType === 'INDIVIDUAL') {
            return 'انفرادی';
        }
        if (!ruleId || !team || !Array.isArray(team.members) || !team.members.length) {
            return 'انفرادی';
        }
        const activeAssigns = (this._ccAssignments || this._ccRuleAssigns || []).filter(item => !item.disabled_at && Number(item.rule_id || 0) === ruleId);
        const assignedUsers = new Set(activeAssigns.map(item => Number(item.salesperson_user_id || 0)).filter(Boolean));
        const memberIds = team.members.map(member => Number(member.id || 0)).filter(Boolean);
        return memberIds.length > 0 && memberIds.every(id => assignedUsers.has(id)) ? 'از طریق تیم' : 'انفرادی';
    },
    ccAssignmentTeamMeta(assignment, userNameById = {}) {
        const userId = Number(assignment?.salesperson_user_id || 0);
        if (!userId) return { teamLabel: 'بدون تیم', originLabel: 'انفرادی' };
        const team = this.ccActiveSalesTeamForUser(userId);
        if (!team) return { teamLabel: 'بدون تیم', originLabel: 'انفرادی' };
        const supervisorId = Number(team.supervisor_id || 0);
        const supervisorName = userNameById[supervisorId] || this.ccUserName(team.supervisor || {}) || ('#' + supervisorId);
        return {
            teamLabel: 'عضو تیم ' + supervisorName,
            originLabel: this.ccAssignmentOriginLabel(assignment, team)
        };
    },
    ccRememberManualTeam(supId) {
        supId = Number(supId || 0);
        if (!supId) return;
        const ids = Array.from(new Set(this.ccTeamManualIds().concat([supId])));
        try { localStorage.setItem('cc_manual_team_supervisors', JSON.stringify(ids)); } catch (_) {}
    },
    ccForgetManualTeam(supId) {
        supId = Number(supId || 0);
        if (!supId) return;
        const ids = this.ccTeamManualIds().filter(id => Number(id) !== supId);
        try { localStorage.setItem('cc_manual_team_supervisors', JSON.stringify(ids)); } catch (_) {}
    },
    ccHierarchyRowIsActive(row) {
        if (!row) return false;
        const today = this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10);
        const from = String(row.effective_from || '').slice(0, 10);
        const to = String(row.effective_to || '').slice(0, 10);
        return (!from || from <= today) && (!to || to >= today);
    },
    ccBuildTeams(includeHistory = false) {
        const users = this._ccTeamMembers || [];
        const byId = this._ccUsersById || {};
        const teamsBySup = {};
        const teamsByManager = {};
        const unassigned = { supervisor_id: 0, supervisor: null, members: [], manual: false };
        if (includeHistory) {
            const addHistoricalMember = (bucket, ownerId, user, row, type) => {
                if (!ownerId || !user) return;
                if (!bucket[ownerId]) {
                    bucket[ownerId] = {
                        supervisor_id: ownerId,
                        supervisor: byId[ownerId] || { id: ownerId, display_name: (type === 'management' ? 'مدیر #' : 'سرپرست #') + ownerId },
                        members: [],
                        manual: false,
                        type,
                        historical: true,
                        active_count: 0
                    };
                }
                const team = bucket[ownerId];
                const uid = Number(user.id || 0);
                const existing = team.members.find(member => Number(member.id || 0) === uid);
                const active = this.ccHierarchyRowIsActive(row);
                const range = this.ccDateRangeLabel(row.effective_from || '', row.effective_to || '', 'تاکنون');
                if (active) team.active_count = Number(team.active_count || 0) + (existing && existing._teamActive ? 0 : 1);
                if (existing) {
                    existing._teamActive = !!existing._teamActive || active;
                    existing._teamRanges = Array.from(new Set((existing._teamRanges || []).concat([range]).filter(Boolean)));
                    return;
                }
                team.members.push(Object.assign({}, user, {
                    _teamActive: active,
                    _teamRanges: range ? [range] : []
                }));
            };
            (this._ccHierAssigns || []).forEach(row => {
                const uid = Number(row.user_id || 0);
                const user = byId[uid] || users.find(u => Number(u.id || 0) === uid);
                const supId = Number(row.supervisor_user_id || 0);
                const managerId = !supId ? Number(row.manager_user_id || 0) : 0;
                if (supId) addHistoricalMember(teamsBySup, supId, user, row, 'sales');
                else if (managerId) addHistoricalMember(teamsByManager, managerId, user, row, 'management');
            });
            this.ccTeamManualIds().forEach(id => {
                if (!teamsBySup[id] && !teamsByManager[id]) teamsBySup[id] = { supervisor_id: id, supervisor: byId[id] || { id, display_name: 'سرپرست #' + id }, members: [], manual: true, type: 'sales', historical: false, active_count: 0 };
            });
            const teams = [];
            teams.push(...Object.values(teamsBySup).sort((a, b) => this.ccUserName(a.supervisor).localeCompare(this.ccUserName(b.supervisor), 'fa')));
            teams.push(...Object.values(teamsByManager).sort((a, b) => this.ccUserName(a.supervisor).localeCompare(this.ccUserName(b.supervisor), 'fa')));
            const assignedCurrentIds = new Set();
            Object.values(teamsBySup).concat(Object.values(teamsByManager)).forEach(team => {
                (team.members || []).forEach(member => { if (member._teamActive) assignedCurrentIds.add(Number(member.id || 0)); });
            });
            unassigned.members = users.filter(u => !assignedCurrentIds.has(Number(u.id || 0)));
            if (unassigned.members.length) teams.unshift(unassigned);
            return teams;
        }
        users.forEach(u => {
            const uid = Number(u.id);
            const a = this.ccActiveAssignment(uid);
            const supId = a && a.supervisor_user_id ? Number(a.supervisor_user_id) : 0;
            const managerId = a && !supId && a.manager_user_id ? Number(a.manager_user_id) : 0;
            if (!supId) {
                if (managerId) {
                    if (!teamsByManager[managerId]) teamsByManager[managerId] = { supervisor_id: managerId, supervisor: byId[managerId] || { id: managerId, display_name: 'مدیر #' + managerId }, members: [], manual: false, type: 'management' };
                    teamsByManager[managerId].members.push(u);
                    return;
                }
                unassigned.members.push(u);
                return;
            }
            if (!teamsBySup[supId]) teamsBySup[supId] = { supervisor_id: supId, supervisor: byId[supId] || { id: supId, display_name: 'سرپرست #' + supId }, members: [], manual: false, type: 'sales' };
            teamsBySup[supId].members.push(u);
        });
        this.ccTeamManualIds().forEach(id => {
            if (!teamsBySup[id] && !teamsByManager[id]) teamsBySup[id] = { supervisor_id: id, supervisor: byId[id] || { id, display_name: 'سرپرست #' + id }, members: [], manual: true, type: 'sales' };
        });
        const teams = [];
        if (unassigned.members.length) teams.push(unassigned);
        teams.push(...Object.values(teamsBySup).sort((a, b) => this.ccUserName(a.supervisor).localeCompare(this.ccUserName(b.supervisor), 'fa')));
        teams.push(...Object.values(teamsByManager).sort((a, b) => this.ccUserName(a.supervisor).localeCompare(this.ccUserName(b.supervisor), 'fa')));
        return teams;
    },
    ccRenderTeamBoard() {
        const board = document.getElementById('cc-team-board'); if (!board) return;
        const teams = this.ccBuildTeams(true);
        const q = String((document.getElementById('cc-team-search') || {}).value || '').trim();
        const filtered = q ? teams.map(t => {
            const teamName = t.supervisor ? this.ccUserName(t.supervisor) : 'بدون تیم';
            const keepTeam = teamName.indexOf(q) !== -1;
            const members = keepTeam ? t.members : t.members.filter(u => this.ccUserName(u).indexOf(q) !== -1 || this.ccUserRuleLabels(Number(u.id)).join(' ').indexOf(q) !== -1);
            return Object.assign({}, t, { members });
        }).filter(t => t.members.length || (t.supervisor && this.ccUserName(t.supervisor).indexOf(q) !== -1)) : teams;
        const heroTitle = document.getElementById('cc-team-hero-title');
        const heroMeta = document.getElementById('cc-team-hero-meta');
        const realTeams = teams.filter(t => Number(t.supervisor_id || 0) > 0);
        const totalMembers = (this._ccTeamMembers || []).length;
        const noRuleCount = (this._ccTeamMembers || []).filter(u => !this.ccUserRuleLabels(Number(u.id)).length).length;
        if (heroTitle) heroTitle.textContent = realTeams.length ? ('مدیریت فروش — ' + this.formatNumber(realTeams.length) + ' تیم') : 'ساختار تیم فروش';
        if (heroMeta) heroMeta.textContent = this.formatNumber(totalMembers) + ' کارشناس · ' + this.formatNumber(noRuleCount) + ' نفر بدون قانون فعال';
        if (!filtered.length) { board.innerHTML = '<div class="cc-team-empty">تیمی برای نمایش وجود ندارد.</div>'; return; }
        board.innerHTML = filtered.map(t => this.ccRenderTeamColumn(t)).join('');
    },
    ccRenderTeamColumn(team) {
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const supId = Number(team.supervisor_id || 0);
        const isManagementTeam = String(team.type || '') === 'management';
        const supervisor = team.supervisor;
        const title = supervisor ? ((isManagementTeam ? 'تیم مدیریتی ' : 'تیم ') + this.ccUserName(supervisor)) : 'بدون تیم';
        const activeCount = Number(team.active_count || 0);
        const meta = supervisor
            ? (team.historical
                ? ((isManagementTeam ? 'تاریخچه سرپرست‌ها · ' : 'تاریخچه اعضا · ') + this.formatNumber(team.members.length) + ' نفر' + (activeCount !== team.members.length ? ' · ' + this.formatNumber(activeCount) + ' فعال' : ''))
                : ((isManagementTeam ? 'تعداد سرپرست‌ها · ' : 'تعداد اعضا · ') + this.formatNumber(team.members.length) + ' نفر'))
            : (this.formatNumber(team.members.length) + ' نفر بدون تیم');
        const firstRule = isManagementTeam ? this.ccManagementTeamSummary(team) : this.ccTeamRuleSummary(team);
        const members = team.members.map(u => this.ccRenderTeamMember(u, supId, team.type || 'sales')).join('');
        const deleteBtn = supId > 0 ? '<button type="button" class="cc-team-delete" title="حذف تیم" onclick="app.ccDeleteTeam(' + supId + ',\'' + (isManagementTeam ? 'management' : 'sales') + '\')"><i class="fa-solid fa-trash"></i></button>' : '';
        const isActive = this.ccTeamIsActive(team);
        const activeToggle = supId > 0 ? '<button type="button" class="cc-team-active-toggle' + (isActive ? ' on' : '') + '" title="فعال/غیرفعال کردن تیم" onclick="app.ccToggleTeamActive(' + supId + ',\'' + (isManagementTeam ? 'management' : 'sales') + '\',' + (isActive ? 0 : 1) + ')"><span class="cc-switch' + (isActive ? ' on' : '') + '"></span><span>' + (isActive ? 'فعال' : 'غیرفعال') + '</span></button>' : '';
        const assignCta = '';
        const rate = isManagementTeam ? this.ccTeamManagerRate(supId) : this.ccTeamSupervisorRate(supId);
        const rateCta = supId > 0 ? '<div class="cc-team-assign-wrap"><button type="button" class="cc-team-rate-cta" onclick="' + (isManagementTeam ? 'app.ccOpenTeamManagerRateModal(' : 'app.ccOpenTeamSupervisorRateModal(') + supId + ')"><i class="fa-solid fa-percent"></i>' + (rate !== '' ? ((isManagementTeam ? 'سهم مدیر: ' : 'سهم سرپرست: ') + esc(this.ccFormatPercentValue(rate)) + '٪') : (isManagementTeam ? 'تنظیم سهم مدیر' : 'تنظیم سهم سرپرست')) + '</button></div>' : '';
        const dropHint = supId > 0 ? '<div class="cc-team-drop-hint">+ کشیدن ' + (isManagementTeam ? 'سرپرست' : 'کارشناس') + ' به این تیم</div>' : '';
        const memberArea = members || dropHint;
        return '<article class="cc-team-column" ondragover="app.ccDragOver(event)" ondragleave="app.ccDragLeave(event)" ondrop="app.ccDrop(event,' + supId + ',\'' + (isManagementTeam ? 'management' : 'sales') + '\')">'
            + '<header class="cc-team-head"><div class="text-right"><h5>' + esc(title) + '</h5><p>' + esc(meta) + '</p><div class="cc-team-rule-summary">' + esc(firstRule || (supId ? (isManagementTeam ? 'سهم مدیر از پورسانت سرپرست‌ها' : 'این تیم هنوز قانونی تخصیص نگرفته') : 'بدون قانون تیم')) + '</div></div><div class="cc-team-head-actions">' + activeToggle + deleteBtn + '</div></header>'
            + '<div class="cc-team-members">' + memberArea + (members ? dropHint : '') + rateCta + assignCta + '</div>'
            + '<footer class="cc-team-foot">' + esc(supId ? 'تغییرات تیم روی پورسانت‌های قبلی اثر ندارد' : 'برای تشکیل تیم، از دکمه افزودن تیم استفاده کنید') + '</footer>'
            + '</article>';
    },
    ccTeamIsActive(team) {
        if (!team || Number(team.supervisor_id || 0) <= 0) return false;
        if (Object.prototype.hasOwnProperty.call(team, 'active_count')) return Number(team.active_count || 0) > 0;
        return Array.isArray(team.members) && team.members.length > 0;
    },
    ccTeamMembersForStateChange(team, shouldActivate) {
        const supId = Number(team && team.supervisor_id || 0);
        const ids = (team && Array.isArray(team.members) ? team.members : [])
            .filter(member => shouldActivate || member._teamActive)
            .map(member => Number(member.id || 0))
            .filter(uid => uid > 0 && uid !== supId);
        return Array.from(new Set(ids));
    },
    ccRenderTeamMember(user, supId, teamType) {
        const uid = Number(user.id);
        const isManagementTeam = String(teamType || '') === 'management';
        const rules = isManagementTeam ? [] : this.ccUserRuleLabels(uid);
        const managementInfo = isManagementTeam ? this.ccSupervisorMemberSummary(uid) : '';
        const noRule = isManagementTeam ? !managementInfo : !rules.length;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const removeBtn = Number(supId || 0) > 0
            ? '<button type="button" class="cc-team-member-action remove" title="حذف از تیم" onclick="app.ccRemoveTeamMember(' + uid + ',' + Number(supId || 0) + ',\'' + (String(teamType || '') === 'management' ? 'management' : 'sales') + '\')"><i class="fa-solid fa-trash"></i></button>'
            : '';
        const ranges = Array.isArray(user._teamRanges) && user._teamRanges.length ? (' · ' + user._teamRanges.slice(0, 2).join('، ')) : '';
        const historyState = user._teamRanges ? (user._teamActive ? 'فعال' : 'تاریخی') : '';
        return '<div class="cc-team-member' + (noRule ? ' no-rule' : '') + '" draggable="true" ondragstart="app.ccDragStart(event,' + uid + ')" ondragend="app.ccDragEnd(event)">'
            + '<div class="text-right flex-1 min-w-0"><div class="name">' + esc(this.ccUserName(user)) + '</div><div class="rule">' + esc((historyState ? historyState + ranges + ' · ' : '') + (isManagementTeam ? (managementInfo || 'سرپرست بدون تیم فروش فعال') : (noRule ? 'بدون قانون فعال' : rules.join('، ')))) + '</div></div>'
            + '<div class="cc-team-member-actions">' + removeBtn + '</div>'
            + '</div>';
    },
    ccSalesTeamForSupervisor(supervisorId) {
        supervisorId = Number(supervisorId || 0);
        if (!supervisorId) return null;
        return this.ccBuildTeams().find(t => String(t.type || 'sales') !== 'management' && Number(t.supervisor_id || 0) === supervisorId) || null;
    },
    ccSupervisorMemberSummary(supervisorId) {
        const team = this.ccSalesTeamForSupervisor(supervisorId);
        if (!team) return '';
        const ruleSummary = this.ccTeamRuleSummary(team);
        const count = Array.isArray(team.members) ? team.members.length : 0;
        return 'سرپرست فروش · ' + this.formatNumber(count) + ' عضو' + (ruleSummary ? ' · ' + ruleSummary.replace(/^قانون تیم:\s*/, '') : '');
    },
    ccManagementTeamSummary(team) {
        const members = Array.isArray(team && team.members) ? team.members : [];
        if (!members.length) return '';
        const withSalesTeam = members.filter(u => this.ccSalesTeamForSupervisor(Number(u.id || 0))).length;
        return 'سهم مدیر از پورسانت ' + this.formatNumber(withSalesTeam) + ' سرپرست فروش';
    },
    ccTeamRuleSummary(teamOrMembers) {
        let ruleIds = [];
        if (teamOrMembers && !Array.isArray(teamOrMembers) && Object.prototype.hasOwnProperty.call(teamOrMembers, 'supervisor_id')) {
            ruleIds = this.ccTeamRuleIds(teamOrMembers);
        } else {
            const labels = [];
            (teamOrMembers || []).forEach(u => this.ccUserRuleLabels(Number(u.id)).forEach(label => labels.push(label)));
            const uniqueLabels = Array.from(new Set(labels));
            if (!uniqueLabels.length) return '';
            if (uniqueLabels.length === 1) return 'قانون تیم: ' + uniqueLabels[0];
            return 'قوانین فعال: ' + this.formatNumber(uniqueLabels.length) + ' قانون';
        }
        const ruleTitle = this._ccRuleTitle || {};
        const labels = ruleIds.map(id => ruleTitle[Number(id)] || ('#' + id)).filter(Boolean);
        if (!labels.length) return '';
        if (labels.length === 1) return 'قانون تیم: ' + labels[0];
        return 'قوانین تیم: ' + this.formatNumber(labels.length) + ' قانون';
    },
    ccFocusCreateTeam() {
        const el = document.getElementById('cc-team-supervisor');
        if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); try { el.focus(); } catch (_) {} }
    },
    async ccOpenCreateTeamModal() {
        await this.ccEnsureSetup();
        const users = this._ccTeamMembers || [];
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const userOptions = '<option value="">انتخاب مسئول تیم...</option>' + users.map(u => '<option value="' + Number(u.id || 0) + '">' + esc(this.ccUserName(u)) + '</option>').join('');
        const today = typeof toPersianDate === 'function' ? (toPersianDate(this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10)) || '') : '';
        const html = '<div class="cc-team-rule-modal-body">'
            + '<div class="cc-team-modal-field"><label>نوع تیم</label><select id="cc-modal-team-type" onchange="app.ccRenderCreateTeamModalMembers()"><option value="sales">تیم فروش - سهم سرپرست از فروش اعضا</option><option value="management">تیم مدیریتی - سهم مدیر از پورسانت سرپرست‌ها</option></select></div>'
            + '<div class="cc-team-modal-field"><label id="cc-modal-team-owner-label">سرپرست تیم</label><select id="cc-modal-team-supervisor">' + userOptions + '</select></div>'
            + '<div class="cc-team-modal-field"><label id="cc-modal-team-rate-label">درصد سهم سرپرست از فروش تیم</label><input id="cc-modal-team-rate" type="text" inputmode="decimal" placeholder="مثلا 1"></div>'
            + '<div class="cc-team-modal-field"><label>اعضا</label><input id="cc-modal-team-search" type="text" placeholder="جستجوی کارشناس..." oninput="app.ccRenderCreateTeamModalMembers()"></div>'
            + '<div id="cc-modal-team-members" class="cc-team-rule-modal-list"></div>'
            + '<div class="cc-team-modal-field"><label>اعمال از تاریخ</label><input id="cc-modal-team-eff" type="text" value="' + esc(today) + '" placeholder="امروز"></div>'
            + '</div>';
        const result = await Swal.fire({
            title: 'افزودن تیم',
            html,
            showCancelButton: true,
            confirmButtonText: 'ساخت تیم',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal' },
            didOpen: () => {
                this.ccRenderCreateTeamModalMembers();
                const sup = document.getElementById('cc-modal-team-supervisor');
                if (sup) sup.addEventListener('change', () => this.ccRenderCreateTeamModalMembers());
                const eff = document.getElementById('cc-modal-team-eff');
                if (eff && typeof this.initJalaliDatePickers === 'function') {
                    this.initJalaliDatePickers([eff], { allowFuture: true });
                }
            },
            preConfirm: () => {
                const teamType = String((document.getElementById('cc-modal-team-type') || {}).value || 'sales');
                const supId = Number((document.getElementById('cc-modal-team-supervisor') || {}).value || 0);
                const memberIds = Array.from(document.querySelectorAll('.cc-modal-team-member:checked')).map(input => Number(input.value || 0)).filter(Boolean);
                if (!supId) {
                    Swal.showValidationMessage(teamType === 'management' ? 'مدیر تیم را انتخاب کنید' : 'سرپرست تیم را انتخاب کنید');
                    return false;
                }
                const rate = String((document.getElementById('cc-modal-team-rate') || {}).value || '').trim();
                const numericRate = rate === '' ? null : Number(rate.replace(',', '.'));
                if (rate !== '' && (!Number.isFinite(numericRate) || numericRate < 0 || numericRate > 100)) {
                    Swal.showValidationMessage(teamType === 'management' ? 'درصد سهم مدیر را بین ۰ تا ۱۰۰ وارد کنید' : 'درصد سهم سرپرست را بین ۰ تا ۱۰۰ وارد کنید');
                    return false;
                }
                return {
                    teamType,
                    supId,
                    memberIds,
                    supervisorRate: teamType === 'sales' ? rate : '',
                    managerRate: teamType === 'management' ? rate : '',
                    effDate: (document.getElementById('cc-modal-team-eff') || {}).value || ''
                };
            }
        });
        if (!result.isConfirmed || !result.value) return;
        await this.ccCreateTeamFromModal(result.value);
    },
    ccRenderCreateTeamModalMembers() {
        const wrap = document.getElementById('cc-modal-team-members');
        if (!wrap) return;
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const q = String((document.getElementById('cc-modal-team-search') || {}).value || '').trim();
        const supId = Number((document.getElementById('cc-modal-team-supervisor') || {}).value || 0);
        const teamType = String((document.getElementById('cc-modal-team-type') || {}).value || 'sales');
        const ownerLabel = document.getElementById('cc-modal-team-owner-label');
        const rateLabel = document.getElementById('cc-modal-team-rate-label');
        if (ownerLabel) ownerLabel.textContent = teamType === 'management' ? 'مدیر تیم' : 'سرپرست تیم';
        if (rateLabel) rateLabel.textContent = teamType === 'management' ? 'درصد سهم مدیر از پورسانت سرپرست‌ها' : 'درصد سهم سرپرست از فروش تیم';
        const supervisorIds = new Set(this.ccBuildTeams().filter(t => String(t.type || 'sales') !== 'management' && Number(t.supervisor_id || 0) > 0).map(t => Number(t.supervisor_id || 0)));
        const baseUsers = teamType === 'management' && supervisorIds.size
            ? (this._ccTeamMembers || []).filter(u => supervisorIds.has(Number(u.id || 0)))
            : (this._ccTeamMembers || []);
        const users = baseUsers.filter(u => Number(u.id || 0) !== supId).filter(u => !q || this.ccUserName(u).indexOf(q) !== -1);
        wrap.innerHTML = users.length ? users.map(u => {
            const id = Number(u.id || 0);
            const rule = this.ccUserRuleLabels(id).join('، ') || 'بدون قانون فعال';
            return '<label class="cc-team-rule-modal-item"><input type="checkbox" class="cc-modal-team-member" value="' + id + '"><span class="title">' + esc(this.ccUserName(u)) + '</span><small>' + esc(rule) + '</small></label>';
        }).join('') : '<div class="text-gray-500 text-xs p-2">کارشناسی برای نمایش وجود ندارد.</div>';
    },
    async ccCreateTeamFromModal(data) {
        const supId = Number(data.supId || 0);
        const selectedMembers = Array.isArray(data.memberIds) ? data.memberIds.map(Number).filter(Boolean) : [];
        if (!supId) return;
        if (String(data.teamType || 'sales') === 'management') {
            for (const uid of selectedMembers) {
                if (uid && uid !== supId) await this.ccChangeManager(uid, supId, data.effDate || this.ccTeamEffDate(), { quiet: true, skipReload: true, managerRate: data.managerRate || '' });
            }
            Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تیم مدیریتی ساخته شد', showConfirmButton: false, timer: 1800 });
            await this.ccLoadTeams();
            return;
        }
        this.ccRememberManualTeam(supId);
        for (const uid of selectedMembers) {
            if (uid && uid !== supId) await this.ccChangeSupervisor(uid, supId, data.effDate || this.ccTeamEffDate(), { quiet: true, skipReload: true, supervisorRate: data.supervisorRate || '' });
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تیم ساخته شد', showConfirmButton: false, timer: 1800 });
        await this.ccLoadTeams();
    },
    async ccOpenTeamSupervisorRateModal(supId) {
        supId = Number(supId || 0);
        const team = this.ccBuildTeams().find(t => Number(t.supervisor_id || 0) === supId);
        if (!team) return;
        const members = (team.members || []).map(u => Number(u.id || 0)).filter(uid => uid && uid !== supId);
        if (!members.length) {
            Swal.fire('سهم سرپرست', 'این تیم هنوز عضوی ندارد.', 'info');
            return;
        }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const today = typeof toPersianDate === 'function' ? (toPersianDate(this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10)) || '') : '';
        const currentRate = this.ccTeamSupervisorRate(supId);
        const supName = this._ccUsersById && this._ccUsersById[supId] ? this.ccUserName(this._ccUsersById[supId]) : ('#' + supId);
        const html = '<div class="cc-team-rule-modal-body">'
            + '<div class="cc-team-rule-modal-hint">این درصد برای فروش اعضای تیم «' + esc(supName) + '» از تاریخ انتخاب‌شده اعمال می‌شود.</div>'
            + '<div class="cc-team-modal-field"><label>درصد سهم سرپرست</label><input id="cc-modal-team-rate-edit" type="text" inputmode="decimal" value="' + esc(currentRate) + '" placeholder="مثلا 1"></div>'
            + '<div class="cc-team-modal-field"><label>اعمال از تاریخ</label><input id="cc-modal-team-rate-eff" type="text" value="' + esc(today) + '" placeholder="امروز"></div>'
            + '</div>';
        const result = await Swal.fire({
            title: 'تنظیم سهم سرپرست',
            html,
            showCancelButton: true,
            confirmButtonText: 'ذخیره سهم',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal' },
            didOpen: () => {
                const eff = document.getElementById('cc-modal-team-rate-eff');
                if (eff && typeof this.initJalaliDatePickers === 'function') this.initJalaliDatePickers([eff], { allowFuture: true });
            },
            preConfirm: () => {
                const rate = String((document.getElementById('cc-modal-team-rate-edit') || {}).value || '').trim();
                const numericRate = Number(rate.replace(',', '.'));
                if (rate === '' || !Number.isFinite(numericRate) || numericRate < 0 || numericRate > 100) {
                    Swal.showValidationMessage('درصد سهم سرپرست را بین ۰ تا ۱۰۰ وارد کنید');
                    return false;
                }
                return {
                    supervisorRate: rate,
                    effDate: (document.getElementById('cc-modal-team-rate-eff') || {}).value || ''
                };
            }
        });
        if (!result.isConfirmed || !result.value) return;
        for (const uid of members) {
            await this.ccChangeSupervisor(uid, supId, result.value.effDate || this.ccTeamEffDate(), { quiet: true, skipReload: true, supervisorRate: result.value.supervisorRate });
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'سهم سرپرست ذخیره شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadTeams();
    },
    async ccOpenTeamManagerRateModal(managerId) {
        managerId = Number(managerId || 0);
        const team = this.ccBuildTeams().find(t => Number(t.supervisor_id || 0) === managerId && String(t.type || '') === 'management');
        if (!team) return;
        const members = (team.members || []).map(u => Number(u.id || 0)).filter(uid => uid && uid !== managerId);
        if (!members.length) {
            Swal.fire('سهم مدیر', 'این تیم مدیریتی هنوز سرپرستی ندارد.', 'info');
            return;
        }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const today = typeof toPersianDate === 'function' ? (toPersianDate(this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10)) || '') : '';
        const currentRate = this.ccTeamManagerRate(managerId);
        const managerName = this._ccUsersById && this._ccUsersById[managerId] ? this.ccUserName(this._ccUsersById[managerId]) : ('#' + managerId);
        const html = '<div class="cc-team-rule-modal-body">'
            + '<div class="cc-team-rule-modal-hint">این درصد از پورسانت سرپرست‌های تیم «' + esc(managerName) + '» برای مدیر محاسبه می‌شود.</div>'
            + '<div class="cc-team-modal-field"><label>درصد سهم مدیر</label><input id="cc-modal-team-manager-rate-edit" type="text" inputmode="decimal" value="' + esc(currentRate) + '" placeholder="مثلا 10"></div>'
            + '<div class="cc-team-modal-field"><label>اعمال از تاریخ</label><input id="cc-modal-team-manager-rate-eff" type="text" value="' + esc(today) + '" placeholder="امروز"></div>'
            + '</div>';
        const result = await Swal.fire({
            title: 'تنظیم سهم مدیر',
            html,
            showCancelButton: true,
            confirmButtonText: 'ذخیره سهم',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal' },
            didOpen: () => {
                const eff = document.getElementById('cc-modal-team-manager-rate-eff');
                if (eff && typeof this.initJalaliDatePickers === 'function') this.initJalaliDatePickers([eff], { allowFuture: true });
            },
            preConfirm: () => {
                const rate = String((document.getElementById('cc-modal-team-manager-rate-edit') || {}).value || '').trim();
                const numericRate = Number(rate.replace(',', '.'));
                if (rate === '' || !Number.isFinite(numericRate) || numericRate < 0 || numericRate > 100) {
                    Swal.showValidationMessage('درصد سهم مدیر را بین ۰ تا ۱۰۰ وارد کنید');
                    return false;
                }
                return {
                    managerRate: rate,
                    effDate: (document.getElementById('cc-modal-team-manager-rate-eff') || {}).value || ''
                };
            }
        });
        if (!result.isConfirmed || !result.value) return;
        for (const uid of members) {
            await this.ccChangeManager(uid, managerId, result.value.effDate || this.ccTeamEffDate(), { quiet: true, skipReload: true, managerRate: result.value.managerRate });
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'سهم مدیر ذخیره شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadTeams();
    },
    ccRenderTeamTable() {
        this.ccRenderTeamBoard();
    },
    ccTeamEffDate() {
        const v = String((document.getElementById('cc-team-eff') || {}).value || '').trim();
        return this.ccToGregorianDate(v) || (this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10));
    },
    ccTeamBySupervisorId(supId) {
        supId = Number(supId || 0);
        if (!supId) return null;
        return this.ccBuildTeams().find(t => String(t.type || 'sales') !== 'management' && Number(t.supervisor_id || 0) === supId) || null;
    },
    ccActiveAssignmentForUserRule(uid, ruleId, supervisorId = 0) {
        uid = Number(uid || 0);
        ruleId = Number(ruleId || 0);
        supervisorId = Number(supervisorId || 0);
        if (!uid || !ruleId) return null;
        const rows = (this._ccRuleAssigns || []).filter(a => !a.disabled_at && Number(a.salesperson_user_id || 0) === uid && Number(a.rule_id || 0) === ruleId);
        if (!supervisorId) return rows[0] || null;
        return rows.find(a => this.ccAssignmentIsTeamSource(a, supervisorId)) || rows[0] || null;
    },
    async ccDeleteUserTeamRules(uid, ruleIds, supervisorId) {
        uid = Number(uid || 0);
        supervisorId = Number(supervisorId || 0);
        const ids = Array.from(new Set((ruleIds || []).map(Number).filter(Boolean)));
        let changed = false;
        for (const ruleId of ids) {
            const rows = (this._ccRuleAssigns || []).filter(a => !a.disabled_at && Number(a.salesperson_user_id || 0) === uid && Number(a.rule_id || 0) === ruleId);
            const row = supervisorId
                ? rows.find(a => this.ccAssignmentIsTeamSource(a, supervisorId))
                : (rows[0] || null);
            if (!row || !Number(row.id || 0)) continue;
            const res = await this.req('delete_commission_assignment', { assignment_id: Number(row.id || 0) });
            if (res && res.status === 'success') changed = true;
        }
        return changed;
    },
    async ccAssignUserTeamRules(uid, ruleIds, supervisorId) {
        uid = Number(uid || 0);
        supervisorId = Number(supervisorId || 0);
        const ids = Array.from(new Set((ruleIds || []).map(Number).filter(Boolean)));
        const needsSync = ids.filter(ruleId => {
            const row = this.ccActiveAssignmentForUserRule(uid, ruleId);
            return !row || !this.ccAssignmentIsTeamSource(row, supervisorId);
        });
        if (!uid || !supervisorId || !needsSync.length) return false;
        const res = await this.req('assign_commission_rules', {
            rule_ids: JSON.stringify(needsSync),
            agent_ids: JSON.stringify([uid]),
            product_ids: JSON.stringify([]),
            source_type: 'TEAM',
            source_team_supervisor_user_id: supervisorId
        });
        return !!(res && res.status === 'success');
    },
    async ccSyncMovedUserTeamRules(uid, oldTeam, newTeam) {
        uid = Number(uid || 0);
        if (!uid) return;
        const oldSupId = Number(oldTeam && oldTeam.supervisor_id || 0);
        const newSupId = Number(newTeam && newTeam.supervisor_id || 0);
        const oldRuleIds = this.ccTeamRuleIds(oldTeam);
        const newRuleIds = this.ccTeamRuleIds(newTeam);
        const keep = new Set(newRuleIds.map(Number));
        await this.ccDeleteUserTeamRules(uid, oldRuleIds.filter(ruleId => !keep.has(Number(ruleId))), oldSupId);
        await this.ccAssignUserTeamRules(uid, newRuleIds, newSupId);
    },
    async ccRepairTeamRuleAssignments() {
        if (this._ccRepairingTeamAssignments) return false;
        this._ccRepairingTeamAssignments = true;
        let changed = false;
        try {
            const teams = this.ccSalesTeamsForAssignment ? this.ccSalesTeamsForAssignment() : [];
            for (const team of teams) {
                const supId = Number(team.supervisor_id || 0);
                if (!supId) continue;
                const ruleIds = this.ccTeamRuleIds(team);
                if (!ruleIds.length) continue;
                const currentIds = (team.members || []).map(member => Number(member.id || 0)).filter(uid => uid > 0 && uid !== supId);
                const currentSet = new Set(currentIds);
                const formerIds = this.ccHistoricalSalesTeamMemberIds(supId).filter(uid => !currentSet.has(uid));
                for (const uid of currentIds) {
                    if (await this.ccAssignUserTeamRules(uid, ruleIds, supId)) changed = true;
                }
                for (const uid of formerIds) {
                    for (const ruleId of ruleIds) {
                        const row = this.ccActiveAssignmentForUserRule(uid, ruleId, supId);
                        if (!row || !Number(row.id || 0)) continue;
                        if (this.ccAssignmentIsTeamSource(row) || !String(row.source_type || '').trim() || String(row.source_type || '').toUpperCase() === 'INDIVIDUAL') {
                            const res = await this.req('delete_commission_assignment', { assignment_id: Number(row.id || 0) });
                            if (res && res.status === 'success') changed = true;
                        }
                    }
                }
            }
        } finally {
            this._ccRepairingTeamAssignments = false;
        }
        if (changed) {
            const rr = await this.req('get_commission_rules', {});
            const rules = (rr && Array.isArray(rr.rules)) ? rr.rules : [];
            const assigns = (rr && Array.isArray(rr.assignments)) ? rr.assignments : [];
            this._ccRules = rules;
            this._ccRuleAssigns = assigns;
            this._ccAssignments = assigns;
            this._ccRuleTitle = {};
            rules.forEach(r => { this._ccRuleTitle[Number(r.id)] = String(r.title || this.getCommissionRuleAmountLabel(r)).replace(/\n/g, ' '); });
        }
        return changed;
    },
    async ccInlineSetSupervisor(uid, supId) {
        await this.ccChangeSupervisor(Number(uid), Number(supId || 0), this.ccTeamEffDate());
    },
    async ccInlineAssignRule(uid, ruleId) {
        ruleId = Number(ruleId || 0); if (!ruleId) return;
        const res = await this.req('assign_commission_rules', { rule_ids: JSON.stringify([ruleId]), agent_ids: JSON.stringify([Number(uid)]), product_ids: JSON.stringify([]) });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تخصیص قانون', 'error'); return; }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون تخصیص یافت', showConfirmButton: false, timer: 1500 });
        await this.ccLoadTeams();
    },
    async ccRemoveTeamMember(uid, supId, teamType) {
        uid = Number(uid || 0);
        supId = Number(supId || 0);
        if (!uid || !supId) return;
        const isManagementTeam = String(teamType || '') === 'management';
        const c = await Swal.fire({
            title: 'حذف از تیم',
            text: isManagementTeam ? 'این سرپرست از زیرمجموعه این مدیر خارج شود؟' : 'این کارشناس از زیرمجموعه این سرپرست خارج شود؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف از تیم',
            cancelButtonText: 'لغو'
        });
        if (!c.isConfirmed) return;
        if (isManagementTeam) {
            await this.ccChangeManager(uid, 0, this.ccTeamEffDate(), { quiet: true, skipReload: true });
        } else {
            const oldTeam = this.ccTeamBySupervisorId(supId);
            await this.ccChangeSupervisor(uid, 0, this.ccTeamEffDate(), { quiet: true, skipReload: true });
            await this.ccSyncMovedUserTeamRules(uid, oldTeam, null);
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: isManagementTeam ? 'سرپرست از تیم مدیریتی حذف شد' : 'کارشناس از تیم حذف شد', showConfirmButton: false, timer: 1500 });
        await this.ccLoadTeams();
    },
    async ccOpenTeamRuleModal(supId) {
        supId = Number(supId || 0);
        const team = this.ccBuildTeams().find(t => Number(t.supervisor_id || 0) === supId);
        if (!team) return;
        const members = (team.members || []).map(u => Number(u.id || 0)).filter(uid => uid && uid !== supId);
        if (!members.length) {
            Swal.fire('تخصیص قانون', 'این تیم هنوز عضوی برای تخصیص قانون ندارد.', 'info');
            return;
        }
        if (!Array.isArray(this._ccRules) || !this._ccRules.length) {
            try {
                const rr = await this.req('get_commission_rules', {});
                this._ccRules = (rr && Array.isArray(rr.rules)) ? rr.rules : [];
            } catch (e) {}
        }
        const rules = (this._ccRules || []).filter(r => Number(r.is_active != null ? r.is_active : 1) !== 0 && String(r.status || 'active').toLowerCase() !== 'inactive');
        if (!rules.length) {
            Swal.fire('تخصیص قانون', 'قانون فعالی برای تخصیص وجود ندارد.', 'info');
            return;
        }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const activeRuleIds = new Set(this.ccTeamRuleIds(team));
        const html = '<div class="cc-team-rule-modal-body">'
            + '<div class="cc-team-rule-modal-hint">یک یا چند قانون را برای ' + esc(this.formatNumber(members.length)) + ' عضو تیم انتخاب کنید.</div>'
            + '<div class="cc-team-rule-modal-list">'
            + rules.map(r => {
                const label = this.ccRuleName(r);
                const type = String(r.rule_type || '').toUpperCase() === 'FIXED_RULE' ? 'ثابت' : 'پلکانی';
                const checked = activeRuleIds.has(Number(r.id || 0)) ? ' checked' : '';
                return '<label class="cc-team-rule-modal-item">'
                    + '<input type="checkbox" class="cc-team-modal-rule" value="' + Number(r.id || 0) + '"' + checked + '>'
                    + '<span class="title">' + esc(label) + '</span>'
                    + '<small>' + esc(type) + '</small>'
                    + '</label>';
            }).join('')
            + '</div></div>';
        const result = await Swal.fire({
            title: 'تخصیص قانون به تیم',
            html,
            showCancelButton: true,
            confirmButtonText: 'تخصیص',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal' },
            preConfirm: () => {
                const ids = Array.from(document.querySelectorAll('.cc-team-modal-rule:checked')).map(input => Number(input.value || 0)).filter(Boolean);
                if (!ids.length) {
                    Swal.showValidationMessage('حداقل یک قانون انتخاب کنید');
                    return false;
                }
                return ids;
            }
        });
        if (!result.isConfirmed || !Array.isArray(result.value)) return;
        const res = await this.req('assign_commission_rules', {
            rule_ids: JSON.stringify(result.value),
            agent_ids: JSON.stringify(members),
            product_ids: JSON.stringify([]),
            source_type: 'TEAM',
            source_team_supervisor_user_id: supId
        });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تخصیص قانون تیم', 'error'); return; }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون به تیم تخصیص یافت', showConfirmButton: false, timer: 1800 });
        await this.ccLoadTeams();
    },
    async ccShowHistoryFor(uid) {
        const box = document.getElementById('cc-supervisor-history'); if (!box) return;
        const res = await this.req('get_user_hierarchy', { user_id: Number(uid) });
        const list = (res && Array.isArray(res.hierarchy_assignments)) ? res.hierarchy_assignments : [];
        const byId = this._ccUsersById || {};
        const nm = byId[Number(uid)] ? this.ccUserName(byId[Number(uid)]) : ('#' + uid);
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        let html = '<div class="flex items-center justify-between mb-2"><h4 class="text-yellow-500 font-bold text-sm">تاریخچه‌ی سرپرستیِ ' + esc(nm) + '</h4><button class="text-xs text-gray-400" onclick="document.getElementById(\'cc-supervisor-history\').classList.add(\'hidden\')">بستن ✕</button></div>';
        if (!list.length) { html += '<div class="text-gray-500 text-sm">سابقه‌ای ثبت نشده است.</div>'; }
        else {
            const sorted = list.slice().sort((a, b) => String(b.effective_from || '').localeCompare(String(a.effective_from || '')));
            html += sorted.map(a => {
                const sup = a.supervisor_user_id ? (byId[Number(a.supervisor_user_id)] ? this.ccUserName(byId[Number(a.supervisor_user_id)]) : ('#' + a.supervisor_user_id)) : 'بدون سرپرست';
                return '<div class="flex items-center justify-between border-b border-gray-800 py-1 text-xs"><span class="text-gray-300">' + esc(sup) + '</span><span class="text-gray-500">' + esc(this.ccDateRangeLabel(a.effective_from || '', a.effective_to || '', 'تاکنون')) + '</span></div>';
            }).join('');
        }
        box.innerHTML = html;
        box.classList.remove('hidden');
        box.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    },
    ccDragStart(e, uid) { this._ccDragUser = Number(uid); try { e.dataTransfer.setData('text/plain', String(uid)); } catch (_) {} if (e.currentTarget) e.currentTarget.classList.add('dragging'); },
    ccDragEnd(e) { if (e && e.currentTarget) e.currentTarget.classList.remove('dragging'); },
    ccDragOver(e) { e.preventDefault(); if (e.currentTarget) e.currentTarget.classList.add('dragover'); },
    ccDragLeave(e) { if (e.currentTarget) e.currentTarget.classList.remove('dragover'); },
    async ccDrop(e, supId, teamType) {
        e.preventDefault(); if (e.currentTarget) e.currentTarget.classList.remove('dragover');
        let uid = this._ccDragUser;
        if (!uid) { try { uid = Number(e.dataTransfer.getData('text/plain')); } catch (_) {} }
        this._ccDragUser = null;
        if (!uid) return;
        const cur = this.ccActiveAssignment(uid);
        if (String(teamType || '') === 'management') {
            if (cur && Number(cur.manager_user_id || 0) === Number(supId || 0) && !Number(cur.supervisor_user_id || 0)) return;
            await this.ccChangeManager(uid, supId, this.ccTeamEffDate(), { managerRate: this.ccTeamManagerRate(supId) });
            return;
        }
        if (cur && Number(cur.supervisor_user_id || 0) === Number(supId || 0)) return;
        const oldTeam = this.ccActiveSalesTeamForUser(uid);
        const newTeam = this.ccTeamBySupervisorId(supId);
        await this.ccChangeSupervisor(uid, supId, this.ccTeamEffDate(), { supervisorRate: this.ccTeamSupervisorRate(supId), quiet: true, skipReload: true });
        await this.ccSyncMovedUserTeamRules(uid, oldTeam, newTeam);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تیم و قوانین تیمی بروزرسانی شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadTeams();
    },
    async ccChangeSupervisor(uid, supId, fromDate, options) {
        options = options || {};
        const sup = Number(supId || 0);
        let managerId = '';
        if (sup > 0) { const a = this.ccActiveAssignment(sup); if (a && a.manager_user_id) managerId = Number(a.manager_user_id); }
        const from = this.ccToGregorianDate(fromDate || '') || (this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10));
        const res = await this.req('save_user_hierarchy', {
            user_id: Number(uid),
            supervisor_user_id: sup > 0 ? sup : '',
            manager_user_id: managerId,
            supervisor_commission_rate_percent: sup > 0 ? (options.supervisorRate || '') : '',
            manager_commission_rate_percent: '',
            effective_from: from,
            effective_to: ''
        });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تغییر سرپرست', 'error'); return; }
        if (!options.quiet) Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'سرپرست بروزرسانی شد', showConfirmButton: false, timer: 1600 });
        if (!options.skipReload) await this.ccLoadTeams();
    },
    async ccChangeManager(uid, managerId, fromDate, options) {
        options = options || {};
        const manager = Number(managerId || 0);
        const from = this.ccToGregorianDate(fromDate || '') || (this.getTodayGregorianDateOnly ? this.getTodayGregorianDateOnly() : new Date().toISOString().slice(0, 10));
        // Each save writes a fresh single-row assignment, so preserve the user's existing
        // supervisor relationship and — when no explicit rate is passed — their existing
        // manager share rate. Otherwise a routine team edit (drag/toggle) would silently
        // wipe a previously configured manager share.
        const cur = this.ccActiveAssignment(Number(uid)) || {};
        const hasVal = (v) => v !== null && v !== undefined && String(v).trim() !== '';
        const keepSup = Number(cur.supervisor_user_id || 0) || '';
        const keepSupRate = (keepSup !== '' && hasVal(cur.supervisor_commission_rate_percent)) ? cur.supervisor_commission_rate_percent : '';
        let managerRate = hasVal(options.managerRate) ? options.managerRate : '';
        if (manager > 0 && managerRate === '' && Number(cur.manager_user_id || 0) === manager && hasVal(cur.manager_commission_rate_percent)) {
            managerRate = cur.manager_commission_rate_percent;
        }
        const res = await this.req('save_user_hierarchy', {
            user_id: Number(uid),
            supervisor_user_id: keepSup,
            manager_user_id: manager > 0 ? manager : '',
            supervisor_commission_rate_percent: keepSupRate,
            manager_commission_rate_percent: manager > 0 ? managerRate : '',
            effective_from: from,
            effective_to: ''
        });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تغییر مدیر', 'error'); return; }
        if (!options.quiet) Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'مدیر بروزرسانی شد', showConfirmButton: false, timer: 1600 });
        if (!options.skipReload) await this.ccLoadTeams();
    },
    async ccToggleTeamActive(supId, teamType, shouldActivate) {
        supId = Number(supId || 0);
        shouldActivate = Number(shouldActivate || 0) === 1;
        if (!supId) return;
        const isManagementTeam = String(teamType || '') === 'management';
        const team = this.ccBuildTeams(true).find(t => Number(t.supervisor_id || 0) === supId && (isManagementTeam ? String(t.type || '') === 'management' : String(t.type || 'sales') !== 'management'));
        if (!team) return;
        const memberIds = this.ccTeamMembersForStateChange(team, shouldActivate);
        if (!memberIds.length) {
            Swal.fire('تیم', shouldActivate ? 'این تیم عضوی برای فعال‌سازی ندارد.' : 'این تیم عضو فعال ندارد.', 'info');
            return;
        }
        const c = await Swal.fire({
            title: shouldActivate ? 'فعال‌سازی تیم' : 'غیرفعال‌سازی تیم',
            text: shouldActivate ? 'اعضای تاریخی این تیم از امروز دوباره عضو همین تیم می‌شوند. ادامه می‌دهید؟' : 'اعضای فعال این تیم از امروز از تیم خارج می‌شوند. ادامه می‌دهید؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: shouldActivate ? 'فعال کن' : 'غیرفعال کن',
            cancelButtonText: 'لغو'
        });
        if (!c.isConfirmed) return;
        const from = this.ccTeamEffDate();
        if (shouldActivate) this.ccRememberManualTeam(supId);
        for (const uid of memberIds) {
            if (isManagementTeam) {
                await this.ccChangeManager(uid, shouldActivate ? supId : 0, from, { quiet: true, skipReload: true, managerRate: shouldActivate ? this.ccTeamManagerRate(supId) : '' });
                continue;
            }
            const oldTeam = this.ccActiveSalesTeamForUser(uid);
            await this.ccChangeSupervisor(uid, shouldActivate ? supId : 0, from, { quiet: true, skipReload: true, supervisorRate: shouldActivate ? this.ccTeamSupervisorRate(supId) : '' });
            await this.ccSyncMovedUserTeamRules(uid, oldTeam || team, shouldActivate ? team : null);
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: shouldActivate ? 'تیم فعال شد' : 'تیم غیرفعال شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadTeams();
    },
    async ccDeleteTeam(supId, teamType) {
        supId = Number(supId || 0);
        if (!supId) return;
        const isManagementTeam = String(teamType || '') === 'management';
        const team = this.ccBuildTeams(true).find(t => Number(t.supervisor_id || 0) === supId && (isManagementTeam ? String(t.type || '') === 'management' : String(t.type || 'sales') !== 'management'));
        if (!team) return;
        const count = Array.isArray(team.members) ? team.members.length : 0;
        const c = await Swal.fire({
            title: 'حذف تیم',
            text: count ? (isManagementTeam ? 'همه سوابق این تیم مدیریتی حذف می‌شود. ادامه می‌دهید؟' : 'همه سوابق این تیم و تخصیص‌های تیمی اعضا حذف می‌شود. ادامه می‌دهید؟') : 'این تیم خالی حذف شود؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف تیم',
            cancelButtonText: 'لغو'
        });
        if (!c.isConfirmed) return;
        const memberIds = this.ccTeamMembersForStateChange(team, true);
        if (!isManagementTeam) {
            const ruleIds = this.ccTeamRuleIds(team);
            for (const uid of memberIds) {
                await this.ccDeleteUserTeamRules(uid, ruleIds, supId);
            }
        }
        const res = await this.req('delete_user_hierarchy_team', {
            owner_user_id: supId,
            team_type: isManagementTeam ? 'management' : 'sales'
        });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در حذف تیم', 'error'); return; }
        if (!isManagementTeam) this.ccForgetManualTeam(supId);
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تیم حذف شد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadTeams();
    },
    async ccSaveTeamBoardSelection() {
        const supId = Number((document.getElementById('cc-team-supervisor') || {}).value || 0);
        const selectedMembers = this.ccSelectedTeamMemberIds();
        if (!supId) { Swal.fire('خطا', 'سرپرست تیم را انتخاب کنید', 'error'); return; }
        this.ccRememberManualTeam(supId);
        const supervisorRate = this.ccTeamSupervisorRate(supId);
        const newTeam = this.ccTeamBySupervisorId(supId);
        for (const uid of selectedMembers) {
            if (uid && uid !== supId) {
                const oldTeam = this.ccActiveSalesTeamForUser(uid);
                await this.ccChangeSupervisor(uid, supId, this.ccTeamEffDate(), { quiet: true, skipReload: true, supervisorRate });
                await this.ccSyncMovedUserTeamRules(uid, oldTeam, newTeam);
            }
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تیم بروزرسانی شد', showConfirmButton: false, timer: 1800 });
        await this.ccLoadTeams();
    },
    async ccLoadAssignments() {
        await this.ccEnsureSetup();
        if (!Array.isArray(this._ccTeamMembers) || !Array.isArray(this._ccHierAssigns)) {
            const teamRes = await this.req('get_users_list', {});
            const users = (teamRes && teamRes.status === 'success' && Array.isArray(teamRes.users)) ? teamRes.users : [];
            const assigns = (teamRes && Array.isArray(teamRes.hierarchy_assignments)) ? teamRes.hierarchy_assignments : [];
            this._ccUsers = users;
            this._ccHierAssigns = assigns;
            this._ccTeamMembers = users.filter(u => !('is_active' in u) || Number(u.is_active) !== 0);
            this._ccUsersById = {};
            users.forEach(u => { this._ccUsersById[Number(u.id)] = u; });
        }
        const rr = await this.req('get_commission_rules', {});
        let rules = (rr && rr.status === 'success' && Array.isArray(rr.rules)) ? rr.rules : [];
        let assigns = (rr && Array.isArray(rr.assignments)) ? rr.assignments : [];
        this._ccRules = rules; this._ccAssignments = assigns;
        this._ccRuleAssigns = assigns;
        this._ccRuleTitle = {};
        rules.forEach(r => { this._ccRuleTitle[Number(r.id)] = String(r.title || this.getCommissionRuleAmountLabel(r)).replace(/\n/g, ' '); });
        if (await this.ccRepairTeamRuleAssignments()) {
            rules = this._ccRules || [];
            assigns = this._ccAssignments || [];
        }
        const ruleSel = document.getElementById('cc-asn-rule');
        if (ruleSel) ruleSel.innerHTML = rules.map(r => { const t = this.getCommissionRuleTypeLabel(r.rule_type); const nm = String(r.title || (t + ' - ' + this.getCommissionRuleAmountLabel(r))); return `<option value="${Number(r.id || 0)}">${this.escapeHtml(nm)}</option>`; }).join('');
        const userSel = document.getElementById('cc-asn-user'); if (userSel) userSel.innerHTML = this.ccAgentOptions();
        this.ccRenderAssignRulePicker();
        this.ccRenderAssignUserPicker();
        this.ccRenderAssignTeamPicker();
        this.ccOnAssignTargetChange();
        const ruleName = {}; rules.forEach(r => { ruleName[Number(r.id)] = String(r.title || (this.getCommissionRuleTypeLabel(r.rule_type) + ' - ' + this.getCommissionRuleAmountLabel(r))); });
        const userName = {}; (this._ccSetup && this._ccSetup.users ? this._ccSetup.users : []).forEach(u => { userName[Number(u.id)] = this.ccUserName(u); });
        const list = document.getElementById('cc-assign-list'); if (!list) return;
        const cnt = document.getElementById('cc-assign-count');
        const activeAssignments = assigns.filter(a => !a.disabled_at);
        if (cnt) cnt.textContent = assigns.length ? (this.formatNumber(activeAssignments.length) + ' فعال از ' + this.formatNumber(assigns.length)) : '';
        const deleteAllBtn = document.getElementById('cc-delete-all-assignments-btn');
        if (deleteAllBtn) deleteAllBtn.classList.toggle('hidden', assigns.length === 0);
        if (!assigns.length) { list.innerHTML = '<div class="text-gray-500 text-sm">تخصیصی ثبت نشده است.</div>'; return; }
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        this._ccAssignmentProductDetails = {};
        list.innerHTML = assigns.map(a => {
            const rn = ruleName[Number(a.rule_id)] || ('#' + a.rule_id);
            const all = String(a.scope_type || '').toUpperCase() === 'ALL' || !a.salesperson_user_id;
            const who = all ? 'همه کارشناسان' : (userName[Number(a.salesperson_user_id)] || ('#' + a.salesperson_user_id));
            const prods = Array.isArray(a.product_names) ? a.product_names.join('، ') : (a.product_names || (a.product_ids ? String(a.product_ids).split(',').filter(Boolean).length + ' محصول' : 'همه محصولات'));
            const isActive = !a.disabled_at;
            const id = Number(a.id || 0);
            const validity = this.ccDateRangeLabel(a.valid_from || '', a.valid_to || '');
            const teamMeta = this.ccAssignmentTeamMeta(a, userName);
            const productLabel = String(prods || 'همه محصولات');
            const hasProductDetails = productLabel !== 'همه محصولات' && productLabel.trim() !== '';
            if (hasProductDetails) this._ccAssignmentProductDetails[id] = productLabel.split('،').map(s => s.trim()).filter(Boolean);
            const productDetailButton = hasProductDetails
                ? '<button type="button" class="cc-assign-product-detail" onclick="app.ccShowAssignmentProducts(' + id + ')">جزئیات</button>'
                : '';
            const actions = '<div class="cc-assign-actions">'
                + '<button type="button" class="edit" title="تغییر قانون" onclick="app.ccOpenEditAssignment(' + id + ')"><i class="fa-solid fa-pen"></i></button>'
                + '<button type="button" class="delete" title="حذف تخصیص" onclick="app.ccDeleteAssignment(' + id + ')"><i class="fa-solid fa-trash"></i></button>'
                + '</div>';
            return '<article class="cc-assign-card' + (isActive ? '' : ' is-inactive') + '">'
                + '<div class="cc-assign-status"><span class="cc-badge ' + (isActive ? 'cc-b-approved' : 'cc-b-rejected') + '">' + (isActive ? 'فعال' : 'غیرفعال') + '</span></div>'
                + '<div class="cc-assign-cell"><small>کارشناس</small><strong>' + esc(who) + '</strong><small>' + esc(teamMeta.teamLabel) + ' · ' + esc(teamMeta.originLabel) + '</small></div>'
                + '<div class="cc-assign-cell"><small>قانون</small><strong>' + esc(rn) + '</strong></div>'
                + '<div class="cc-assign-cell"><small>اعتبار قانون</small><strong>' + esc(validity) + '</strong></div>'
                + '<div class="cc-assign-cell"><small>محصولات</small><div class="cc-assign-products-row"><div class="cc-assign-products">' + esc(productLabel) + '</div>' + productDetailButton + '</div></div>'
                + '<div class="cc-assign-toggle"><span class="cc-switch' + (isActive ? ' on' : '') + '" title="فعال/غیرفعال" onclick="app.ccToggleAssignment(' + id + ', ' + (isActive ? 0 : 1) + ')"></span>' + actions + '</div>'
                + '</article>';
        }).join('');
        if (this._ccState && this._ccState.sub === 'policy') this.ccRenderPolicyDashboard();
    },
    ccAssignmentById(id) {
        id = Number(id || 0);
        return (this._ccAssignments || []).find(a => Number(a.id || 0) === id) || null;
    },
    async ccOpenEditAssignment(id) {
        const assignment = this.ccAssignmentById(id);
        if (!assignment) return;
        if (!Array.isArray(this._ccRules) || !this._ccRules.length) {
            const rr = await this.req('get_commission_rules', {});
            this._ccRules = (rr && Array.isArray(rr.rules)) ? rr.rules : [];
        }
        const rules = (this._ccRules || []).filter(r => Number(r.is_active != null ? r.is_active : 1) !== 0 && String(r.status || 'active').toLowerCase() !== 'inactive');
        const esc = (s) => this.escapeHtml(String(s == null ? '' : s));
        const ruleOptions = rules.map(r => '<option value="' + Number(r.id || 0) + '"' + (Number(r.id || 0) === Number(assignment.rule_id || 0) ? ' selected' : '') + '>' + esc(this.ccRuleName(r)) + '</option>').join('');
        const productIds = String(assignment.product_ids || '').split(',').map(v => Number(v || 0)).filter(v => v > 0);
        const selectedProducts = new Set(productIds);
        const products = this.ccProductData();
        const productPicker = products.length ? products.map(product => {
            const checked = selectedProducts.has(Number(product.id));
            return '<label class="cc-product-choice"><span>' + esc(product.name) + '</span>'
                + '<input type="checkbox" value="' + Number(product.id) + '" ' + (checked ? 'checked' : '') + ' onchange="app.ccSyncEditAssignmentProducts()">'
                + '</label>';
        }).join('') : '<div class="text-gray-500 text-xs py-2">محصولی برای انتخاب ثبت نشده است.</div>';
        const allProducts = productIds.length === 0;
        const ruleValidity = this.ccDateRangeLabel(assignment.rule_valid_from || assignment.valid_from || '', assignment.rule_valid_to || assignment.valid_to || '');
        const html = '<div class="cc-team-rule-modal-body">'
            + '<div class="cc-team-modal-field"><label>قانون</label><select id="cc-edit-assignment-rule">' + ruleOptions + '</select></div>'
            + '<div class="cc-team-rule-modal-hint">اعتبار قانون از بخش ویرایش خود قانون تنظیم می‌شود: ' + esc(ruleValidity || 'بدون محدودیت') + '</div>'
            + '<div class="cc-team-modal-field"><label>محصولات تخصیص</label>'
            + '<label class="cc-product-all"><span>همه محصولات</span><input id="cc-edit-assignment-products-all" type="checkbox" ' + (allProducts ? 'checked' : '') + ' onchange="app.ccToggleEditAssignmentProductsAll(this.checked)"></label>'
            + '<div id="cc-edit-assignment-products" class="cc-product-picker">' + productPicker + '</div>'
            + '<p class="cc-team-modal-help">اگر همه محصولات فعال باشد، تخصیص برای کل دامنه قانون اعمال می‌شود؛ در غیر این صورت فقط محصولات انتخاب‌شده محاسبه می‌شوند.</p>'
            + '</div>'
            + '</div>';
        const result = await Swal.fire({
            title: 'ویرایش تخصیص کارشناس',
            html,
            showCancelButton: true,
            confirmButtonText: 'ذخیره تغییر',
            cancelButtonText: 'لغو',
            customClass: { popup: 'cc-team-rule-modal' },
            didOpen: () => {
                this.ccToggleEditAssignmentProductsAll(allProducts);
            },
            preConfirm: () => {
                const ruleId = Number((document.getElementById('cc-edit-assignment-rule') || {}).value || 0);
                if (!ruleId) {
                    Swal.showValidationMessage('قانون را انتخاب کنید');
                    return false;
                }
                return { ruleId, productIds: this.ccSelectedEditAssignmentProductIds() };
            }
        });
        if (!result.isConfirmed || !result.value) return;
        const res = await this.req('update_commission_assignment', {
            assignment_id: Number(id),
            rule_id: Number(result.value.ruleId),
            product_ids: JSON.stringify(result.value.productIds || [])
        });
        if (!res || res.status !== 'success') {
            Swal.fire('خطا', (res && res.message) || 'خطا در تغییر قانون تخصیص', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون کارشناس تغییر کرد', showConfirmButton: false, timer: 1600 });
        await this.ccLoadAssignments();
    },
    ccToggleEditAssignmentProductsAll(checked) {
        Array.from(document.querySelectorAll('#cc-edit-assignment-products input[type="checkbox"]')).forEach(input => { input.checked = !!checked; input.disabled = !!checked; });
    },
    ccSyncEditAssignmentProducts() {
        const inputs = Array.from(document.querySelectorAll('#cc-edit-assignment-products input[type="checkbox"]'));
        const allBox = document.getElementById('cc-edit-assignment-products-all');
        if (allBox) allBox.checked = false;
        inputs.forEach(input => { input.disabled = false; });
    },
    ccSelectedEditAssignmentProductIds() {
        const allBox = document.getElementById('cc-edit-assignment-products-all');
        if (allBox && allBox.checked) return [];
        return Array.from(document.querySelectorAll('#cc-edit-assignment-products input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean);
    },
    async ccDeleteAssignment(id) {
        id = Number(id || 0);
        if (!id) return;
        const c = await Swal.fire({
            title: 'حذف تخصیص',
            text: 'این تخصیص قانون از کارشناس حذف شود؟',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف',
            cancelButtonText: 'لغو'
        });
        if (!c.isConfirmed) return;
        const res = await this.req('delete_commission_assignment', { assignment_id: id });
        if (!res || res.status !== 'success') {
            Swal.fire('خطا', (res && res.message) || 'خطا در حذف تخصیص', 'error');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'تخصیص حذف شد', showConfirmButton: false, timer: 1500 });
        await this.ccLoadAssignments();
    },
    async ccDeleteAllAssignments() {
        const assignments = Array.isArray(this._ccAssignments) ? this._ccAssignments : [];
        const assignmentIds = assignments
            .filter(Boolean)
            .map(assignment => Number(assignment.id || 0))
            .filter(id => id > 0);
        if (!assignmentIds.length) {
            Swal.fire('', 'تخصیصی برای حذف وجود ندارد.', 'info');
            return;
        }
        const confirm = await Swal.fire({
            title: 'حذف همه تخصیص‌ها؟',
            text: `${this.formatNumber(assignmentIds.length)} تخصیص، شامل فعال و غیرفعال، حذف می‌شود. این کار قابل برگشت نیست.`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'حذف همه',
            cancelButtonText: 'لغو',
            confirmButtonColor: '#dc2626'
        });
        if (!confirm.isConfirmed) return;

        Swal.fire({
            title: 'در حال حذف...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        let deleted = 0;
        const failed = [];
        for (const id of assignmentIds) {
            try {
                const res = await this.req('delete_commission_assignment', { assignment_id: id });
                if (res && res.status === 'success') {
                    deleted++;
                } else {
                    failed.push({ id, message: (res && res.message) || 'خطا در حذف تخصیص' });
                }
            } catch (error) {
                failed.push({ id, message: error?.message || 'خطا در حذف تخصیص' });
            }
        }

        await this.ccLoadAssignments();
        if (failed.length) {
            Swal.fire('حذف ناقص', `${this.formatNumber(deleted)} تخصیص حذف شد، ${this.formatNumber(failed.length)} مورد حذف نشد.`, 'warning');
            return;
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: `${this.formatNumber(deleted)} تخصیص حذف شد`, showConfirmButton: false, timer: 1800 });
    },
    ccShowAssignmentProducts(id) {
        const products = (this._ccAssignmentProductDetails && this._ccAssignmentProductDetails[Number(id)]) || [];
        if (!products.length) return;
        const html = '<div class="cc-products-modal-meta">' + this.formatNumber(products.length) + ' محصول در این تخصیص</div>'
            + '<div class="cc-products-modal-body">'
            + products.map(name => '<div class="cc-products-modal-item" title="' + this.escapeHtml(name) + '">' + this.escapeHtml(name) + '</div>').join('')
            + '</div>';
        Swal.fire({
            title: 'محصولات تخصیص',
            html,
            confirmButtonText: 'بستن',
            width: 520,
            customClass: { popup: 'cc-products-modal' },
        });
    },
    ccRenderAssignUserPicker() {
        const select = document.getElementById('cc-asn-user');
        const picker = document.getElementById('cc-asn-user-picker');
        if (!select || !picker) return;
        if (!select.options.length) select.innerHTML = this.ccAgentOptions();
        const selected = new Set(Array.from(select.selectedOptions || []).map(o => Number(o.value || 0)).filter(Boolean));
        const options = Array.from(select.options || []);
        picker.innerHTML = options.length ? options.map(option => {
            const id = Number(option.value || 0);
            return '<label class="cc-asn-user-choice"><span>' + this.escapeHtml(option.textContent || option.value || '') + '</span>'
                + '<input type="checkbox" value="' + id + '" ' + (selected.has(id) ? 'checked' : '') + ' onchange="app.ccSyncAssignUsersFromPicker()"></label>';
        }).join('') : '<div class="text-gray-500 text-xs py-2">کارشناسی برای انتخاب وجود ندارد.</div>';
    },
    ccRenderAssignRulePicker() {
        const select = document.getElementById('cc-asn-rule');
        const picker = document.getElementById('cc-asn-rule-picker');
        if (!select || !picker) return;
        const selected = new Set(Array.from(select.selectedOptions || []).map(o => Number(o.value || 0)).filter(Boolean));
        const rules = (this._ccRules || []).filter(r => Number(r.is_active != null ? r.is_active : 1) !== 0 && String(r.status || 'active').toLowerCase() !== 'inactive');
        if (!rules.length) {
            picker.innerHTML = '<div class="text-gray-500 text-xs py-2">قانون فعالی برای انتخاب وجود ندارد.</div>';
            return;
        }
        picker.innerHTML = rules.map(rule => {
            const id = Number(rule.id || 0);
            const name = this.ccRuleName(rule) || ('قانون #' + id);
            const meta = [this.getCommissionRuleTypeLabel(rule.rule_type), this.getCommissionRuleAmountLabel(rule)].filter(Boolean).join(' · ');
            return '<label class="cc-asn-user-choice"><span><strong>' + this.escapeHtml(name) + '</strong>'
                + '<small class="meta">' + this.escapeHtml(meta || 'قانون پورسانت') + '</small></span>'
                + '<input type="checkbox" value="' + id + '" ' + (selected.has(id) ? 'checked' : '') + ' onchange="app.ccSyncAssignRulesFromPicker()"></label>';
        }).join('');
    },
    ccSyncAssignRulesFromPicker() {
        const select = document.getElementById('cc-asn-rule');
        if (!select) return;
        const picked = new Set(Array.from(document.querySelectorAll('#cc-asn-rule-picker input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean));
        Array.from(select.options || []).forEach(option => { option.selected = picked.has(Number(option.value || 0)); });
    },
    ccSelectedAssignRuleIds() {
        this.ccSyncAssignRulesFromPicker();
        return Array.from(document.getElementById('cc-asn-rule') ? document.getElementById('cc-asn-rule').selectedOptions : [])
            .map(o => Number(o.value || 0))
            .filter(v => v > 0);
    },
    ccSelectAllAssignRules() {
        Array.from(document.querySelectorAll('#cc-asn-rule-picker input[type="checkbox"]')).forEach(input => { input.checked = true; });
        this.ccSyncAssignRulesFromPicker();
    },
    ccClearAssignRules() {
        Array.from(document.querySelectorAll('#cc-asn-rule-picker input[type="checkbox"]')).forEach(input => { input.checked = false; });
        const select = document.getElementById('cc-asn-rule');
        if (select) Array.from(select.options || []).forEach(option => { option.selected = false; });
    },
    ccSalesTeamsForAssignment() {
        return this.ccBuildTeams()
            .filter(t => String(t.type || 'sales') !== 'management' && Number(t.supervisor_id || 0) > 0 && Array.isArray(t.members) && t.members.length > 0)
            .sort((a, b) => this.ccUserName(a.supervisor || {}).localeCompare(this.ccUserName(b.supervisor || {}), 'fa'));
    },
    ccRenderAssignTeamPicker() {
        const picker = document.getElementById('cc-asn-team-picker');
        if (!picker) return;
        const selected = new Set(Array.from(document.querySelectorAll('#cc-asn-team-picker input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean));
        const teams = this.ccSalesTeamsForAssignment();
        if (!teams.length) {
            picker.innerHTML = '<div class="text-gray-500 text-xs py-2">تیم فروش فعالی برای تخصیص قانون وجود ندارد.</div>';
            return;
        }
        picker.innerHTML = teams.map(team => {
            const supId = Number(team.supervisor_id || 0);
            const membersCount = Array.isArray(team.members) ? team.members.length : 0;
            const rate = this.ccTeamSupervisorRate(supId);
            const summary = this.ccTeamRuleSummary(team) || 'بدون قانون تیم';
            return '<label class="cc-asn-user-choice"><span><strong>' + this.escapeHtml(this.ccUserName(team.supervisor || {})) + '</strong><small class="meta">' + this.escapeHtml(this.formatNumber(membersCount) + ' عضو' + (rate !== '' ? ' · سهم سرپرست ' + this.ccFormatPercentValue(rate) + '٪' : '') + ' · ' + summary) + '</small></span>'
                + '<input type="checkbox" value="' + supId + '" ' + (selected.has(supId) ? 'checked' : '') + '></label>';
        }).join('');
    },
    ccSelectedAssignTeamIds() {
        return Array.from(document.querySelectorAll('#cc-asn-team-picker input[type="checkbox"]:checked'))
            .map(input => Number(input.value || 0))
            .filter(Boolean);
    },
    ccSelectAllAssignTeams() {
        Array.from(document.querySelectorAll('#cc-asn-team-picker input[type="checkbox"]')).forEach(input => { input.checked = true; });
    },
    ccClearAssignTeams() {
        Array.from(document.querySelectorAll('#cc-asn-team-picker input[type="checkbox"]')).forEach(input => { input.checked = false; });
    },
    ccAssignTeamMemberIds(teamIds) {
        const selected = new Set((teamIds || []).map(Number).filter(Boolean));
        const memberIds = [];
        this.ccSalesTeamsForAssignment().forEach(team => {
            if (!selected.has(Number(team.supervisor_id || 0))) return;
            (team.members || []).forEach(member => {
                const uid = Number(member.id || 0);
                if (uid > 0) memberIds.push(uid);
            });
        });
        return Array.from(new Set(memberIds));
    },
    ccSyncAssignUsersFromPicker() {
        const select = document.getElementById('cc-asn-user');
        if (!select) return;
        const picked = new Set(Array.from(document.querySelectorAll('#cc-asn-user-picker input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean));
        Array.from(select.options || []).forEach(option => { option.selected = picked.has(Number(option.value || 0)); });
    },
    ccSelectAllAssignUsers() {
        Array.from(document.querySelectorAll('#cc-asn-user-picker input[type="checkbox"]')).forEach(input => { input.checked = true; });
        this.ccSyncAssignUsersFromPicker();
    },
    ccClearAssignUsers() {
        Array.from(document.querySelectorAll('#cc-asn-user-picker input[type="checkbox"]')).forEach(input => { input.checked = false; });
        const select = document.getElementById('cc-asn-user');
        if (select) Array.from(select.options || []).forEach(option => { option.selected = false; });
    },
    ccRenderAssignProductPicker() {
        const select = document.getElementById('cc-asn-products');
        const picker = document.getElementById('cc-asn-product-picker');
        if (!select || !picker) return;
        if (!select.options.length) select.innerHTML = this.ccProductOptions();
        const allBox = document.getElementById('cc-asn-products-all');
        const all = allBox ? !!allBox.checked : true;
        const selected = new Set(Array.from(select.selectedOptions || []).map(o => Number(o.value || 0)).filter(Boolean));
        const options = Array.from(select.options || []);
        picker.innerHTML = options.length ? options.map(option => {
            const id = Number(option.value || 0);
            const checked = all || selected.has(id);
            return '<label class="cc-product-choice"><span>' + this.escapeHtml(option.textContent || option.value || '') + '</span>'
                + '<input type="checkbox" value="' + id + '" ' + (checked ? 'checked' : '') + ' onchange="app.ccSyncAssignProductsFromPicker()"></label>';
        }).join('') : '<div class="text-gray-500 text-xs py-2">محصولی برای انتخاب ثبت نشده است.</div>';
    },
    ccToggleAllAssignProducts(checked) {
        const select = document.getElementById('cc-asn-products');
        if (checked && select) Array.from(select.options || []).forEach(option => { option.selected = false; });
        this.ccRenderAssignProductPicker();
    },
    ccSyncAssignProductsFromPicker() {
        const select = document.getElementById('cc-asn-products');
        if (!select) return;
        const picked = new Set(Array.from(document.querySelectorAll('#cc-asn-product-picker input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean));
        Array.from(select.options || []).forEach(option => { option.selected = picked.has(Number(option.value || 0)); });
        const allBox = document.getElementById('cc-asn-products-all');
        if (allBox) allBox.checked = picked.size === 0;
        this.ccRenderAssignProductPicker();
    },
    ccSelectedAssignProductIds() {
        const allBox = document.getElementById('cc-asn-products-all');
        if (allBox && allBox.checked) return [];
        this.ccSyncAssignProductsFromPicker();
        return Array.from(document.getElementById('cc-asn-products') ? document.getElementById('cc-asn-products').selectedOptions : [])
            .map(o => Number(o.value || 0)).filter(v => v > 0);
    },
    async ccToggleAssignment(id, active) {
        const res = await this.req('toggle_commission_assignment', { assignment_id: Number(id), is_active: active ? 1 : 0 });
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در تغییر وضعیت تخصیص', 'error'); return; }
        await this.ccLoadAssignments();
    },
    async ccSaveRule() {
        const type = (document.getElementById('cc-rule-type') || {}).value || 'FIXED_RULE';
        const title = (document.getElementById('cc-rule-title') || {}).value || '';
        const from = (document.getElementById('cc-rule-from') || {}).value || '';
        const to = (document.getElementById('cc-rule-to') || {}).value || '';
        if (!this.ccValidateRuleDates()) { Swal.fire('خطا', '«معتبر تا» نمی‌تواند قبل از «معتبر از» باشد', 'error'); return; }
        const fixedMode = this.ccFixedValueMode();
        const fixedValue = this.ccNormalizeDecimalInput((document.getElementById('cc-rule-rate') || {}).value || '');
        this.ccSyncTierAutoMins();
        const tiers = this.ccCollectTiers();
        if (type === 'TIERED_RULE' && tiers.length < 2) { Swal.fire('خطا', 'برای قانون پلکانی حداقل دو پله تعریف کنید', 'error'); return; }
        if (type === 'FIXED_RULE' && this.ccParseDecimal(fixedValue || 0) <= 0) { Swal.fire('خطا', fixedMode === 'amount' ? 'برای قانون ثابت، مبلغ ثابت را وارد کنید' : 'برای قانون ثابت، درصد را وارد کنید', 'error'); return; }
        if (type === 'FIXED_RULE' && fixedMode === 'percent' && this.ccParseDecimal(fixedValue || 0) > 100) { Swal.fire('خطا', 'درصد ثابت باید بین ۰ تا ۱۰۰ باشد', 'error'); return; }
        const productIds = this.ccSelectedRuleProductIds();
        const payload = {
            id: (document.getElementById('cc-rule-id') || {}).value || '',
            rule_type: type,
            salesperson_user_id: '',
            target_users: JSON.stringify([]),
            requested_services: JSON.stringify(Array.from(new Set(productIds))),
            base_amount: type === 'FIXED_RULE' && fixedMode === 'amount' ? String(fixedValue) : '0',
            reward_percentage: type === 'TIERED_RULE' ? String((tiers[0] && tiers[0].commission_percent) || '0') : (fixedMode === 'percent' ? String(fixedValue) : '0'),
            tiers: JSON.stringify(type === 'TIERED_RULE' ? tiers : []),
            title: title, valid_from: from, valid_to: to
        };
        const res = await this.req('store_commission_rule', payload);
        if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در ذخیره قانون', 'error'); return; }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'قانون ذخیره شد', showConfirmButton: false, timer: 1800 });
        this.ccResetRuleForm();
        await this.ccLoadRules();
    },
    ccResetRuleForm() {
        ['cc-rule-id', 'cc-rule-title', 'cc-rule-from', 'cc-rule-to', 'cc-rule-rate'].forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });
        const t = document.getElementById('cc-rule-type'); if (t) t.value = 'FIXED_RULE';
        this.ccSetFixedValueMode('percent');
        const wrap = document.getElementById('cc-tier-rows'); if (wrap) wrap.innerHTML = '';
        this.ccSetRuleProductIds([]);
        this.ccOnRuleTypeChange();
    },
    ccDateCmp(a, b) {
        const p = (s) => String(s == null ? '' : s).trim().split(/[\/\-]/).map(x => parseInt(x, 10) || 0);
        const A = p(a), B = p(b);
        for (let i = 0; i < 3; i++) { if ((A[i] || 0) !== (B[i] || 0)) return (A[i] || 0) - (B[i] || 0); }
        return 0;
    },
    ccValidateRuleDates() {
        const from = ((document.getElementById('cc-rule-from') || {}).value || '').trim();
        const to = ((document.getElementById('cc-rule-to') || {}).value || '').trim();
        const warn = document.getElementById('cc-rule-date-warn');
        const bad = !!(from && to && this.ccDateCmp(to, from) < 0);
        if (warn) warn.classList.toggle('hidden', !bad);
        return !bad;
    },
    ccOnRuleTypeChange() {
        const t = (document.getElementById('cc-rule-type') || {}).value;
        const fixed = document.getElementById('cc-rule-fixed');
        const tiered = document.getElementById('cc-rule-tiered');
        if (fixed) fixed.classList.toggle('hidden', t !== 'FIXED_RULE');
        if (tiered) tiered.classList.toggle('hidden', t !== 'TIERED_RULE');
        if (t === 'TIERED_RULE') this.ccEnsureMinimumTierRows(2);
        this.ccRefreshFixedValueModeUi();
        if (typeof this.ccWhatIf === 'function') this.ccWhatIf();
    },
    ccFormatTierAmount(input) {
        if (!input) return;
        const digits = String(input.value || '').replace(/[^\d]/g, '');
        input.value = digits ? Number(digits).toLocaleString('en-US') : '';
    },
    ccFormatWhatIfAmount(input) {
        if (!input) return;
        const digits = String(input.value || '').replace(/[^\d]/g, '');
        input.value = digits ? Number(digits).toLocaleString('en-US') : '';
    },
    ccNormalizeDecimalInput(value) {
        return String(value == null ? '' : value).replace(/,/g, '').replace(/[^\d.]/g, '');
    },
    ccParseDecimal(value) {
        const n = Number(this.ccNormalizeDecimalInput(value));
        return Number.isFinite(n) ? n : 0;
    },
    ccFixedValueMode() {
        return (this._ccFixedValueMode === 'amount') ? 'amount' : 'percent';
    },
    ccSetFixedValueMode(mode) {
        this._ccFixedValueMode = mode === 'amount' ? 'amount' : 'percent';
        const input = document.getElementById('cc-rule-rate');
        if (input) {
            input.value = '';
        }
        this.ccRefreshFixedValueModeUi();
        if (typeof this.ccWhatIf === 'function') this.ccWhatIf();
    },
    ccRefreshFixedValueModeUi() {
        const mode = this.ccFixedValueMode();
        document.querySelectorAll('#cc-fixed-kind [data-mode]').forEach(item => {
            item.classList.toggle('active', item.getAttribute('data-mode') === mode);
        });
        const label = document.getElementById('cc-rule-rate-label');
        const input = document.getElementById('cc-rule-rate');
        if (label) label.textContent = mode === 'amount' ? 'مبلغ ثابت (تومان)' : 'درصد ثابت (%)';
        if (input) input.placeholder = mode === 'amount' ? 'مثلاً 200,000,000' : 'مثلاً 4';
    },
    ccFormatRuleRate(input) {
        if (!input) return;
        const raw = String(input.value || '').replace(/,/g, '').replace(/[^\d.]/g, '');
        if (raw === '') {
            input.value = '';
            return;
        }
        if (this.ccFixedValueMode() === 'percent' && raw.indexOf('.') !== -1) {
            const parts = raw.split('.');
            input.value = parts.shift().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '.' + parts.join('').replace(/\./g, '');
            return;
        }
        const normalized = this.ccFixedValueMode() === 'amount' ? raw.replace(/\..*$/, '') : raw;
        input.value = normalized.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    },
    ccFormatEditRuleRate(input) {
        if (!input) return;
        const mode = String((document.getElementById('cc-edit-rule-fixed-mode') || {}).value || 'percent');
        const raw = String(input.value || '').replace(/,/g, '').replace(/[^\d.]/g, '');
        if (raw === '') {
            input.value = '';
            return;
        }
        if (mode === 'percent' && raw.indexOf('.') !== -1) {
            const parts = raw.split('.');
            input.value = parts.shift().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '.' + parts.join('').replace(/\./g, '');
            return;
        }
        const normalized = mode === 'amount' ? raw.replace(/\..*$/, '') : raw;
        input.value = normalized.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    },
    ccRefreshTierRows() {
        this.ccSyncTierAutoMins();
        Array.from(document.querySelectorAll('#cc-tier-rows .cc-tier-row')).forEach((row, idx) => {
            const index = row.querySelector('.cc-tier-index');
            if (index) index.textContent = this.formatNumber(idx + 1);
        });
    },
    ccParseTierAmount(value) {
        const n = Math.floor(Number(String(value == null ? '' : value).replace(/,/g, '').replace(/[^\d.]/g, '')));
        return Number.isFinite(n) && n > 0 ? n : 0;
    },
    ccFormatPlainAmount(value, options = {}) {
        const raw = String(value == null ? '' : value).trim();
        if (options.showZero && raw !== '' && Number(raw.replace(/,/g, '').replace(/[^\d.]/g, '')) === 0) {
            return '0';
        }
        const n = Math.floor(Number(String(value == null ? '' : value).replace(/,/g, '').replace(/[^\d.]/g, '')) || 0);
        return n > 0 ? n.toLocaleString('en-US') : '';
    },
    ccFormatPercentInput(value) {
        if (value === null || value === undefined || String(value).trim() === '') return '';
        const numeric = Number(String(value).trim());
        return Number.isFinite(numeric) ? String(parseFloat(numeric.toFixed(4))) : String(value);
    },
    ccSyncTierAutoMins(force = false) {
        this.ccSyncTierAutoMinsFor('#cc-tier-rows', force);
    },
    ccSyncTierAutoMinsFor(containerSelector, force = false) {
        const rows = Array.from(document.querySelectorAll(containerSelector + ' .cc-tier-row'));
        rows.forEach((row, idx) => {
            const minInput = row.querySelector('.cc-tier-min');
            if (!minInput) return;
            if (idx === 0) {
                minInput.dataset.autoMin = '';
                return;
            }
            const prev = rows[idx - 1];
            const prevMax = this.ccParseTierAmount(prev && prev.querySelector('.cc-tier-max') ? prev.querySelector('.cc-tier-max').value : '');
            if (prevMax <= 0) return;
            const expected = prevMax + 1;
            const currentRaw = String(minInput.value || '').trim();
            const current = this.ccParseTierAmount(currentRaw);
            const previousAuto = this.ccParseTierAmount(minInput.dataset.autoMin || '');
            if (force || currentRaw === '' || (previousAuto > 0 && current === previousAuto)) {
                minInput.value = this.ccFormatPlainAmount(expected);
                minInput.dataset.autoMin = String(expected);
            }
        });
    },
    ccEnsureMinimumTierRows(minRows) {
        const wrap = document.getElementById('cc-tier-rows');
        if (!wrap) return;
        const target = Math.max(0, Number(minRows || 0));
        while (wrap.querySelectorAll('.cc-tier-row').length < target) {
            this.ccAddTierRow();
        }
    },
    ccAddTierRow(min, max, pct) {
        const wrap = document.getElementById('cc-tier-rows'); if (!wrap) return;
        const row = document.createElement('div');
        row.className = 'cc-tier-row';
        row.innerHTML = '<span class="cc-tier-index"></span>'
            + '<div class="cc-tier-range">'
            + '<input class="input cc-tier-min" inputmode="numeric" oninput="this.dataset.autoMin = \'\'; app.ccFormatTierAmount(this); app.ccWhatIf()" placeholder="از مبلغ" value="' + this.escapeHtml(this.ccFormatPlainAmount(min, { showZero: true })) + '">'
            + '<span class="cc-tier-range-sep">تا</span>'
            + '<input class="input cc-tier-max" inputmode="numeric" oninput="app.ccFormatTierAmount(this); app.ccSyncTierAutoMins(true); app.ccWhatIf()" placeholder="بدون سقف" value="' + this.escapeHtml(this.ccFormatPlainAmount(max)) + '">'
            + '</div>'
            + '<div class="cc-tier-pct-wrap"><input class="input cc-tier-pct" type="text" inputmode="decimal" oninput="app.ccWhatIf()" placeholder="3" value="' + this.escapeHtml(this.ccFormatPercentInput(pct)) + '"><span>٪</span></div>'
            + '<button type="button" class="cc-tier-remove" title="حذف پله" onclick="this.closest(\'.cc-tier-row\').remove(); app.ccWhatIf()"><i class="fa-solid fa-xmark"></i></button>';
        wrap.appendChild(row);
        this.ccWhatIf();
    },
    ccRefreshEditRuleTypeUi() {
        const type = String((document.getElementById('cc-edit-rule-type') || {}).value || 'TIERED_RULE');
        const fixedWrap = document.getElementById('cc-edit-rule-fixed-wrap');
        const tierWrap = document.getElementById('cc-edit-rule-tier-wrap');
        const fixedMode = document.getElementById('cc-edit-rule-fixed-mode');
        const fixedModeWrap = document.getElementById('cc-edit-rule-fixed-mode-wrap');
        if (fixedWrap) fixedWrap.classList.toggle('hidden', type !== 'FIXED_RULE');
        if (tierWrap) tierWrap.classList.toggle('hidden', type !== 'TIERED_RULE');
        if (fixedMode) fixedMode.disabled = type !== 'FIXED_RULE';
        if (fixedModeWrap) fixedModeWrap.classList.toggle('hidden', type !== 'FIXED_RULE');
    },
    ccAddEditRuleTierRow(min, max, pct, options = {}) {
        const wrap = document.getElementById('cc-edit-tier-rows'); if (!wrap) return;
        const row = document.createElement('div');
        row.className = 'cc-tier-row';
        row.innerHTML = '<span class="cc-tier-index"></span>'
            + '<div class="cc-tier-range">'
            + '<input class="input cc-tier-min" inputmode="numeric" oninput="this.dataset.autoMin = \'\'; app.ccFormatTierAmount(this); app.ccRefreshEditRuleTierRows()" placeholder="از مبلغ" value="' + this.escapeHtml(this.ccFormatPlainAmount(min, { showZero: true })) + '">'
            + '<span class="cc-tier-range-sep">تا</span>'
            + '<input class="input cc-tier-max" inputmode="numeric" oninput="app.ccFormatTierAmount(this); app.ccSyncEditRuleTierAutoMins(true); app.ccRefreshEditRuleTierRows()" placeholder="بدون سقف" value="' + this.escapeHtml(this.ccFormatPlainAmount(max)) + '">'
            + '</div>'
            + '<div class="cc-tier-pct-wrap"><input class="input cc-tier-pct" type="text" inputmode="decimal" oninput="app.ccRefreshEditRuleTierRows()" placeholder="3" value="' + this.escapeHtml(this.ccFormatPercentInput(pct)) + '"><span>٪</span></div>'
            + '<button type="button" class="cc-tier-remove" title="حذف پله" onclick="this.closest(\'.cc-tier-row\').remove(); app.ccRefreshEditRuleTierRows()"><i class="fa-solid fa-xmark"></i></button>';
        wrap.appendChild(row);
        if (!options.quiet) this.ccRefreshEditRuleTierRows();
    },
    ccSyncEditRuleTierAutoMins(force = false) {
        this.ccSyncTierAutoMinsFor('#cc-edit-tier-rows', force);
    },
    ccRefreshEditRuleTierRows() {
        this.ccSyncEditRuleTierAutoMins();
        Array.from(document.querySelectorAll('#cc-edit-tier-rows .cc-tier-row')).forEach((row, idx) => {
            const index = row.querySelector('.cc-tier-index');
            if (index) index.textContent = this.formatNumber(idx + 1);
        });
    },
    ccCollectEditRuleTiers() {
        return Array.from(document.querySelectorAll('#cc-edit-tier-rows .cc-tier-row')).map(r => ({
            min_amount: (r.querySelector('.cc-tier-min') ? r.querySelector('.cc-tier-min').value : '').replace(/,/g, '').trim(),
            max_amount: (r.querySelector('.cc-tier-max') ? r.querySelector('.cc-tier-max').value : '').replace(/,/g, '').trim(),
            commission_percent: (r.querySelector('.cc-tier-pct') ? r.querySelector('.cc-tier-pct').value : '').trim()
        })).filter(t => t.min_amount !== '' && t.commission_percent !== '');
    },
    ccToggleEditRuleProductsAll(checked) {
        Array.from(document.querySelectorAll('#cc-edit-rule-products input[type="checkbox"]')).forEach(input => { input.checked = !!checked; input.disabled = !!checked; });
    },
    ccSyncEditRuleProducts() {
        const allBox = document.getElementById('cc-edit-rule-products-all');
        if (allBox) allBox.checked = false;
        Array.from(document.querySelectorAll('#cc-edit-rule-products input[type="checkbox"]')).forEach(input => { input.disabled = false; });
    },
    ccSelectedEditRuleProductIds() {
        const allBox = document.getElementById('cc-edit-rule-products-all');
        if (allBox && allBox.checked) return [];
        return Array.from(document.querySelectorAll('#cc-edit-rule-products input[type="checkbox"]:checked')).map(input => Number(input.value || 0)).filter(Boolean);
    },
    ccOnAssignTargetChange() {
        const t = (document.getElementById('cc-asn-target') || {}).value;
        const wrap = document.getElementById('cc-asn-user-wrap');
        const teamWrap = document.getElementById('cc-asn-team-wrap');
        if (wrap) wrap.classList.toggle('hidden', t !== 'user');
        if (teamWrap) teamWrap.classList.toggle('hidden', t !== 'team');
        if (t === 'all') {
            this.ccClearAssignUsers();
            this.ccClearAssignTeams();
        }
        if (t === 'team') {
            this.ccClearAssignUsers();
            this.ccRenderAssignTeamPicker();
        }
        if (t === 'user') this.ccClearAssignTeams();
    },
    async ccChangeSupervisorFromForm() {
        const uid = Number((document.getElementById('cc-sup-user') || {}).value || 0);
        const sup = Number((document.getElementById('cc-sup-new') || {}).value || 0);
        const from = (document.getElementById('cc-sup-from') || {}).value || '';
        if (!uid) { Swal.fire('', 'ابتدا کارشناس را انتخاب کنید', 'info'); return; }
        await this.ccChangeSupervisor(uid, sup, from);
        await this.ccShowHistory();
    },
    async ccShowHistory() {
        const uid = Number((document.getElementById('cc-sup-user') || {}).value || 0);
        const box = document.getElementById('cc-supervisor-history'); if (!box) return;
        if (!uid) { box.innerHTML = ''; return; }
        const res = await this.req('get_user_hierarchy', { user_id: uid });
        const list = (res && Array.isArray(res.hierarchy_assignments)) ? res.hierarchy_assignments : [];
        if (!list.length) { box.innerHTML = '<div class="text-gray-600">سابقه‌ای ثبت نشده است</div>'; return; }
        const byId = this._ccUsersById || {};
        const sorted = list.slice().sort((a, b) => String(b.effective_from || '').localeCompare(String(a.effective_from || '')));
        box.innerHTML = sorted.map(a => {
            const sup = a.supervisor_user_id ? (byId[Number(a.supervisor_user_id)] ? this.ccUserName(byId[Number(a.supervisor_user_id)]) : ('#' + a.supervisor_user_id)) : 'بدون سرپرست';
            const range = this.ccDateRangeLabel(a.effective_from || '', a.effective_to || '', 'تاکنون');
            return '<div class="flex items-center justify-between border-b border-gray-800 pb-1"><span class="text-gray-300">' + this.escapeHtml(sup) + '</span><span class="text-gray-500">' + this.escapeHtml(range) + '</span></div>';
        }).join('');
    },
    async ccAssign() {
        const ruleIds = this.ccSelectedAssignRuleIds();
        if (!ruleIds.length) { Swal.fire('خطا', 'حداقل یک قانون انتخاب کنید', 'error'); return; }
        const target = (document.getElementById('cc-asn-target') || {}).value || 'user';
        let agentIds = [];
        if (target === 'all') agentIds = this.ccAgents().map(u => Number(u.id));
        else if (target === 'team') {
            const teamIds = this.ccSelectedAssignTeamIds();
            if (!teamIds.length) { Swal.fire('خطا', 'حداقل یک تیم فروش انتخاب کنید', 'error'); return; }
            agentIds = this.ccAssignTeamMemberIds(teamIds);
            if (!agentIds.length) { Swal.fire('خطا', 'تیم‌های انتخاب‌شده عضو فعالی برای تخصیص قانون ندارند', 'error'); return; }
        }
        else {
            this.ccSyncAssignUsersFromPicker();
            agentIds = Array.from(document.getElementById('cc-asn-user') ? document.getElementById('cc-asn-user').selectedOptions : []).map(o => Number(o.value || 0)).filter(v => v > 0);
        }
        if (!agentIds.length) { Swal.fire('خطا', 'حداقل یک کارشناس انتخاب کنید', 'error'); return; }
        if (target === 'team') {
            const selectedTeamIds = this.ccSelectedAssignTeamIds();
            for (const teamId of selectedTeamIds) {
                const members = this.ccAssignTeamMemberIds([teamId]);
                if (!members.length) continue;
                const res = await this.req('assign_commission_rules', {
                    rule_ids: JSON.stringify(Array.from(new Set(ruleIds))),
                    agent_ids: JSON.stringify(members),
                    product_ids: JSON.stringify([]),
                    source_type: 'TEAM',
                    source_team_supervisor_user_id: teamId
                });
                if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در ثبت تخصیص', 'error'); return; }
            }
        } else {
            const res = await this.req('assign_commission_rules', { rule_ids: JSON.stringify(Array.from(new Set(ruleIds))), agent_ids: JSON.stringify(agentIds), product_ids: JSON.stringify([]), source_type: 'INDIVIDUAL' });
            if (!res || res.status !== 'success') { Swal.fire('خطا', (res && res.message) || 'خطا در ثبت تخصیص', 'error'); return; }
        }
        Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: target === 'team' ? 'قانون‌ها به اعضای فعلی تیم تخصیص یافت' : 'تخصیص ثبت شد', showConfirmButton: false, timer: 1800 });
        await this.ccLoadAssignments();
    },

    // ===== Financial payments + C9 cheque workflow =====
    resolveAllowedFinancialTab(preferred = '') {
        const canPayments = this.canCurrentUserCapabilityClient('financial_payments.view');
        const canCheques = this.canCurrentUserCapabilityClient('cheques.view');
        if (preferred === 'cheques' && canCheques) return 'cheques';
        if (preferred === 'payments' && canPayments) return 'payments';
        if (canPayments) return 'payments';
        if (canCheques) return 'cheques';
        return 'payments';
    },

    switchFinancialTab(tab) {
        const next = this.resolveAllowedFinancialTab(tab === 'cheques' ? 'cheques' : 'payments');
        const canPayments = this.canCurrentUserCapabilityClient('financial_payments.view');
        const canCheques = this.canCurrentUserCapabilityClient('cheques.view');
        this.state.activeFinancialTab = next;
        const paymentsPanel = document.getElementById('financial-payments-panel');
        const chequesPanel = document.getElementById('financial-cheques-panel');
        const paymentsTab = document.getElementById('financial-tab-payments');
        const chequesTab = document.getElementById('financial-tab-cheques');
        paymentsTab?.classList.toggle('hidden', !canPayments);
        chequesTab?.classList.toggle('hidden', !canCheques);
        paymentsPanel?.classList.toggle('hidden', next !== 'payments');
        chequesPanel?.classList.toggle('hidden', next !== 'cheques');
        const setTab = (el, active) => {
            if (!el) return;
            el.classList.toggle('bg-yellow-500', active);
            el.classList.toggle('text-black', active);
            el.classList.toggle('shadow-sm', active);
            el.classList.toggle('text-gray-400', !active);
            el.classList.toggle('hover:text-white', !active);
            el.classList.toggle('hover:bg-gray-800/70', !active);
            el.style.color = active ? '#000' : '';
        };
        setTab(paymentsTab, next === 'payments');
        setTab(chequesTab, next === 'cheques');
        if (next === 'payments') {
            this.loadFinancialPayments();
        } else {
            this.loadCheques();
        }
    },

    refreshFinancialPaymentsCheques() {
        if ((this.state.activeFinancialTab || 'payments') === 'cheques') {
            this.loadCheques();
            return;
        }
        this.loadFinancialPayments();
    },

    handleFinancialPaymentsSearchInput(input) {
        const clearButton = document.getElementById('payments-filter-search-clear');
        if (clearButton) {
            const hasValue = !!String(input?.value || '').trim();
            clearButton.classList.toggle('hidden', !hasValue);
            clearButton.classList.toggle('inline-flex', hasValue);
        }
    },

    clearFinancialPaymentsSearch() {
        const input = document.getElementById('payments-filter-search');
        if (input) input.value = '';
        const clearButton = document.getElementById('payments-filter-search-clear');
        if (clearButton) {
            clearButton.classList.add('hidden');
            clearButton.classList.remove('inline-flex');
        }
        clearTimeout(this.financialPaymentsSearchTimer);
    },

    async ensureFinancialPaymentFiltersReady() {
        this.initFinancialPaymentDateFilters();
        if (!this.state.lookupsLoaded) {
            await this.loadLookups();
        }
        if ((!Array.isArray(this.state.didarUsers) || this.state.didarUsers.length === 0) && !this.financialPaymentUsersLoadAttempted) {
            this.financialPaymentUsersLoadAttempted = true;
            await this.loadDidarUsers('payments');
        }
        this.populateFinancialPaymentFilterOptions();
    },

    populateFinancialPaymentFilterOptions() {
        const ownerSelect = document.getElementById('payments-filter-lead-owner');
        if (ownerSelect) {
            const current = ownerSelect.value || '';
            ownerSelect.innerHTML = '<option value="">همه مسئول‌ها</option>';
            (this.state.didarUsers || []).forEach(user => {
                const value = String(user.didar_user_id || user.owner_id || user.id || '').trim();
                if (!value) return;
                const label = user.display_name || `${user.first_name || ''} ${user.last_name || ''}`.trim() || user.email || value;
                const opt = document.createElement('option');
                opt.value = value;
                opt.textContent = label;
                ownerSelect.appendChild(opt);
            });
            ownerSelect.value = current;
        }

        const serviceSelect = document.getElementById('payments-filter-service');
        if (serviceSelect) {
            const current = serviceSelect.value || '';
            const serviceItems = typeof this.getAllowedServiceLookupItemsDetailed === 'function'
                ? this.getAllowedServiceLookupItemsDetailed(false)
                : [];
            const options = serviceItems.length
                ? serviceItems.map(item => ({
                    value: item.value ?? item.title ?? item.label ?? '',
                    label: item.title ?? item.label ?? item.value ?? ''
                }))
                : this.getLookupOptions('allowed_services');
            serviceSelect.innerHTML = '<option value="">همه خدمات</option>';
            options
                .filter(opt => String(opt.value || opt.label || '').trim() !== '')
                .forEach(opt => {
                    const el = document.createElement('option');
                    el.value = String(opt.value || opt.label || '').trim();
                    el.textContent = String(opt.label || opt.value || '').trim();
                    serviceSelect.appendChild(el);
                });
            serviceSelect.value = current;
        }
    },

    initFinancialPaymentDateFilters() {
        this.initJalaliRangeFilter('payments-filter-date-from', 'payments-filter-date-to');
    },

    normalizeFinancialPaymentDateFilters() {
        const fromEl = document.getElementById('payments-filter-date-from');
        const toEl = document.getElementById('payments-filter-date-to');
        const today = this.getTodayGregorianDateOnly();
        const normalize = (el, endOfDay = false) => {
            if (!el) return '';
            const normalized = this.normalizeJalaliInputValue(el.value || '');
            if (normalized && normalized !== el.value) el.value = normalized;
            let gregorian = normalized ? this.jalaliToGregorianDateOnly(normalized, endOfDay) : '';
            if (gregorian && gregorian > today) {
                gregorian = today;
                el.value = toPersianDate(today) || el.value;
            }
            return gregorian;
        };
        const fromG = normalize(fromEl, false);
        let toG = normalize(toEl, true);
        if (fromEl && toEl && fromG && toG && toG < fromG) {
            toG = fromG;
            toEl.value = toPersianDate(fromG) || toEl.value;
        }
        this.applyJalaliRangeLimits('payments-filter-date-from', 'payments-filter-date-to');
        return { date_from: fromG, date_to: toG };
    },

    clearFinancialPaymentFilters() {
        [
            'payments-filter-sepidar',
            'payments-filter-lead-owner',
            'payments-filter-service',
            'payments-filter-tafsili',
            'payments-filter-image',
            'payments-filter-search',
            'payments-filter-date-from',
            'payments-filter-date-to'
        ].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.value = '';
        });
        const clearButton = document.getElementById('payments-filter-search-clear');
        if (clearButton) {
            clearButton.classList.add('hidden');
            clearButton.classList.remove('inline-flex');
        }
        clearTimeout(this.financialPaymentsSearchTimer);
        this.applyFinancialPaymentFilters();
    },

    applyFinancialPaymentFilters() {
        this.state.financialPaymentsPage = 1;
        this.state.financialPaymentsAppliedFilters = null;
        this.loadFinancialPayments({ page: 1 });
    },

    getFinancialPaymentFilterPayload() {
        const dateFilters = this.normalizeFinancialPaymentDateFilters();
        return {
            sepidar_status: document.getElementById('payments-filter-sepidar')?.value || '',
            search: this.normalizeFinancialPaymentsSearch(document.getElementById('payments-filter-search')?.value || ''),
            date_from: dateFilters.date_from,
            date_to: dateFilters.date_to,
            lead_owner_id: document.getElementById('payments-filter-lead-owner')?.value || '',
            service: document.getElementById('payments-filter-service')?.value || '',
            tafsili_status: document.getElementById('payments-filter-tafsili')?.value || '',
            image_status: document.getElementById('payments-filter-image')?.value || '',
        };
    },

    normalizeFinancialPaymentsSearch(value) {
        const raw = String(value || '').trim();
        return (typeof toEnglishDigits === 'function' ? toEnglishDigits(raw) : raw).toLowerCase();
    },

    filterFinancialPaymentsRows(rows, query) {
        const needle = this.normalizeFinancialPaymentsSearch(query);
        if (!needle) return rows;
        return (rows || []).filter(row => [
            row.owner_name,
            row.deal_title,
            row.contact_name,
            row.payer_name,
            row.payment_owner_name_snapshot,
            row.commission_status,
            row.customer_code,
            row.mobile_phone,
        ].some(value => this.normalizeFinancialPaymentsSearch(value).includes(needle)));
    },

    async loadFinancialPayments(options = {}) {
        if (!this.canCurrentUserCapabilityClient('financial_payments.view')) return;
        const body = document.getElementById('financial-payments-tbody');
        await this.ensureFinancialPaymentFiltersReady();
        const pageSize = Number(this.state.financialPaymentsPageSize || 50);
        const requestedPage = Number(options.page || this.state.financialPaymentsPage || 1);
        const page = Math.max(1, requestedPage);
        const filters = this.getFinancialPaymentFilterPayload();
        this.state.financialPaymentsPage = page;
        this.state.financialPaymentsPageSize = pageSize;
        const requestId = (this.financialPaymentsRequestId || 0) + 1;
        this.financialPaymentsRequestId = requestId;
        if (body && !options.searchMode) {
            body.innerHTML = '<tr><td colspan="14" class="text-center text-gray-400 py-6">در حال بارگذاری...</td></tr>';
        }
        const pagination = document.getElementById('financial-payments-pagination');
        if (pagination && !options.searchMode) pagination.innerHTML = '';
        try {
            const res = await this.req('get_financial_payments', {
                ...filters,
                limit: pageSize,
                offset: (page - 1) * pageSize
            });
            if (requestId !== this.financialPaymentsRequestId) return;
            if (res.status !== 'success') {
                if (body) body.innerHTML = `<tr><td colspan="14" class="text-center text-red-400 py-6">${this.escapeHtml(res.message || 'خطا')}</td></tr>`;
                return;
            }
            this.state.financialPayments = res.payments || [];
            this.state.financialPaymentsTotal = Number(res.total || 0);
            this.state.financialPaymentsLimit = Number(res.limit || pageSize);
            this.state.financialPaymentsOffset = Number(res.offset || 0);
            this.state.financialPaymentsAppliedFilters = filters;
            this.renderFinancialPayments(null, {
                emptyMessage: filters.search ? 'موردی با این جستجو پیدا نشد' : 'پرداختی یافت نشد'
            });
            this.renderFinancialPaymentsPagination();
        } catch (e) {
            if (requestId !== this.financialPaymentsRequestId) return;
            if (body) body.innerHTML = '<tr><td colspan="14" class="text-center text-red-400 py-6">خطا در دریافت پرداخت‌ها</td></tr>';
            this.renderFinancialPaymentsPagination(true);
        }
    },

    renderFinancialPaymentsPagination(clear = false) {
        const el = document.getElementById('financial-payments-pagination');
        if (!el) return;
        if (clear) {
            el.innerHTML = '';
            return;
        }
        const total = Number(this.state.financialPaymentsTotal || 0);
        const limit = Math.max(1, Number(this.state.financialPaymentsLimit || this.state.financialPaymentsPageSize || 50));
        const offset = Math.max(0, Number(this.state.financialPaymentsOffset || 0));
        const page = Math.floor(offset / limit) + 1;
        const totalPages = Math.max(1, Math.ceil(total / limit));
        const shownFrom = total === 0 ? 0 : offset + 1;
        const shownTo = Math.min(offset + (this.state.financialPayments || []).length, total);
        const disabledPrev = page <= 1 ? 'disabled opacity-40 cursor-not-allowed' : '';
        const disabledNext = page >= totalPages ? 'disabled opacity-40 cursor-not-allowed' : '';
        el.innerHTML = `
            <div class="text-gray-400">
                نمایش <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(shownFrom))}</span>
                تا <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(shownTo))}</span>
                از <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(total))}</span> پرداخت
            </div>
            <div class="flex items-center gap-2">
                <button type="button" class="rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm font-bold text-gray-200 hover:bg-gray-700 ${disabledPrev}" ${page <= 1 ? 'disabled' : ''} onclick="app.goFinancialPaymentsPage(${page - 1})">قبلی</button>
                <span class="rounded-lg bg-gray-900 px-3 py-2 text-xs text-gray-300">صفحه ${this.escapeHtml(this.formatNumber(page))} از ${this.escapeHtml(this.formatNumber(totalPages))}</span>
                <button type="button" class="rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm font-bold text-gray-200 hover:bg-gray-700 ${disabledNext}" ${page >= totalPages ? 'disabled' : ''} onclick="app.goFinancialPaymentsPage(${page + 1})">بعدی</button>
            </div>`;
    },

    goFinancialPaymentsPage(page) {
        const total = Number(this.state.financialPaymentsTotal || 0);
        const limit = Math.max(1, Number(this.state.financialPaymentsLimit || this.state.financialPaymentsPageSize || 50));
        const totalPages = Math.max(1, Math.ceil(total / limit));
        const nextPage = Math.min(Math.max(1, Number(page || 1)), totalPages);
        this.loadFinancialPayments({ page: nextPage });
    },

    paymentSepidarBadge(row) {
        if (row?.sepidar_registered_at) {
            const registeredAt = this.financialPaymentDateTime(row.sepidar_registered_at);
            return `<div class="inline-flex flex-col items-center gap-2" title="تاریخ ثبت در سپیدار: ${this.escapeHtml(registeredAt)}" aria-label="ثبت شده در سپیدار، تاریخ ثبت ${this.escapeHtml(registeredAt)}">
                <span class="inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded bg-green-500/15 text-green-400 font-bold">
                    <i class="fa-solid fa-circle-check text-[11px]"></i>
                    ثبت شده
                </span>
                <span class="text-xs text-gray-200 whitespace-nowrap ltr">${this.escapeHtml(registeredAt)}</span>
            </div>`;
        }
        return '<span class="inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded bg-gray-800 text-gray-200"><i class="fa-regular fa-clock text-[11px]"></i>ثبت نشده</span>';
    },

    financialPaymentPayerCell(row) {
        const payer = String(row?.payer_name || row?.depositor_name || row?.account_owner_name || row?.destination_account_owner || row?.check_owner_name || '').trim();
        if (!payer) {
            return '<span class="text-gray-500">-</span>';
        }
        return `<div class="leading-6">
            <span class="text-gray-100 font-medium">${this.escapeHtml(payer)}</span>
        </div>`;
    },

    financialPaymentOwnerCell(row) {
        const owner = String(row?.payment_owner_name_snapshot || row?.registered_by_name || '').trim();
        if (!owner) {
            return '<span class="text-gray-500">-</span>';
        }
        return `<span class="text-gray-100 font-medium">${this.escapeHtml(owner)}</span>`;
    },

    financialPaymentCommissionBadge(row) {
        const rawStatus = String(row?.commission_status || '').trim().toUpperCase();
        const labels = {
            NOT_ELIGIBLE: 'غیرمشمول',
            PENDING: 'در انتظار محاسبه',
            CALCULATED: 'محاسبه شده',
            APPROVED: 'تایید شده',
            READY_FOR_PAYOUT: 'آماده پرداخت',
            PAID: 'پرداخت شده',
            REFUNDED: 'عودت شده',
            CLAWBACK_PENDING: 'در انتظار برگشت',
            CLAWED_BACK: 'برگشت خورده',
            VOID: 'باطل'
        };
        const styles = {
            NOT_ELIGIBLE: 'bg-gray-800 text-gray-300',
            PENDING: 'bg-amber-500/15 text-amber-300',
            CALCULATED: 'bg-blue-500/15 text-blue-300',
            APPROVED: 'bg-cyan-500/15 text-cyan-300',
            READY_FOR_PAYOUT: 'bg-indigo-500/15 text-indigo-300',
            PAID: 'bg-emerald-500/15 text-emerald-300',
            REFUNDED: 'bg-orange-500/15 text-orange-300',
            CLAWBACK_PENDING: 'bg-rose-500/15 text-rose-300',
            CLAWED_BACK: 'bg-red-500/15 text-red-300',
            VOID: 'bg-gray-900 text-gray-500'
        };
        const label = labels[rawStatus] || (rawStatus || 'نامشخص');
        const style = styles[rawStatus] || 'bg-gray-800 text-gray-300';
        const effectiveDate = String(row?.commission_effective_date || '').trim();
        const dateLine = effectiveDate ? `<span class="block text-[11px] text-gray-500 mt-1">${this.escapeHtml(effectiveDate)}</span>` : '';
        return `<div class="inline-flex flex-col items-center">
            <span class="inline-flex items-center rounded px-2.5 py-1 text-xs font-bold ${style}">${this.escapeHtml(label)}</span>
            ${dateLine}
        </div>`;
    },

    financialPaymentDateTime(value) {
        const raw = String(value || '').trim();
        if (!raw) return '-';
        const formatted = (typeof toPersianDateTime === 'function' ? (toPersianDateTime(raw) || raw) : raw).trim();
        const normalized = formatted.replace(/\s+-\s+/g, ' ');
        const parts = normalized.split(/\s+/).filter(Boolean);
        const datePart = parts.find(part => part.includes('/')) || '';
        const timePart = parts.find(part => part.includes(':')) || '';
        if (datePart && timePart) {
            return `${datePart} - ${timePart}`;
        }
        return formatted.replace(/\s+-\s+/g, ' - ');
    },

    financialPaymentDateTimeCell(value) {
        const raw = String(value || '').trim();
        if (!raw) return '<span class="text-gray-500">-</span>';
        const formatted = this.financialPaymentDateTime(raw);
        const normalized = formatted.replace(/\s+-\s+/g, ' ');
        const parts = normalized.split(/\s+/).filter(Boolean);
        const datePart = parts.find(part => part.includes('/')) || formatted;
        const timePart = parts.find(part => part.includes(':')) || '';
        return `<div class="inline-flex flex-col items-center leading-6">
            <span class="text-gray-100 font-bold ltr">${this.escapeHtml(datePart)}</span>
            ${timePart ? `<span class="text-[12px] text-gray-400 ltr">${this.escapeHtml(timePart)}</span>` : ''}
        </div>`;
    },

    financialPaymentMoney(value) {
        const numeric = Number(String(value ?? '').replace(/,/g, ''));
        if (!Number.isFinite(numeric) || numeric <= 0) return '-';
        const formatted = typeof formatMoneyString === 'function'
            ? formatMoneyString(String(Math.round(numeric)))
            : this.formatNumber(Math.round(numeric));
        return `${formatted} تومان`;
    },

    financialPaymentBankDetail(row) {
        const tafsiliCode = String(row?.destination_tafsili_code || '').trim();
        return tafsiliCode !== '' ? `<span class="ltr inline-block">${this.escapeHtml(tafsiliCode)}</span>` : '-';
    },

    async openFinancialPaymentDeal(dealId) {
        const safeDealId = String(dealId || '').trim();
        if (!safeDealId) return;
        await this.openDealModal(safeDealId);
        this.showDealFormTab('payments');
    },

    renderFinancialPayments(inputRows = null, options = {}) {
        const body = document.getElementById('financial-payments-tbody');
        if (!body) return;
        const rows = inputRows !== null ? inputRows : (this.state.financialPayments || []);
        if (!rows.length) {
            body.innerHTML = `<tr><td colspan="15" class="text-center text-gray-400 py-6">${this.escapeHtml(options.emptyMessage || 'پرداختی یافت نشد')}</td></tr>`;
            return;
        }
        const canSepidar = this.canCurrentUserCapabilityClient('financial_payments.sepidar');
        body.innerHTML = rows.map((r, idx) => {
            const receiptUrl = this.normalizePaymentUploadUrl ? this.normalizePaymentUploadUrl(r.receipt_image_path || '') : '#';
            const receipt = receiptUrl !== '#'
                ? `<a href="${receiptUrl}" target="_blank" rel="noopener" class="inline-flex items-center justify-center gap-1 text-xs px-2.5 py-1.5 rounded border border-blue-500/60 text-blue-300 bg-blue-500/5 hover:bg-blue-500/15 whitespace-nowrap"><i class="fa-solid fa-receipt"></i> مشاهده</a>`
                : '<span class="text-xs text-gray-600">ندارد</span>';
            const locked = !!r.sepidar_registered_at;
            const sepidarAction = canSepidar
                ? `<button type="button" class="inline-flex items-center justify-center whitespace-nowrap ${locked ? 'bg-green-500/15 text-green-400 font-bold cursor-not-allowed' : ''}" style="${locked ? '' : 'height:28px;padding:0 12px;border-radius:10px;background:#eab308;color:#000;border:0;outline:0;box-shadow:none;font-weight:900;font-size:12px;'}" ${locked ? 'disabled' : ''} onfocus="this.style.outline='0';this.style.boxShadow='none';" onclick="app.markPaymentSepidar('${this.escapeHtml(String(r.transaction_id))}', this)">
                    <span style="${locked ? '' : 'color:#000;'}">${locked ? 'انجام شده' : 'ثبت در سپیدار'}</span>
                  </button>`
                : this.paymentSepidarBadge(r);
            const stripe = idx % 2 === 0 ? 'bg-gray-900/20' : 'bg-gray-900/40';
            const dealId = String(r.didar_deal_id || r.deal_id || '').trim();
            const dealTitle = this.escapeHtml(r.deal_title || '-');
            const dealCell = dealId
                ? `<button type="button" class="group text-right w-full rounded-md px-2 py-1 -mx-2 hover:bg-yellow-500/10 focus:bg-yellow-500/10 focus:outline-none focus:ring-1 focus:ring-yellow-500/70 transition" onclick="app.openFinancialPaymentDeal('${this.escapeHtml(dealId)}')" title="باز کردن مودال معامله">
                    <span class="inline-flex items-center gap-2 text-white font-bold group-hover:text-yellow-300 group-focus:text-yellow-300">
                        <i class="fa-solid fa-arrow-up-right-from-square text-[11px] text-yellow-400 opacity-80"></i>
                        ${dealTitle}
                    </span>
                    <span class="block text-[11px] text-gray-500 group-hover:text-yellow-200/80 mt-0.5">مشاهده معامله</span>
                  </button>`
                : `<div class="text-white font-bold">${dealTitle}</div>`;
            return `<tr class="border-b border-gray-800/80 hover:bg-gray-800/60 align-middle ${stripe}">
                <td class="py-3 px-3 text-sm text-gray-100 border-r-2 border-r-yellow-500/50">${this.escapeHtml(r.owner_name || '-')}</td>
                <td class="py-3 px-3 text-sm leading-6 break-words">
                    ${dealCell}
                </td>
                <td class="py-3 px-3 text-sm text-gray-100 font-medium">${this.escapeHtml(r.contact_name || '-')}</td>
                <td class="py-3 px-3 text-sm">${this.financialPaymentPayerCell(r)}</td>
                <td class="py-3 px-3 text-sm">${this.financialPaymentOwnerCell(r)}</td>
                <td class="py-3 px-3 text-center"><span class="inline-flex items-center justify-center min-w-16 rounded bg-gray-800 px-2 py-1 text-xs ltr text-gray-200 font-mono">${this.escapeHtml(r.customer_code || '-')}</span></td>
                <td class="py-3 px-3 text-center"><span class="inline-flex items-center justify-center rounded bg-gray-800 px-2 py-1 text-xs ltr text-gray-200 font-mono">${this.escapeHtml(r.mobile_phone || '-')}</span></td>
                <td class="py-3 px-3 text-sm text-center whitespace-nowrap text-gray-300">${this.financialPaymentMoney(r.service_cost || r.payable_amount || 0)}</td>
                <td class="py-3 px-3 text-sm text-center whitespace-nowrap"><span class="inline-flex items-center rounded bg-emerald-500/10 px-3 py-1 font-bold text-emerald-200">${this.financialPaymentMoney(r.paid_amount || 0)}</span></td>
                <td class="py-3 px-3 text-sm text-center whitespace-nowrap text-gray-200">${this.financialPaymentDateTimeCell(r.payment_registered_at || r.payment_date)}</td>
                <td class="py-3 px-3 text-center">${this.financialPaymentCommissionBadge(r)}</td>
                <td class="py-3 px-3 text-sm text-center text-gray-200">${this.financialPaymentBankDetail(r)}</td>
                <td class="py-3 px-3 text-center">${receipt}</td>
                <td class="py-3 px-3 text-center">${this.paymentSepidarBadge(r)}</td>
                <td class="py-3 px-3 text-center">${sepidarAction}</td>
            </tr>`;
        }).join('');
    },

    async markPaymentSepidar(transactionId, control) {
        if (!this.canCurrentUserCapabilityClient('financial_payments.sepidar')) return;
        const confirmed = await Swal.fire({
            title: 'ثبت در سپیدار',
            text: 'بعد از ثبت، این گزینه قابل تغییر نیست.',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'ثبت شود',
            cancelButtonText: 'انصراف'
        });
        if (!confirmed.isConfirmed) {
            if (control && control.type === 'checkbox') control.checked = false;
            return;
        }
        if (control) control.disabled = true;
        const res = await this.req('mark_payment_sepidar', { transaction_id: transactionId });
        if (res.status === 'success') {
            this.toast ? this.toast('ثبت سپیدار انجام شد') : null;
            const row = (this.state.financialPayments || []).find(item => String(item.transaction_id) === String(transactionId));
            if (row) {
                row.sepidar_registered_at = res.review?.sepidar_registered_at || new Date().toISOString().slice(0, 19).replace('T', ' ');
                row.registered_by = res.review?.registered_by || row.registered_by || '';
                this.renderFinancialPayments();
            } else {
                this.loadFinancialPayments({ page: this.state.financialPaymentsPage || 1 });
            }
        } else {
            if (control) {
                control.disabled = false;
                if (control.type === 'checkbox') control.checked = false;
            }
            Swal.fire('خطا', res.message || 'خطا در ثبت سپیدار', 'error');
        }
    },

    async loadCheques(options = {}) {
        if (!this.canCurrentUserCapabilityClient('cheques.view')) return;
        const body = document.getElementById('cheques-tbody');
        if (body) body.innerHTML = '<tr><td colspan="10" class="text-center text-gray-400 py-6">در حال بارگذاری...</td></tr>';
        this.initChequeDateFilters();
        const dateFilters = this.getChequeDateFilterPayload();
        const pageSize = Number(this.state.chequesPageSize || 50);
        const requestedPage = Number(options.page || this.state.chequesPage || 1);
        const page = Math.max(1, requestedPage);
        this.state.chequesPage = page;
        this.state.chequesPageSize = pageSize;
        const filters = {
            check_status: document.getElementById('cheques-filter-status')?.value || '',
            collection_status: document.getElementById('cheques-filter-collection')?.value || '',
            search: document.getElementById('cheques-filter-search')?.value || '',
            date_from: dateFilters.date_from,
            date_to: dateFilters.date_to,
        };
        try {
            const res = await this.req('get_cheques', {
                ...filters,
                limit: pageSize,
                offset: (page - 1) * pageSize
            });
            if (res.status !== 'success') {
                if (body) body.innerHTML = `<tr><td colspan="10" class="text-center text-red-400 py-6">${this.escapeHtml(res.message || 'خطا')}</td></tr>`;
                return;
            }
            this.state.cheques = res.cheques || [];
            this.state.chequesTotal = Number(res.total || 0);
            this.state.chequesLimit = Number(res.limit || pageSize);
            this.state.chequesOffset = Number(res.offset || 0);
            this.renderCheques();
            this.renderChequesPagination();
        } catch (e) {
            if (body) body.innerHTML = '<tr><td colspan="10" class="text-center text-red-400 py-6">خطا در دریافت چک‌ها</td></tr>';
            this.renderChequesPagination(true);
        }
    },

    initChequeDateFilters() {
        this.initJalaliRangeFilter('cheques-filter-date-from', 'cheques-filter-date-to');
    },

    applyChequeFilters() {
        this.state.chequesPage = 1;
        this.loadCheques({ page: 1 });
    },

    clearChequeFilters() {
        [
            'cheques-filter-status',
            'cheques-filter-collection',
            'cheques-filter-search',
            'cheques-filter-date-from',
            'cheques-filter-date-to'
        ].forEach(id => {
            const el = document.getElementById(id);
            if (el) el.value = '';
        });
        clearTimeout(this.chequeDateFilterTimer);
        this.state.chequesPage = 1;
        this.loadCheques({ page: 1 });
    },

    renderChequesPagination(clear = false) {
        const el = document.getElementById('cheques-pagination');
        if (!el) return;
        if (clear) {
            el.innerHTML = '';
            return;
        }
        const total = Number(this.state.chequesTotal || 0);
        const limit = Math.max(1, Number(this.state.chequesLimit || this.state.chequesPageSize || 50));
        const offset = Math.max(0, Number(this.state.chequesOffset || 0));
        const page = Math.floor(offset / limit) + 1;
        const totalPages = Math.max(1, Math.ceil(total / limit));
        const shownFrom = total === 0 ? 0 : offset + 1;
        const shownTo = Math.min(offset + (this.state.cheques || []).length, total);
        const disabledPrev = page <= 1 ? 'disabled opacity-40 cursor-not-allowed' : '';
        const disabledNext = page >= totalPages ? 'disabled opacity-40 cursor-not-allowed' : '';
        el.innerHTML = `
            <div class="text-gray-400">
                نمایش <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(shownFrom))}</span>
                تا <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(shownTo))}</span>
                از <span class="font-bold text-gray-100">${this.escapeHtml(this.formatNumber(total))}</span> چک
            </div>
            <div class="flex items-center gap-2">
                <button type="button" class="rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm font-bold text-gray-200 hover:bg-gray-700 ${disabledPrev}" ${page <= 1 ? 'disabled' : ''} onclick="app.goChequesPage(${page - 1})">قبلی</button>
                <span class="rounded-lg bg-gray-900 px-3 py-2 text-xs text-gray-300">صفحه ${this.escapeHtml(this.formatNumber(page))} از ${this.escapeHtml(this.formatNumber(totalPages))}</span>
                <button type="button" class="rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm font-bold text-gray-200 hover:bg-gray-700 ${disabledNext}" ${page >= totalPages ? 'disabled' : ''} onclick="app.goChequesPage(${page + 1})">بعدی</button>
            </div>`;
    },

    goChequesPage(page) {
        const total = Number(this.state.chequesTotal || 0);
        const limit = Math.max(1, Number(this.state.chequesLimit || this.state.chequesPageSize || 50));
        const totalPages = Math.max(1, Math.ceil(total / limit));
        const nextPage = Math.min(Math.max(1, Number(page || 1)), totalPages);
        this.loadCheques({ page: nextPage });
    },

    getChequeDateFilterPayload() {
        const fromEl = document.getElementById('cheques-filter-date-from');
        const toEl = document.getElementById('cheques-filter-date-to');
        const normalize = (el, endOfDay = false) => {
            if (!el) return '';
            const normalized = this.normalizeJalaliInputValue(el.value || '');
            if (normalized && normalized !== el.value) el.value = normalized;
            return normalized ? this.jalaliToGregorianDateOnly(normalized, endOfDay) : '';
        };
        return {
            date_from: normalize(fromEl, false),
            date_to: normalize(toEl, true),
        };
    },

    chequeStatusBadge(status) {
        const map = { approved: ['تایید', 'bg-green-500/20 text-green-400'], rejected: ['رد', 'bg-red-500/20 text-red-400'], pending: ['در انتظار', 'bg-gray-500/20 text-gray-300'] };
        const [label, cls] = map[status] || map.pending;
        return `<span class="inline-flex items-center justify-center rounded px-2.5 py-1 text-xs font-bold whitespace-nowrap ${cls}">${label}</span>`;
    },
    chequeCollectionBadge(status) {
        const map = { collected: ['وصول', 'bg-green-500/20 text-green-400'], returned: ['برگشت', 'bg-red-500/20 text-red-400'], blataklif: ['بلاتکلیف', 'bg-yellow-500/20 text-yellow-400'], pending: ['در انتظار', 'bg-gray-500/20 text-gray-300'] };
        const [label, cls] = map[status] || map.pending;
        return `<span class="inline-flex items-center justify-center rounded px-2.5 py-1 text-xs font-bold whitespace-nowrap ${cls}">${label}</span>`;
    },

    formatChequeTableDate(value) {
        const raw = String(value || '').trim();
        if (!raw) return '';
        if (typeof toPersianDate === 'function') {
            return toPersianDate(raw) || raw.slice(0, 10);
        }
        return raw.slice(0, 10);
    },

    chequeDateCell(value) {
        const formatted = this.formatChequeTableDate(value);
        if (!formatted) return '<span class="text-gray-500">-</span>';
        return `<span class="ltr whitespace-nowrap text-gray-100 font-bold">${this.escapeHtml(formatted)}</span>`;
    },

    chequeRegisteredDateCell(row) {
        const raw = String(row?.payment_registered_date || row?.payment_registered_at || row?.created_at || row?.payment_date || '').trim();
        return this.chequeDateCell(raw);
    },

    renderCheques() {
        const body = document.getElementById('cheques-tbody');
        if (!body) return;
        const rows = this.state.cheques || [];
        if (!rows.length) {
            body.innerHTML = '<tr><td colspan="10" class="text-center text-gray-400 py-6">چکی یافت نشد</td></tr>';
            return;
        }
        const canReview = this.canCurrentUserCapabilityClient('cheques.review');
        const canCollect = this.canCurrentUserCapabilityClient('cheques.collect');
        body.innerHTML = rows.map((r, idx) => {
            const id = `${r.deal_id}|${r.check_number}`;
            const actions = [];
            if (canReview) actions.push(`<button onclick="app.setChequeStatus('${this.escapeHtml(String(r.transaction_id))}')" class="inline-flex items-center justify-center whitespace-nowrap rounded-lg border border-yellow-500/80 bg-yellow-500/5 px-3 py-1.5 text-xs font-bold text-yellow-400 hover:bg-yellow-500/15">وضعیت چک</button>`);
            if (canCollect) actions.push(`<button onclick="app.setChequeCollection('${this.escapeHtml(String(r.transaction_id))}')" class="inline-flex items-center justify-center whitespace-nowrap rounded-lg border border-blue-500/80 bg-blue-500/5 px-3 py-1.5 text-xs font-bold text-blue-400 hover:bg-blue-500/15">وضعیت وصول</button>`);
            const dealId = String(r.didar_deal_id || r.deal_id || '').trim();
            const dealTitle = this.escapeHtml(r.deal_title || '-');
            const dealCell = dealId
                ? `<button type="button" class="group text-right w-full rounded-md px-2 py-1 -mx-2 hover:bg-yellow-500/10 focus:bg-yellow-500/10 focus:outline-none focus:ring-1 focus:ring-yellow-500/70 transition" onclick="app.openFinancialPaymentDeal('${this.escapeHtml(dealId)}')" title="باز کردن مودال معامله">
                    <span class="inline-flex items-center gap-2 text-white font-bold group-hover:text-yellow-300 group-focus:text-yellow-300">
                        <i class="fa-solid fa-arrow-up-right-from-square text-[11px] text-yellow-400 opacity-80"></i>
                        ${dealTitle}
                    </span>
                    <span class="block text-[11px] text-gray-500 group-hover:text-yellow-200/80 mt-0.5">مشاهده معامله</span>
                  </button>`
                : `<div class="text-white font-bold">${dealTitle}</div>`;
            const customerCode = String(r.customer_code || '').trim();
            const checkNumber = String(r.check_number || '').trim();
            const bankName = String(r.check_bank_name || '').trim();
            const stripe = idx % 2 === 0 ? 'bg-gray-900/20' : 'bg-gray-900/40';
            return `<tr class="border-b border-gray-800/80 hover:bg-gray-800/60 align-middle ${stripe}" data-cheque="${this.escapeHtml(id)}">
                <td class="py-3 px-3 text-sm text-gray-100 border-r-2 border-r-yellow-500/50">
                    <span class="font-medium">${this.escapeHtml(r.contact_name || '-')}</span>
                    ${customerCode ? `<div class="mt-1"><span class="inline-flex items-center rounded bg-gray-800 px-2 py-0.5 text-[11px] ltr text-gray-400 font-mono">${this.escapeHtml(customerCode)}</span></div>` : ''}
                </td>
                <td class="py-3 px-3 text-sm leading-6 break-words">${dealCell}</td>
                <td class="py-3 px-3 text-center">${checkNumber ? `<span class="inline-flex items-center justify-center rounded bg-gray-800 px-2 py-1 text-xs ltr text-gray-200 font-mono">${this.escapeHtml(checkNumber)}</span>` : '<span class="text-gray-500">-</span>'}</td>
                <td class="py-3 px-3 text-center text-gray-200 font-medium">${bankName ? this.escapeHtml(bankName) : '<span class="text-gray-500">-</span>'}</td>
                <td class="py-3 px-3 text-center whitespace-nowrap"><span class="inline-flex items-center rounded bg-emerald-500/10 px-3 py-1 font-bold text-emerald-200">${this.financialPaymentMoney(r.amount || 0)}</span></td>
                <td class="py-3 px-3 text-center">${this.chequeRegisteredDateCell(r)}</td>
                <td class="py-3 px-3 text-center">${this.chequeDateCell(r.check_due_date || r.check_date)}</td>
                <td class="py-3 px-3 text-center">${this.chequeStatusBadge(r.check_status)}${r.check_status_note ? `<div class="text-[11px] text-gray-500 mt-1">${this.escapeHtml(r.check_status_note)}</div>` : ''}</td>
                <td class="py-3 px-3 text-center">${this.chequeCollectionBadge(r.collection_status)}${r.collection_date ? `<div class="text-[11px] text-gray-500 mt-1 ltr">${this.escapeHtml(this.formatChequeTableDate(r.collection_date))}</div>` : ''}</td>
                <td class="py-3 px-3 text-center"><div class="flex flex-wrap justify-center gap-2">${actions.join('') || '<span class="text-xs text-gray-600">-</span>'}</div></td>
            </tr>`;
        }).join('');
    },

    // Locate a cheque row in current state by its stable transaction id, for
    // modal context (contact/deal/amount) and prefilling current values.
    chequeRowByTxn(transactionId) {
        const id = String(transactionId);
        return (this.state.cheques || []).find(r => String(r.transaction_id) === id) || null;
    },

    // Shared context header markup for the cheque modals. Uses explicit inline
    // styles (not Tailwind classes / the popup theme) so text stays readable
    // regardless of the SweetAlert popup background.
    chequeModalContextHtml(row) {
        if (!row) return '';
        const money = this.financialPaymentMoney(row.amount || 0);
        const due = this.formatChequeTableDate(row.check_due_date || row.check_date) || '-';
        const item = (label, value) => `
            <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:3px 0;">
                <span style="color:#94a3b8;">${label}</span>
                <span style="color:#f1f5f9;font-weight:600;">${this.escapeHtml(String(value ?? '-'))}</span>
            </div>`;
        return `
            <div style="font-size:13px;background:#1e293b;border:1px solid #334155;border-radius:10px;padding:12px;margin-bottom:16px;text-align:right;">
                ${item('مشتری', row.contact_name || '-')}
                ${item('معامله', row.deal_title || '-')}
                ${item('بانک', row.check_bank_name || '-')}
                ${item('مبلغ', money)}
                ${item('سررسید', due)}
                ${row.check_number ? item('شماره چک', row.check_number) : ''}
            </div>`;
    },

    // Shared inline styles for the cheque modal form controls so labels/inputs
    // are legible on any popup theme.
    _chequeFieldStyles() {
        return {
            label: 'display:block;font-size:12px;color:#cbd5e1;margin-bottom:4px;text-align:right;',
            control: 'width:100%;background:#0f172a;color:#f1f5f9;border:1px solid #334155;border-radius:8px;padding:8px 10px;font-size:14px;margin-bottom:14px;text-align:right;'
        };
    },

    async setChequeStatus(transactionId) {
        if (!this.canCurrentUserCapabilityClient('cheques.review')) return;
        const row = this.chequeRowByTxn(transactionId);
        const cur = String(row?.check_status || 'pending');
        const opt = (v, label) => `<option value="${v}" ${cur === v ? 'selected' : ''}>${label}</option>`;
        const st = this._chequeFieldStyles();
        const { value: form } = await Swal.fire({
            title: 'تعیین وضعیت چک',
            html: `
                <div dir="rtl" style="text-align:right;">
                    ${this.chequeModalContextHtml(row)}
                    <label style="${st.label}">وضعیت چک</label>
                    <select id="swal-cheque-status" style="${st.control}">
                        ${opt('approved', 'تایید')}${opt('rejected', 'رد')}${opt('pending', 'در انتظار')}
                    </select>
                    <label style="${st.label}">توضیحات <span style="color:#64748b;">(برای رد الزامی)</span></label>
                    <textarea id="swal-cheque-note" rows="3" style="${st.control}resize:vertical;" placeholder="توضیحات...">${this.escapeHtml(String(row?.check_status_note || ''))}</textarea>
                </div>`,
            focusConfirm: false, showCancelButton: true, confirmButtonText: 'ثبت', cancelButtonText: 'انصراف',
            customClass: { popup: 'swal2-popup-dark' },
            preConfirm: () => {
                const status = document.getElementById('swal-cheque-status').value;
                const note = document.getElementById('swal-cheque-note').value.trim();
                if (status === 'rejected' && !note) { Swal.showValidationMessage('برای رد چک، درج توضیحات الزامی است'); return false; }
                return { status, note };
            }
        });
        if (!form) return;
        const res = await this.req('set_cheque_status', { transaction_id: transactionId, status: form.status, note: form.note });
        if (res.status === 'success') { this.toast ? this.toast('وضعیت چک ثبت شد') : null; this.loadCheques({ page: this.state.chequesPage || 1 }); } else { Swal.fire('خطا', res.message || 'خطا در ثبت وضعیت', 'error'); }
    },

    async setChequeCollection(transactionId) {
        if (!this.canCurrentUserCapabilityClient('cheques.collect')) return;
        const row = this.chequeRowByTxn(transactionId);
        const cur = String(row?.collection_status || 'pending');
        const opt = (v, label) => `<option value="${v}" ${cur === v ? 'selected' : ''}>${label}</option>`;
        // Prefill the picker with the existing collection date (Gregorian -> Jalali).
        const curPersianDate = row?.collection_date ? (typeof toPersianDate === 'function' ? toPersianDate(row.collection_date) : '') : '';
        const st = this._chequeFieldStyles();
        const { value: form } = await Swal.fire({
            title: 'وضعیت وصول چک',
            html: `
                <div dir="rtl" style="text-align:right;">
                    ${this.chequeModalContextHtml(row)}
                    <label style="${st.label}">وضعیت وصول</label>
                    <select id="swal-coll-status" style="${st.control}">
                        ${opt('collected', 'وصول')}${opt('returned', 'برگشت')}${opt('blataklif', 'بلاتکلیف')}${opt('pending', 'در انتظار')}
                    </select>
                    <label style="${st.label}">تاریخ وصول/برگشت</label>
                    <input id="swal-coll-date" type="text" readonly autocomplete="off" value="${this.escapeHtml(curPersianDate)}" style="${st.control}cursor:pointer;" placeholder="انتخاب تاریخ">
                </div>`,
            focusConfirm: false, showCancelButton: true, confirmButtonText: 'ثبت', cancelButtonText: 'انصراف',
            // Disable outside-click dismissal so clicking inside the (body-appended)
            // date picker calendar doesn't close the whole modal.
            allowOutsideClick: false,
            customClass: { popup: 'swal2-popup-dark' },
            didOpen: () => {
                const input = document.getElementById('swal-coll-date');
                if (input && typeof $ !== 'undefined' && $.fn && $.fn.persianDatepicker) {
                    try { $(input).persianDatepicker({ format: 'YYYY/MM/DD', initialValue: false, autoClose: true, observer: true }); } catch (e) {}
                }
            },
            preConfirm: () => {
                const status = document.getElementById('swal-coll-status').value;
                const persian = document.getElementById('swal-coll-date').value.trim();
                let gregorian = '';
                // persianToGregorian returns "YYYY-MM-DD HH:mm:ss"; keep only the date part.
                if (persian) { try { const g = persianToGregorian(persian); gregorian = g ? g.split(/[ T]/)[0] : ''; } catch (e) {} }
                if ((status === 'collected' || status === 'returned') && !gregorian) { Swal.showValidationMessage('برای وصول/برگشت، درج تاریخ معتبر الزامی است'); return false; }
                return { status, collection_date: gregorian };
            }
        });
        if (!form) return;
        const res = await this.req('set_cheque_collection', { transaction_id: transactionId, status: form.status, collection_date: form.collection_date });
        if (res.status === 'success') { this.toast ? this.toast('وضعیت وصول ثبت شد') : null; this.loadCheques({ page: this.state.chequesPage || 1 }); } else { Swal.fire('خطا', res.message || 'خطا در ثبت وضعیت وصول', 'error'); }
    },

	    deleteLeadFromLeadModal() {
	        const form = document.getElementById('lead-form');
	        const contactId = form?.dataset?.contactId || '';
	        return this.deleteLead(contactId);
	    },

	    auditLogs: { offset: 0, limit: 100, total: 0, rows: [], filterOptionsLoaded: false },

	    async loadAuditLogs(reset = false) {
	        if (!this.canCurrentUserCapabilityClient('audit_logs.view')) return;
	        if (reset) this.auditLogs.offset = 0;

	        const action = document.getElementById('audit-filter-action')?.value || '';
	        const actorUserId = document.getElementById('audit-filter-user')?.value || '';
	        const entityType = document.getElementById('audit-filter-entity')?.value || '';
            const personSearch = document.getElementById('audit-filter-person')?.value || '';
            const detailSearch = document.getElementById('audit-filter-detail')?.value || '';
            const ipSearch = document.getElementById('audit-filter-ip')?.value || '';
	        const dateFromJ = (document.getElementById('audit-filter-from')?.value || '').trim();
	        const dateToJ = (document.getElementById('audit-filter-to')?.value || '').trim();
	        const dateFromG = dateFromJ ? (persianToGregorian(this.toEnglishDigits(dateFromJ), '00:00') || '') : '';
	        const dateToG = dateToJ ? (persianToGregorian(this.toEnglishDigits(dateToJ), '23:59') || '') : '';
	        const dateFrom = dateFromG ? String(dateFromG).split(' ')[0] : '';
	        const dateTo = dateToG ? String(dateToG).split(' ')[0] : '';

	        try {
	            this.initJalaliRangeFilter('audit-filter-from', 'audit-filter-to');
	            const res = await this.req('get_audit_logs', {
	                action,
	                actor_user_id: actorUserId,
	                entity_type: entityType,
                    person_search: personSearch,
                    detail_search: detailSearch,
                    ip_search: ipSearch,
	                date_from: dateFrom,
	                date_to: dateTo,
	                limit: this.auditLogs.limit,
	                offset: this.auditLogs.offset
	            });
	            if (res.status !== 'success') {
	                Swal.fire('خطا', res.message || 'خطا در دریافت لاگ‌ها', 'error');
	                return;
	            }
	            this.auditLogs.total = res.logs?.total || 0;
	            this.auditLogs.rows = res.logs?.rows || [];
	            this.renderAuditLogFilterOptions(res.filter_options || {});
	            this.renderAuditLogs();
	        } catch (e) {
	            Swal.fire('خطا', 'خطا در دریافت لاگ‌ها', 'error');
	        }
	    },

        renderAuditLogFilterOptions(options = {}) {
            if (this.auditLogs.filterOptionsLoaded) return;

            const actionSelect = document.getElementById('audit-filter-action');
            if (actionSelect && Array.isArray(options.actions)) {
                const existing = new Set(Array.from(actionSelect.options).map(o => o.value));
                const separator = document.createElement('option');
                separator.disabled = true;
                separator.textContent = '--- عملیات‌های ثبت‌شده ---';
                actionSelect.appendChild(separator);
                options.actions.forEach(row => {
                    const value = String(row.action || '').trim();
                    if (!value || existing.has(value)) return;
                    const opt = document.createElement('option');
                    opt.value = value;
                    opt.textContent = `${this.auditActionLabel(value)} (${this.formatNumber(row.count_rows || 0)})`;
                    actionSelect.appendChild(opt);
                    existing.add(value);
                });
            }

            const entitySelect = document.getElementById('audit-filter-entity');
            if (entitySelect && Array.isArray(options.entities)) {
                const existing = new Set(Array.from(entitySelect.options).map(o => o.value));
                options.entities.forEach(row => {
                    const value = String(row.entity_type || '').trim();
                    if (!value || existing.has(value)) return;
                    const opt = document.createElement('option');
                    opt.value = value;
                    opt.textContent = `${this.auditEntityLabel(value)} (${this.formatNumber(row.count_rows || 0)})`;
                    entitySelect.appendChild(opt);
                    existing.add(value);
                });
            }

            const userSelect = document.getElementById('audit-filter-user');
            if (userSelect && Array.isArray(options.users)) {
                const current = userSelect.value;
                userSelect.innerHTML = '<option value="">همه کاربران</option>' + options.users.map(u => {
                    const name = (u.name || '').trim() || (u.didar_user_id ? `CRM خارجی: ${u.didar_user_id}` : `کاربر #${u.id}`);
                    return `<option value="${this.escapeHtml(u.id)}">${this.escapeHtml(name)}</option>`;
                }).join('');
                userSelect.value = current;
            }

            this.auditLogs.filterOptionsLoaded = true;
        },

        clearAuditLogFilters() {
            ['audit-filter-action', 'audit-filter-user', 'audit-filter-entity'].forEach(id => {
                const el = document.getElementById(id);
                if (el) el.value = '';
            });
            ['audit-filter-person', 'audit-filter-detail', 'audit-filter-ip', 'audit-filter-from', 'audit-filter-to'].forEach(id => {
                const el = document.getElementById(id);
                if (el) el.value = '';
            });
            this.loadAuditLogs(true);
        },

	    async loadPhoneLogs() {
	        if (!this.isAdmin()) return;
	        
	        // Load phone logs view
	        const viewEl = document.getElementById('view-phone-logs');
	        if (!viewEl) {
	            const content = document.getElementById('content');
	            const section = document.createElement('div');
	            section.id = 'view-phone-logs';
	            section.className = 'view-section hidden flex-1 flex flex-col overflow-hidden';
	            
	            // Load HTML using fetch
	            try {
	                const response = await fetch('?view=phone-logs');
	                const html = await response.text();
	                section.innerHTML = html;
	                content.appendChild(section);
	            } catch (e) {
	                console.error('Failed to load phone logs view:', e);
	                Swal.fire('خطا', 'خطا در بارگذاری صفحه', 'error');
	            }
	        }
	    },

	    auditLogsPrev() {
	        if (this.auditLogs.offset <= 0) return;
	        this.auditLogs.offset = Math.max(0, this.auditLogs.offset - this.auditLogs.limit);
	        this.loadAuditLogs(false);
	    },

	    auditLogsNext() {
	        if (this.auditLogs.offset + this.auditLogs.limit >= this.auditLogs.total) return;
	        this.auditLogs.offset += this.auditLogs.limit;
	        this.loadAuditLogs(false);
	    },

        async recoverDeletedRecord(auditLogId) {
            const id = Number(auditLogId || 0);
            if (!id) return;

            const confirmRes = await Swal.fire({
                title: 'بازیابی رکورد حذف‌شده؟',
                text: 'این عملیات رکورد انتخابی را به دیتابیس برمی‌گرداند.',
                icon: 'warning',
                showCancelButton: true,
                confirmButtonText: 'بله، بازیابی شود',
                cancelButtonText: 'انصراف'
            });
            if (!confirmRes.isConfirmed) return;

            try {
                const res = await this.req('recover_deleted_record', { audit_log_id: id });
                if (res.status !== 'success') {
                    Swal.fire('خطا', res.message || 'خطا در بازیابی', 'error');
                    return;
                }
                Swal.fire('انجام شد', res.message || 'رکورد بازیابی شد', 'success');
                this.loadAuditLogs(false);
            } catch (error) {
                Swal.fire('خطا', 'خطا در بازیابی رکورد', 'error');
            }
        },

        auditActionLabel(action) {
            const map = {
                login: 'ورود به سیستم',
                logout: 'خروج از سیستم',
                create: 'ثبت رکورد',
                save: 'ذخیره',
                update: 'ویرایش',
                edit: 'ویرایش',
                delete: 'حذف',
                recover: 'بازیابی',
                owner_change: 'تغییر مسئول',
                'client:delete': 'درخواست حذف از رابط کاربری',
                'client:recover': 'درخواست بازیابی از رابط کاربری',
                'client:owner_change': 'تغییر مسئول در رابط کاربری',
                Click: 'کلیک در رابط کاربری',
                'Client Event': 'رویداد رابط کاربری',
                AppTelemetry: 'تله‌متری سیستم',
                app_telemetry: 'تله‌متری سیستم'
            };
            const raw = String(action || '').trim();
            if (map[raw]) return map[raw];
            if (raw.indexOf('API: ') === 0) return `درخواست API: ${raw.replace('API: ', '')}`;
            if (raw.indexOf('HTTP ') === 0) return `درخواست ${raw.replace('HTTP ', '')}`;
            if (raw.indexOf('client:') === 0) return `رابط کاربری: ${raw.replace('client:', '')}`;
            return raw;
        },

        auditEntityLabel(entityType) {
            const map = {
                person: 'لید',
                deal: 'معامله',
                activity: 'فعالیت',
                payment: 'پرداخت',
                system: 'سیستم',
                client_ui: 'رابط کاربری',
                ui_element: 'عنصر رابط کاربری',
                client_person: 'لید در رابط کاربری',
                client_deal: 'معامله در رابط کاربری',
                client_activity: 'فعالیت در رابط کاربری',
                client_payment: 'پرداخت در رابط کاربری',
                AppTelemetry: 'تله‌متری برنامه',
                app_telemetry: 'تله‌متری برنامه'
            };
            const raw = String(entityType || '').trim();
            return map[raw] || raw;
        },

        auditEventLabel(value) {
            const map = {
                request_start: 'شروع درخواست',
                request_success: 'پاسخ موفق درخواست',
                request_error: 'خطای درخواست',
                request_failed: 'شکست درخواست',
                click: 'کلیک',
                page_view: 'مشاهده صفحه'
            };
            return map[String(value || '').trim()] || value;
        },

        auditRequestActionLabel(value) {
            const map = {
                get_notifications: 'دریافت اعلان‌ها',
                get_notification_count: 'دریافت تعداد اعلان‌ها',
                check_reminders: 'بررسی یادآورها',
                get_audit_logs: 'دریافت لاگ‌ها',
                save_activity: 'ثبت فعالیت',
                update_activity: 'ویرایش فعالیت',
                delete_activity: 'حذف فعالیت',
                save_deal: 'ذخیره معامله',
                delete_deal: 'حذف معامله',
                set_deal_status: 'تغییر وضعیت معامله',
                set_deal_pipeline_stage: 'تغییر مرحله معامله',
                save_lead: 'ذخیره لید',
                delete_lead: 'حذف لید'
            };
            const raw = String(value || '').trim();
            return map[raw] || raw;
        },

        auditStatusSentence(status) {
            const raw = String(status || '').trim().toLowerCase();
            if (raw === 'success' || raw === 'ok') return 'با موفقیت انجام شد';
            if (raw === 'error' || raw === 'failed' || raw === 'fail') return 'با خطا مواجه شد';
            if (raw === 'pending') return 'در انتظار انجام است';
            return status ? `با وضعیت «${status}» ثبت شد` : 'ثبت شد';
        },

        auditReadablePage(path) {
            const raw = String(path || '').trim();
            if (!raw || raw === 'dashboard' || raw === '/' || raw === '?') return 'داشبورد';
            const clean = raw.replace(/^\?+/, '');
            const params = new URLSearchParams(clean);
            const view = params.get('view') || params.get('page') || '';
            const api = params.get('api') || '';
            if (view) return this.auditEntityLabel(view.replace(/^view-/, '').replace(/-/g, '_')) || view;
            if (api) return `درخواست ${this.auditRequestActionLabel(api)}`;
            return raw;
        },

        auditInputHumanSummary(input = {}) {
            if (!input || typeof input !== 'object') return '';
            const ignored = new Set(['password', 'new_password', 'confirm_password', 'current_password', 'api_key', 'details']);
            const labelMap = {
                id: 'شناسه',
                entity_id: 'شناسه رکورد',
                deal_id: 'شناسه معامله',
                contact_id: 'شناسه لید',
                person_id: 'شناسه شخص',
                title: 'عنوان',
                name: 'نام',
                first_name: 'نام',
                last_name: 'نام خانوادگی',
                mobile_phone: 'موبایل',
                amount: 'مبلغ',
                payment_amount: 'مبلغ پرداخت',
                status: 'وضعیت',
                pipeline_stage: 'مرحله کاریز',
                owner_didar_id: 'مسئول',
                user_id: 'کاربر',
                action: 'عملیات'
            };
            const parts = [];
            Object.keys(input).forEach(key => {
                if (ignored.has(key) || parts.length >= 4) return;
                const value = input[key];
                if (value === null || value === undefined || value === '' || typeof value === 'object') return;
                const label = labelMap[key] || key.replace(/_/g, ' ');
                const displayValue = (key.includes('amount') || key === 'amount')
                    ? `${this.formatNumber(Number(value || 0))} تومان`
                    : String(value).slice(0, 80);
                parts.push(`${label}: ${displayValue}`);
            });
            return parts.join('، ');
        },

        auditApiSemanticSentence(actionName, input = {}) {
            const action = String(actionName || '').trim();
            const map = {
                login: 'کاربر وارد سیستم شد.',
                logout: 'کاربر از سیستم خارج شد.',
                get_notifications: 'اعلان‌های کاربر بررسی شد.',
                get_notification_count: 'تعداد اعلان‌های خوانده‌نشده بررسی شد.',
                check_reminders: 'یادآورهای کاربر بررسی شد.',
                get_audit_logs: 'فهرست لاگ فعالیت کاربران مشاهده شد.',
                get_users_list: 'فهرست کاربران مشاهده شد.',
                get_didar_users: 'فهرست کاربران CRM خارجی دریافت شد.',
                get_leads: 'فهرست لیدها مشاهده شد.',
                save_lead: 'اطلاعات لید ثبت یا ویرایش شد.',
                delete_lead: 'یک لید حذف شد.',
                get_deals: 'فهرست معاملات مشاهده شد.',
                save_deal: 'اطلاعات معامله ثبت یا ویرایش شد.',
                delete_deal: 'یک معامله حذف شد.',
                set_deal_status: 'وضعیت معامله تغییر کرد.',
                set_deal_pipeline_stage: 'مرحله کاریز معامله تغییر کرد.',
                save_activity: 'یک فعالیت برای لید یا معامله ثبت شد.',
                update_activity: 'اطلاعات فعالیت ویرایش شد.',
                delete_activity: 'یک فعالیت حذف شد.',
                upload_payment_file: 'فایل یا رسید پرداخت بارگذاری شد.',
                get_financial_payments: 'فهرست پرداخت‌های مالی مشاهده شد.',
                override_payment_owner: 'کارشناس ثبت‌کننده پرداخت تغییر داده شد.',
                get_dashboard_stats: 'آمار داشبورد مشاهده شد.',
                get_performance_dashboard_section: 'بخش عملکرد داشبورد مشاهده شد.',
                get_kpi_drilldown: 'جزئیات KPI مشاهده شد.',
                get_kpi_section: 'بخش KPI مشاهده شد.',
                get_lookup_groups: 'گروه‌های لیست‌های انتخابی مشاهده شد.',
                get_lookup_items: 'گزینه‌های لیست انتخابی مشاهده شد.',
                rbac_current_permissions: 'دسترسی‌های فعلی کاربر بررسی شد.',
                rbac_catalog: 'کاتالوگ دسترسی‌ها مشاهده شد.',
                rbac_role_permissions: 'دسترسی‌های نقش مشاهده شد.',
                rbac_save_role: 'نقش کاربری ذخیره شد.',
                rbac_save_user_capabilities: 'دسترسی‌های اختصاصی کاربر ذخیره شد.'
            };
            const base = map[action] || `عملیات «${this.auditRequestActionLabel(action)}» انجام شد.`;
            const summary = this.auditInputHumanSummary(input);
            return summary ? `${base} اطلاعات مهم: ${summary}.` : base;
        },

        buildAuditLogDetail(row, snapshot) {
            const action = String(row.action || '');
            const entityType = String(row.entity_type || '');
            const parts = [];

            if (action === 'delete' && entityType === 'person') {
                const p = snapshot?.person || {};
                const name = `${p.first_name || ''} ${p.last_name || ''}`.trim();
                parts.push(`لید «${name || p.mobile_phone || p.didar_contact_id || 'بدون نام'}» حذف شد.`);
                if (p.mobile_phone) parts.push(`موبایل: ${p.mobile_phone}.`);
                if (p.customer_code) parts.push(`کد مشتری: ${p.customer_code}.`);
            } else if (action === 'delete' && entityType === 'deal') {
                const d = snapshot?.deal || {};
                parts.push(`معامله «${d.title || d.didar_deal_id || 'بدون عنوان'}» حذف شد.`);
                if (d.status) parts.push(`وضعیت معامله هنگام حذف: ${d.status}.`);
                if (d.price || d.payable_amount) parts.push(`مبلغ معامله: ${this.formatNumber(Number(d.payable_amount || d.price || 0))} تومان.`);
            } else if (action === 'delete' && entityType === 'activity') {
                const a = snapshot?.activity || {};
                parts.push(`فعالیت «${a.activity_type_title || a.title || a.didar_activity_id || 'بدون عنوان'}» حذف شد.`);
                if (a.result_note) parts.push(`نتیجه ثبت‌شده برای فعالیت: ${String(a.result_note).slice(0, 120)}.`);
            } else if (action === 'delete' && entityType === 'payment') {
                const p = snapshot?.payment || snapshot?.transaction || {};
                const amount = p.amount ? `${this.formatNumber(Number(p.amount || 0))} تومان` : 'ثبت‌شده';
                parts.push(`پرداخت ${amount} حذف شد.`);
                if (p.method) parts.push(`روش پرداخت: ${p.method}.`);
                if (p.id) parts.push(`شناسه داخلی پرداخت: ${p.id}.`);
            } else if (action === 'owner_change') {
                const personName = snapshot?.person_name || '';
                const oldName = snapshot?.old_owner_name || snapshot?.old_owner_id || '-';
                const newName = snapshot?.new_owner_name || snapshot?.new_owner_id || '-';
                parts.push(`${personName ? `مسئول لید «${personName}»` : 'مسئول لید'} از «${oldName}» به «${newName}» تغییر کرد.`);
            } else if (action === 'Click' || entityType === 'ui_element' || entityType === 'client_ui') {
                const d = snapshot?.details || snapshot || {};
                const target = d.text || d.id || d.element || 'یک دکمه یا لینک';
                parts.push(`کاربر روی «${target}» کلیک کرد.`);
                if (d.path) parts.push(`محل انجام کار: ${this.auditReadablePage(d.path)}.`);
            } else if (action === 'AppTelemetry' || action === 'app_telemetry' || entityType === 'AppTelemetry' || entityType === 'app_telemetry') {
                const d = snapshot || {};
                const actionLabel = this.auditRequestActionLabel(d.action || '');
                const event = String(d.event || '').trim();
                if (event === 'request_start') {
                    parts.push(`سیستم شروع به انجام عملیات «${actionLabel || 'نامشخص'}» کرد.`);
                } else if (event === 'request_success') {
                    parts.push(`عملیات «${actionLabel || 'نامشخص'}» با موفقیت انجام شد.`);
                } else if (event === 'request_error' || event === 'request_failed') {
                    parts.push(`عملیات «${actionLabel || 'نامشخص'}» با خطا مواجه شد.`);
                } else {
                    parts.push(`رویداد سیستمی «${this.auditEventLabel(event) || actionLabel || 'نامشخص'}» ثبت شد.`);
                }
                if (d.status && !['success', 'ok'].includes(String(d.status).toLowerCase())) {
                    parts.push(`وضعیت ثبت‌شده: ${this.auditStatusSentence(d.status)}.`);
                }
                if (d.reqId) parts.push(`کد پیگیری داخلی: ${d.reqId}.`);
            } else if (action.indexOf('API: ') === 0 || action.indexOf('HTTP ') === 0) {
                const d = snapshot || {};
                const apiAction = action.indexOf('API: ') === 0
                    ? action.replace('API: ', '').trim()
                    : String(d.input?.action || d.uri || action).replace(/[?&]?api=/, '').trim();
                parts.push(this.auditApiSemanticSentence(apiAction, d.input || {}));
                if (d.uri && !apiAction.includes(d.uri)) parts.push(`مسیر داخلی: ${d.uri}.`);
            }

            if (!parts.length && snapshot && typeof snapshot === 'object') {
                ['title', 'name', 'person_name', 'deal_title', 'activity_type_title', 'status', 'message'].forEach(key => {
                    if (snapshot[key] !== undefined && snapshot[key] !== null && snapshot[key] !== '') {
                        parts.push(`این رویداد درباره «${snapshot[key]}» ثبت شد.`);
                    }
                });
                if (!parts.length) {
                    const summary = this.auditInputHumanSummary(snapshot);
                    parts.push(summary ? `یک تغییر یا رویداد ثبت شد. اطلاعات مهم: ${summary}.` : 'یک رویداد در سیستم ثبت شد.');
                }
            }

            if (!parts.length) {
                parts.push('یک رویداد در سیستم ثبت شد.');
            }

            return parts.join(' ');
        },

	    renderAuditLogs() {
	        const tbody = document.getElementById('audit-logs-table');
	        const info = document.getElementById('audit-logs-info');
	        if (!tbody) return;

	        const rows = Array.isArray(this.auditLogs.rows) ? this.auditLogs.rows : [];
	        if (!rows.length) {
	            tbody.innerHTML = '<tr><td class="p-3 text-gray-500" colspan="8">لاگی یافت نشد</td></tr>';
	        } else {
	            tbody.innerHTML = rows.map(r => {
	                let snapshot = null;
	                try { snapshot = r.snapshot_json ? JSON.parse(r.snapshot_json) : null; } catch (e) {}

	                const actor = r.actor_name_resolved || r.actor_name || (r.actor_didar_id || '') || (r.actor_user_id ? `کاربر #${r.actor_user_id}` : '');
	                const actorIp = r.actor_ip || '';
	                const action = this.auditActionLabel(r.action || '');
	                const entity = this.auditEntityLabel(r.entity_type || '');
                    
                    let relatedPerson = r.related_person_name || '';
                    if (r.related_person_mobile) relatedPerson += ` (${r.related_person_mobile})`;
                    if (!relatedPerson && r.related_person_didar_id) relatedPerson = r.related_person_didar_id;

	                const detail = this.buildAuditLogDetail(r, snapshot);

                    const canRecover = r.action === 'delete' && ['person', 'deal', 'activity', 'payment'].includes((r.entity_type || '').toLowerCase());
                    const actionButton = canRecover
                        ? `<button class="px-2 py-1 rounded bg-emerald-700 hover:bg-emerald-600 text-xs" onclick="app.recoverDeletedRecord(${Number(r.id || 0)})">بازیابی</button>`
                        : '<span class="text-gray-500">-</span>';

	                return `
	                    <tr>
	                        <td class="p-3 whitespace-nowrap">${this.escapeHtml(r.created_at || '')}</td>
	                        <td class="p-3 whitespace-nowrap">${this.escapeHtml(actor || '')}</td>
                            <td class="p-3 whitespace-nowrap">${this.escapeHtml(relatedPerson)}</td>
	                        <td class="p-3 whitespace-nowrap">${this.escapeHtml(actorIp || '')}</td>
	                        <td class="p-3 whitespace-nowrap">${this.escapeHtml(action)}</td>
	                        <td class="p-3 whitespace-nowrap">${this.escapeHtml(entity)}</td>
	                        <td class="p-3">${this.escapeHtml(detail || '')}</td>
                            <td class="p-3 whitespace-nowrap">${actionButton}</td>
	                    </tr>
	                `;
	            }).join('');
	        }

	        const from = this.auditLogs.total ? (this.auditLogs.offset + 1) : 0;
	        const to = Math.min(this.auditLogs.offset + this.auditLogs.limit, this.auditLogs.total);
	        if (info) info.textContent = `نمایش ${from} تا ${to} از ${this.auditLogs.total}`;
	    },

	    async loadExportCatalog(force = false) {
	        if (!this.canCurrentUserCapabilityClient('menu.exports') && !this.canCurrentUserCapabilityClient('export.catalog')) return;

	        if (this.state.exportCatalog && !force) {
	            this.renderExportUi();
	            return;
	        }

	        const datasetSelect = document.getElementById('export-dataset');
	        if (datasetSelect) datasetSelect.innerHTML = '<option value="">در حال بارگذاری...</option>';

	        try {
	            const res = await this.req('export_catalog', {});
	            if (res.status !== 'success') {
	                Swal.fire('خطا', res.message || 'خطا در دریافت لیست خروجی‌ها', 'error');
	                return;
	            }
	            this.state.exportCatalog = res;
	            this.renderExportUi();
	        } catch (e) {
	            Swal.fire('خطا', 'خطا در دریافت لیست خروجی‌ها', 'error');
	        }
	    },

	    renderExportUi() {
	        const datasetSelect = document.getElementById('export-dataset');
	        const catalog = this.state.exportCatalog;
	        if (!datasetSelect || !catalog || !Array.isArray(catalog.datasets)) return;

	        datasetSelect.innerHTML = '<option value="">-- انتخاب کنید --</option>' + catalog.datasets.map(d => {
	            return `<option value="${this.escapeHtml(d.id)}">${this.escapeHtml(d.title)}</option>`;
	        }).join('');

	        if (!this.state.exportCurrentDatasetId && catalog.datasets.length) {
	            this.state.exportCurrentDatasetId = catalog.datasets[0].id;
	        }
	        if (this.state.exportCurrentDatasetId) {
	            datasetSelect.value = this.state.exportCurrentDatasetId;
	        }

	        this.onExportDatasetChange();
	    },

	    onExportDatasetChange() {
	        const datasetSelect = document.getElementById('export-dataset');
	        const formatSelect = document.getElementById('export-format');
	        const catalog = this.state.exportCatalog;
	        if (!datasetSelect || !catalog || !Array.isArray(catalog.datasets)) return;

	        const datasetId = datasetSelect.value;
	        this.state.exportCurrentDatasetId = datasetId || null;
	        const dataset = catalog.datasets.find(d => d.id === datasetId);

	        if (formatSelect) {
	            const allowed = dataset?.formats || ['csv', 'xlsx'];
	            formatSelect.innerHTML = allowed.map(f => `<option value="${f}">${f.toUpperCase()}</option>`).join('');
	            if (!allowed.includes(formatSelect.value)) {
	                formatSelect.value = allowed[0] || 'csv';
	            }
	        }

	        this.renderExportFilters(dataset);
	        this.renderExportColumns(dataset);
	    },

	    renderExportFilters(dataset) {
	        const container = document.getElementById('export-filters');
	        if (!container) return;
	        if (!dataset) {
	            container.innerHTML = '';
	            return;
	        }

	        const filters = Array.isArray(dataset.filters) ? dataset.filters : [];
	        container.innerHTML = filters.map(f => {
	            const key = this.escapeHtml(f.key || '');
	            const label = this.escapeHtml(f.label || key);
	            const type = f.type || 'text';
	            const placeholder = this.escapeHtml(f.placeholder || '');

	            if (type === 'checkbox') {
	                return `
	                    <label class="flex items-center gap-2 text-sm text-gray-300 bg-gray-900/40 border border-gray-700 rounded-lg px-3 py-2">
	                        <input type="checkbox" class="accent-yellow-500" data-export-filter="${key}" value="1" onchange="app.onExportFilterChange()">
	                        <span>${label}</span>
	                    </label>
	                `;
	            }

	            if (type === 'select') {
	                const opts = Array.isArray(f.options) ? f.options : [];
	                const optionsHtml = opts.map(o => `<option value="${this.escapeHtml(o.value ?? '')}">${this.escapeHtml(o.label ?? '')}</option>`).join('');
	                return `
	                    <div>
	                        <label class="block text-xs text-gray-400 mb-1">${label}</label>
	                        <select class="input bg-gray-800 w-full" data-export-filter="${key}">${optionsHtml}</select>
	                    </div>
	                `;
	            }

	            if (type === 'date_jalali') {
	                return `
	                    <div>
	                        <label class="block text-xs text-gray-400 mb-1">${label}</label>
	                        <input type="text" class="input bg-gray-800 w-full export-date-jalali" data-export-filter="${key}" data-jalali-picker="1" placeholder="${placeholder}">
	                    </div>
	                `;
	            }

	            return `
	                <div>
	                    <label class="block text-xs text-gray-400 mb-1">${label}</label>
	                    <input type="text" class="input bg-gray-800 w-full" data-export-filter="${key}" placeholder="${placeholder}">
	                </div>
	            `;
	        }).join('');

	        this.initJalaliDatePickers(container.querySelectorAll('input[data-jalali-picker="1"]'));
	    },

	    onExportFilterChange() {
	        // Used to re-render columns when include_raw_json toggles
	        const datasetSelect = document.getElementById('export-dataset');
	        const catalog = this.state.exportCatalog;
	        if (!datasetSelect || !catalog || !Array.isArray(catalog.datasets)) return;
	        const dataset = catalog.datasets.find(d => d.id === datasetSelect.value);
	        this.renderExportColumns(dataset);
	    },

	    renderExportColumns(dataset) {
	        const container = document.getElementById('export-columns');
	        if (!container) return;
	        if (!dataset) {
	            container.innerHTML = '';
	            return;
	        }

	        const includeRaw = !!document.querySelector('[data-export-filter="include_raw_json"]')?.checked;
	        const cols = Array.isArray(dataset.columns) ? dataset.columns : [];

	        container.innerHTML = cols.map(c => {
	            const key = c.key || '';
	            const label = c.label || key;
	            const disabled = (key === 'raw_json' && !includeRaw);
	            const checked = !disabled && (key !== 'raw_json'); // default: raw_json unchecked unless enabled by user
	            return `
	                <label class="flex items-center gap-2 text-sm text-gray-300 bg-gray-900/40 border border-gray-700 rounded-lg px-3 py-2 export-col-item" data-export-col-item="1">
	                    <input type="checkbox" class="accent-yellow-500" data-export-column="${this.escapeHtml(key)}" ${checked ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
	                    <span class="${disabled ? 'text-gray-600' : ''}">${this.escapeHtml(label)}</span>
	                </label>
	            `;
	        }).join('');

	        this.filterExportColumns();
	    },

	    filterExportColumns() {
	        const q = (document.getElementById('export-columns-search')?.value || '').trim().toLowerCase();
	        document.querySelectorAll('#export-columns .export-col-item').forEach(el => {
	            const text = (el.textContent || '').toLowerCase();
	            if (!q || text.includes(q)) {
	                el.classList.remove('hidden');
	            } else {
	                el.classList.add('hidden');
	            }
	        });
	    },

	    selectAllExportColumns(on) {
	        document.querySelectorAll('#export-columns input[type="checkbox"][data-export-column]').forEach(cb => {
	            if (cb.disabled) return;
	            cb.checked = !!on;
	        });
	    },

	    collectExportFilters() {
	        const out = {};
	        document.querySelectorAll('[data-export-filter]').forEach(el => {
	            const key = el.getAttribute('data-export-filter');
	            if (!key) return;
	            if (el.type === 'checkbox') {
	                out[key] = el.checked ? '1' : '0';
	            } else {
	                out[key] = (el.value || '').trim();
	            }
	        });

	        // Convert Jalali date filters to Gregorian where backend needs it.
	        // - *_from_jalali -> *_from (00:00)
	        // - *_to_jalali -> *_to (23:59)
	        Object.keys(out).forEach((k) => {
	            if (!k.endsWith('_jalali')) return;
	            const val = (out[k] || '').trim();
	            if (!val) return;

	            const base = k.replace(/_jalali$/, '');
	            if (base.endsWith('_from')) {
	                const g = persianToGregorian(val, '00:00');
	                if (g) out[base] = g;
	            } else if (base.endsWith('_to')) {
	                const g = persianToGregorian(val, '23:59');
	                if (g) out[base] = g;
	            } else {
	                // fallback: store as-is (not converted)
	            }
	        });

	        return out;
	    },

	    initJalaliDatePickers(nodeList, options = {}) {
	        const inputs = Array.from(nodeList || []).filter(Boolean);
	        if (!inputs.length) return;

        const initInput = (input) => {
            if (!input) return;
            if (typeof $ === 'undefined' || typeof $.fn.persianDatepicker === 'undefined') {
                setTimeout(() => initInput(input), 200);
                return;
            }
	            try {
	                if ($(input).data('persianDatepicker')) {
	                    $(input).persianDatepicker('destroy');
	                }
	            } catch (_) {}
            $(input).persianDatepicker({
                format: 'YYYY/MM/DD',
                initialValue: false,
                autoClose: true,
                observer: true,
                maxDate: (options && options.allowFuture) ? null : this.getTodayGregorianDateOnly()
            });
        };

	        inputs.forEach(initInput);

	        // پیوند بازهٔ تاریخ: وقتی دقیقاً یک جفت [شروع، پایان] داده شود و linkRange فعال باشد،
	        // تاریخ پایان همیشه بزرگ‌تر-مساوی تاریخ شروع نگه داشته می‌شود.
        if (options && options.linkRange && inputs.length === 2) {
            this.linkJalaliDateRange(inputs[0], inputs[1], options);
        }
    },

	    // تاریخِ «پایان» را وادار می‌کند بزرگ‌تر-مساویِ تاریخِ «شروع» بماند:
	    // ۱) مینیمم تقویمِ پایان روی تاریخ شروع تنظیم می‌شود (روزهای قبل‌تر غیرفعال)
	    // ۲) اگر پایان قبل از شروع باشد، خودکار برابر شروع می‌شود (به‌جای نمایش خطای بازهٔ نامعتبر)
    linkJalaliDateRange(fromEl, toEl, options = {}) {
        if (!fromEl || !toEl || fromEl._ccRangeLinkedTo === toEl) return;
        fromEl._ccRangeLinkedTo = toEl;
        const self = this;
        const allowFuture = !!options.allowFuture;

        const parse = (value) => {
            const normalized = self.normalizeJalaliInputValue
                ? self.normalizeJalaliInputValue(value || '')
                : String(value || '').trim().replace(/[-.]/g, '/');
            const m = normalized.match(/^(\d{3,4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/);
            return m ? { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) } : null;
        };
        const cmp = (a, b) => (a.year - b.year) || (a.month - b.month) || (a.day - b.day);
        const format = (date) => `${String(date.year).padStart(4, '0')}/${String(date.month).padStart(2, '0')}/${String(date.day).padStart(2, '0')}`;
        const toGregorian = (date, endOfDay = false) => {
            if (!date) return '';
            return self.jalaliToGregorianDateOnly
                ? self.jalaliToGregorianDateOnly(format(date), endOfDay)
                : '';
        };
        const maxDate = () => allowFuture ? null : (self.getTodayGregorianDateOnly ? self.getTodayGregorianDateOnly() : null);

        const enforce = () => {
            if (toEl._ccRangeSyncing) return;
            const from = parse(fromEl.value);
            if (!from) return;
            const fromValue = format(from);
            if (fromEl.value !== fromValue) fromEl.value = fromValue;
            const fromG = toGregorian(from, false);
            // مینیمم تاریخِ قابل‌انتخابِ پایان = تاریخ شروع
            const applyMin = () => {
                if (typeof $ !== 'undefined' && typeof $.fn?.persianDatepicker === 'function') {
                    try {
                        $(toEl).persianDatepicker({
                            minDate: fromG || fromValue,
                            maxDate: maxDate()
                        });
                        return true;
                    } catch (_) {}
                }
                if (!toEl._neoJdp) return false;
                toEl._neoJdp.minDate = { year: from.year, month: from.month, day: from.day };
                return true;
            };
            if (!applyMin()) setTimeout(applyMin, 250);
            // اصلاح خودکار: پایان نباید قبل از شروع باشد
            const to = parse(toEl.value);
            if (to && cmp(to, from) < 0) {
                toEl._ccRangeSyncing = true;
                toEl.value = fromValue;
                toEl.dispatchEvent(new Event('input', { bubbles: true }));
                toEl.dispatchEvent(new Event('change', { bubbles: true }));
                toEl._ccRangeSyncing = false;
            }
        };

        fromEl.addEventListener('input', enforce);
        fromEl.addEventListener('change', enforce);
        fromEl.addEventListener('blur', enforce);
        toEl.addEventListener('input', enforce);
        toEl.addEventListener('change', enforce);
        toEl.addEventListener('blur', enforce);
        ['focus', 'pointerdown', 'mousedown', 'click'].forEach((eventName) => {
            toEl.addEventListener(eventName, enforce, true);
        });
        enforce();
    },

	    collectExportColumns() {
	        const cols = [];
	        document.querySelectorAll('#export-columns input[type="checkbox"][data-export-column]').forEach(cb => {
	            if (cb.checked && !cb.disabled) {
	                cols.push(cb.getAttribute('data-export-column'));
	            }
	        });
	        return cols.filter(Boolean);
	    },

	    submitExport() {
	        if (!this.canCurrentUserCapabilityClient('menu.exports') && !this.canCurrentUserCapabilityClient('export.catalog')) {
	            Swal.fire('خطا', 'دسترسی خروجی برای شما مجاز نیست', 'error');
	            return;
	        }

	        const dataset = document.getElementById('export-dataset')?.value || '';
	        const format = document.getElementById('export-format')?.value || 'csv';
	        if (!dataset) {
	            Swal.fire('خطا', 'نوع خروجی را انتخاب کنید', 'error');
	            return;
	        }

	        const columns = this.collectExportColumns();
	        const filters = this.collectExportFilters();

	        document.getElementById('export-form-dataset').value = dataset;
	        document.getElementById('export-form-format').value = format;
	        document.getElementById('export-form-columns').value = JSON.stringify(columns);
	        document.getElementById('export-form-filters').value = JSON.stringify(filters);

	        document.getElementById('export-form').submit();
	    },
    
    // رفتن به بخش لیدها با فیلتر مشخص
    async goToLeadsWithFilter(stage = 'all') {
        const moved = await this.navigateToView('leads', { source: 'go_to_leads_filter', skipInitialLoad: true });
        if (!moved) return;
        // تنظیم فیلترها
        const stageFilter = document.getElementById('leads-stage-filter');
        const registerFrom = document.getElementById('leads-register-from');
        const registerTo = document.getElementById('leads-register-to');
        const activityFrom = document.getElementById('leads-activity-from');
        const activityTo = document.getElementById('leads-activity-to');
        const productFilter = document.getElementById('leads-product-filter');
        
        if(stageFilter) stageFilter.value = stage;
        [registerFrom, registerTo, activityFrom, activityTo].forEach(inp => { if (inp) inp.value = ''; });
        if (productFilter) productFilter.value = '';
        
        // بارگذاری لیدها با فیلتر
        this.loadLeads();
    },

    async goToDealsWithFilter(pipelineStage = 'all', stageLabel = null, options = {}) {
        const moved = await this.navigateToView('deals', { source: 'go_to_deals_filter', skipInitialLoad: true });
        if (!moved) return;
        const stageFilter = document.getElementById('deals-pipeline-filter');
        // Sync the bucket dropdown so loadDeals (which reads it) filters correctly. Clicking a
        // parked-stage card => only parked deals; any other dashboard card => clear the filter.
        const bucketFilter = document.getElementById('deals-bucket-filter');
        if (bucketFilter) bucketFilter.value = options?.pipelineBucket || 'all';
        this.state.dealsPipelineBucketFilter = options?.pipelineBucket || '';
        if (stageFilter && pipelineStage !== 'all') {
            this.ensureSelectOption(stageFilter, pipelineStage, stageLabel || pipelineStage, true);
        }
        if (stageFilter) stageFilter.value = pipelineStage;
        // Carry the dashboard owner filter over to the deals page so a stage click respects
        // the currently selected user (both selects share the same user option list).
        const dashOwner = document.getElementById('dashboard-owner-filter');
        const dealsOwner = document.getElementById('deals-owner-filter');
        if (dealsOwner && dashOwner) {
            const ownerVal = dashOwner.value || '';
            if (ownerVal) {
                const ownerLabel = dashOwner.options[dashOwner.selectedIndex]?.text || ownerVal;
                this.ensureSelectOption(dealsOwner, ownerVal, ownerLabel, true);
            }
            dealsOwner.value = ownerVal;
        }
        this.loadDeals();
    },

    safeSetText(id, value = '-') {
        const el = document.getElementById(id);
        if (el) el.textContent = value;
    },

    resetProfileModalState(options = {}) {
        const opts = options || {};
        const forOpen = opts.forOpen === true;

        this.safeSetText('prof-name', '---');
        this.safeSetText('prof-mobile', '-');
        this.safeSetText('prof-email', '-');
        this.safeSetText('prof-info-name', '-');
        this.safeSetText('prof-info-customer-code', '-');
        this.safeSetText('prof-info-mobile', '-');
        this.safeSetText('prof-info-mobile2', '-');
        this.safeSetText('prof-info-mobile3', '-');
        this.safeSetText('prof-info-mobile4', '-');
        this.safeSetText('prof-info-email', '-');
        this.safeSetText('prof-info-owner', '-');
        this.safeSetText('prof-info-prev-owner', '-');
        this.safeSetText('prof-info-register', '-');
        this.safeSetText('prof-info-created-by', '-');
        this.safeSetText('prof-info-created-via', '-');
        document.getElementById('prof-info-created-by-container')?.classList.add('hidden');
        document.getElementById('prof-info-created-via-container')?.classList.add('hidden');
        this.safeSetText('prof-info-sale-status', '-');
        this.safeSetText('prof-info-national-id', '-');
        this.safeSetText('prof-info-city', '-');
        this.safeSetText('prof-info-job', '-');
        this.safeSetText('prof-info-job-desc', '-');
        this.safeSetText('prof-info-source', '-');
        this.safeSetText('prof-info-source-detail', '-');
        this.safeSetText('prof-info-topic', '-');
        this.safeSetText('prof-info-products', '-');
        this.safeSetText('prof-info-acq-duration', '-');
        this.safeSetText('prof-info-extra-info', '-');
        this.safeSetText('prof-info-financial-level', '-');
        this.safeSetText('prof-info-asset', '-');
        this.safeSetText('prof-info-income', '-');
        this.safeSetText('prof-info-purchase-status', '-');
        this.safeSetText('prof-info-requested-services', '-');
        this.safeSetText('prof-info-background', '-');
        const stageEl = document.getElementById('prof-info-stage');
        if (stageEl) stageEl.innerHTML = '';

        const deals = document.getElementById('prof-deals');
        if (deals) deals.innerHTML = forOpen ? '<p class="text-sm text-gray-500">در حال بارگذاری...</p>' : '';
        const activities = document.getElementById('prof-activities');
        if (activities) activities.innerHTML = forOpen ? '<p class="text-sm text-gray-500">در حال بارگذاری...</p>' : '';
        const sats = document.getElementById('prof-satisfactions');
        if (sats) sats.innerHTML = forOpen ? '<p class="text-sm text-gray-500">در حال بارگذاری...</p>' : '';

        this.state.currentLeadActivities = [];
    },

    resetDealModalState(options = {}) {
        const opts = options || {};
        const keepContact = opts.keepContact === true;
        const prevContact = document.getElementById('deal-contact-id')?.value || window.currentContactId || '';

        this.cleanupPersianDatepickers(document.getElementById('modalDeal') || document);
        const form = document.getElementById('deal-form');
        if (form) form.reset();
        const titleEl = document.getElementById('deal-modal-title');
        if (titleEl) titleEl.textContent = 'ثبت معامله جدید';

        const dealIdInput = document.getElementById('deal-id');
        if (dealIdInput) dealIdInput.value = '';
        const dealContactInput = document.getElementById('deal-contact-id');
        if (dealContactInput) dealContactInput.value = keepContact ? prevContact : '';
        const userName = document.getElementById('deal-user-name');
        if (userName) userName.value = '';
        const userMobile = document.getElementById('deal-user-mobile');
        if (userMobile) userMobile.value = '';
        const createdByWrap = document.getElementById('deal-summary-created-by-wrap');
        if (createdByWrap) createdByWrap.classList.add('hidden');
        this.safeSetText('deal-summary-created-by', '-');
        const createdViaWrap = document.getElementById('deal-summary-created-via-wrap');
        if (createdViaWrap) createdViaWrap.classList.add('hidden');
        this.safeSetText('deal-summary-created-via', '-');
        document.getElementById('deal-parked-badge')?.classList.add('hidden');
        const moveToParkedBtn = document.getElementById('move-to-parked-btn');
        if (moveToParkedBtn) moveToParkedBtn.style.display = 'none';
        const moveOutOfParkedBtn = document.getElementById('move-out-of-parked-btn');
        if (moveOutOfParkedBtn) moveOutOfParkedBtn.style.display = 'none';
        this.updateDealMarketingUi(null, false);

        this.safeSetText('deal-current-status', '-');
        this.safeSetText('deal-current-stage', '-');
        const dealActDeal = document.getElementById('deal-activity-deal-id');
        if (dealActDeal) dealActDeal.value = '';
        const dealActContact = document.getElementById('deal-activity-contact-id');
        if (dealActContact) dealActContact.value = keepContact ? prevContact : '';
        const dealActStage = document.getElementById('deal-activity-stage');
        if (dealActStage) dealActStage.value = '';
        const dealActCurrent = document.getElementById('deal-activity-current-stage');
        if (dealActCurrent) dealActCurrent.textContent = '-';
        const inline = document.getElementById('deal-activities-inline');
        if (inline) inline.innerHTML = '<div class="text-center text-gray-500 py-4">فعالیتی یافت نشد</div>';

        const failureSection = document.getElementById('deal-failure-reason-section');
        if (failureSection) failureSection.classList.add('hidden');
        const failureReason = document.getElementById('deal-failure-reason');
        if (failureReason) {
            failureReason.value = '';
            failureReason.disabled = false;
        }
        const failureOther = document.getElementById('deal-failure-other-reason');
        if (failureOther) failureOther.classList.add('hidden');
        const failureOtherDesc = document.getElementById('deal-failure-other-description');
        if (failureOtherDesc) {
            failureOtherDesc.value = '';
            failureOtherDesc.readOnly = false;
        }

        this.state.dealHasActivity = false;
        this.state.currentDeal = null;
        this.state.currentDeals = [];
        this.state.dealActivityRequestedServices = [];
        this.paymentRowCounter = 0;
        this.setPaymentsLockState(false);
        this.state.dealInfoLocked = false;
        this.state.dealInfoAdminUnlocked = false;
        this.enableDealFormFields();
        this.dealIsWon = false;
        const paidEditBtn = document.getElementById('deal-paid-edit-toggle-btn');
        if (paidEditBtn) {
            paidEditBtn.classList.add('hidden');
            paidEditBtn.textContent = 'ویرایش اطلاعات قفل‌شده';
        }
    },

    resetActivityModalState() {
        const form = document.querySelector('#modalActivity form');
        if (form) form.reset();
        const activityContactId = document.getElementById('activity-contact-id');
        if (activityContactId) activityContactId.value = '';
        const activityDealId = document.getElementById('activity-deal-id');
        if (activityDealId) activityDealId.value = '';
        const display = document.getElementById('activity-current-stage-display');
        if (display) display.innerHTML = '<span class="text-sm text-gray-500">مرحله تعیین نشده</span>';
        const currentStage = document.getElementById('activity-current-stage');
        if (currentStage) currentStage.value = 'keep_current';
        const ownerSnapshot = document.getElementById('activity-owner-snapshot-display');
        if (ownerSnapshot) {
            const name = String(APP_USER?.name || this.state.user?.name || this.state.user?.display_name || APP_USER?.didar_id || '').trim() || 'نامشخص';
            ownerSnapshot.innerHTML = `<span class="text-sm font-bold text-white">${this.escapeHtml(name)}</span>`;
        }
        const leadInfo = document.getElementById('activity-lead-info-display');
        if (leadInfo) leadInfo.classList.add('hidden');
        const leadsSelect = document.getElementById('activity-deal-select');
        if (leadsSelect) leadsSelect.innerHTML = '<option value="">-- انتخاب معامله مرتبط (اختیاری) --</option>';
        this.state.activityStagePrev = '';
    },
     
    openModal(id, options = {}) { 
        const opts = options || {};
        const source = String(opts.source || 'program');
        if (typeof this.closeAllCustomMultiSelectDropdowns === 'function') {
            this.closeAllCustomMultiSelectDropdowns();
        }
        if (document.activeElement && typeof document.activeElement.blur === 'function') {
            document.activeElement.blur();
        }
        const el = document.getElementById(id);
        if(el) el.classList.remove('hidden');
        // Prevent background focus when modal open
        this.setPageInert(true);
        
        // Initialize Persian date picker when activity modal opens
        if(id === 'modalActivity') {
            this.resetActivityModalState();
            // Populate lead info display
            const infoDisplay = document.getElementById('activity-lead-info-display');
            const nameText = document.getElementById('activity-lead-name-text');
            if (this.state.currentLead && infoDisplay && nameText) {
                const fullName = `${this.state.currentLead.first_name || ''} ${this.state.currentLead.last_name || ''}`.trim() || 'بدون نام';
                nameText.textContent = `${fullName} (${this.state.currentLead.mobile_phone || '-'})`;
                infoDisplay.classList.remove('hidden');
            } else if (infoDisplay) {
                infoDisplay.classList.add('hidden');
            }

            // تنظیم contactId از window.currentContactId
            if (window.currentContactId) {
                const activityContactId = document.getElementById('activity-contact-id');
                if (activityContactId) {
                    activityContactId.value = window.currentContactId;
                }
            }

            // Populate deals dropdown with cached deals for this شخص
            if (this.state.currentDeals && this.state.currentDeals.length) {
                this.populateActivityDealSelect(this.state.currentDeals);
            } else {
                this.populateActivityDealSelect([]);
            }
            this.toggleDealActivityFailure();
            
            // Reset schedule fields
            const isScheduledCheckbox = document.getElementById('activity-is-scheduled');
            const scheduleFields = document.getElementById('activity-schedule-fields');
            if (isScheduledCheckbox) isScheduledCheckbox.checked = false;
            if (scheduleFields) scheduleFields.classList.add('hidden');
            
            // Load activity types for modal
            this.loadActivityTypesForModal();

            // Initialize deal selection handling
            this.handleActivityDealSelection();

            const activityStageSelect = document.getElementById('activity-virtual-stage');
            if (activityStageSelect) {
                activityStageSelect.onchange = (e) => {
                    this.state.activityStagePrev = e.target.value;
                    this.toggleDealActivityFailure();
                    this.handleActivityStageChange(e.target.value);
                };
            }
            
            setTimeout(() => {
                // Initialize activity date picker when modal opens (update every time)
                this.initActivityDatePicker(); // This will update the datepicker with today's date
                this.initPersianDatePicker();
                this.syncActivityDatePickerToTehran();
                
                // افزودن event listener به select های مرحله
                // فقط برای تنظیم فرم، بدون وابستگی به virtual_stage
            }, 100);
        }
        
        // Set contact ID for deal modal
        if(id === 'modalDeal' && window.currentContactId) {
            const dealContactId = document.getElementById('deal-contact-id');
            if (dealContactId) {
                dealContactId.value = window.currentContactId;
            }
        }

        if (this.featureFlags.historyV1 && !opts.skipHistory && !this._isApplyingHistoryState) {
            const view = this.normalizeView(this.getCurrentView());
            const top = this.isValidHistoryState(history.state) ? history.state : null;
            const entityId = String(opts.entityId || '');
            const sameModal = !!(top &&
                top.view === view &&
                top.modal &&
                top.modal.id === id &&
                String(top.modal.entityId || '') === entityId);
            if (!sameModal) {
                const modalPayload = { id: String(id) };
                if (entityId) modalPayload.entityId = entityId;
                const state = this.buildHistoryState({
                    view,
                    modal: modalPayload,
                    ui: this.collectHistoryUiState(view)
                });
                this.syncHistoryState(state, { replace: false, source: `modal_open_${source}` });
            }
        }
    },
    
    initPersianDatePicker() {
        // Initialize Persian date picker for activity modal
        const dateInput = document.getElementById('due-date-jalali');
        if (!dateInput) return;
        
        // Wait for jQuery and persianDatepicker to be available
        if (typeof $ === 'undefined' || typeof $.fn.persianDatepicker === 'undefined') {
            // Retry after a short delay
            setTimeout(() => this.initPersianDatePicker(), 200);
            return;
        }
        
        // Destroy existing picker if any
        try {
            if ($(dateInput).data('persianDatepicker')) {
                $(dateInput).persianDatepicker('destroy');
            }
        } catch(e) {
            console.log('No existing picker to destroy');
        }
        
        // Initialize Persian date picker
        try {
            $(dateInput).persianDatepicker({
                observer: true,
                format: 'YYYY/MM/DD',
                altField: '#due-date-hidden',
                altFormat: 'YYYY-MM-DD',
                calendarType: 'persian',
                timePicker: {
                    enabled: false
                },
                initialValue: false,
                autoClose: true,
                calendar: {
                    persian: {
                        locale: 'fa'
                    }
                }
            });
        } catch(e) {
            console.error('Error initializing Persian date picker:', e);
            // Fallback: make it a simple text input
            dateInput.removeAttribute('readonly');
        }
    },
    
    async closeModalFromUser(id) {
        const key = this.getDirtyKeyByModal(id);
        const ok = key ? await this.confirmDiscardChanges(`close_${String(id || '')}`, [key]) : true;
        if (!ok) return false;
        this.clearDirtyFormsByModal(id);
        this.closeModal(id, { source: 'user' });
        return true;
    },

    suspendKpiDrilldownForNestedModal() {
        const drilldown = document.getElementById('modalKpiDrilldown');
        if (!drilldown || drilldown.classList.contains('hidden')) {
            return false;
        }
        this.state.suspendedKpiDrilldown = true;
        drilldown.classList.add('hidden');
        return true;
    },

    restoreSuspendedKpiDrilldown() {
        if (!this.state.suspendedKpiDrilldown) {
            return;
        }
        const drilldown = document.getElementById('modalKpiDrilldown');
        if (drilldown) {
            drilldown.classList.remove('hidden');
            this.setPageInert(true);
        }
        this.state.suspendedKpiDrilldown = false;
    },

    closeModal(id, options = {}) { 
        const opts = options || {};
        const source = String(opts.source || 'program');

        const el = document.getElementById(id);
        if(el) el.classList.add('hidden');

        this.cleanupPersianDatepickers(el || document);
        if (id === 'modalProfile') this.resetProfileModalState({ forOpen: false });
        if (id === 'modalDeal') this.resetDealModalState({ keepContact: true });
        if (id === 'modalActivity') this.resetActivityModalState();
        this.clearDirtyFormsByModal(id);
        if (['modalDeal', 'modalProfile', 'modalLead'].includes(id)) {
            this.restoreSuspendedKpiDrilldown();
        }

        if (this.featureFlags.historyV1 && !opts.skipHistory && source !== 'history' && !this._isApplyingHistoryState) {
            const view = this.normalizeView(this.getCurrentView());
            const state = this.buildHistoryState({
                view,
                modal: null,
                ui: this.collectHistoryUiState(view)
            });
            this.syncHistoryState(state, { replace: true, source: `modal_close_${source}` });
        }

        // Restore background focusability if no other modals are open
        const anyOpen = document.querySelectorAll('.fixed.inset-0:not(.hidden)').length > 0;
        if (!anyOpen) {
            this.setPageInert(false);
        }
    },
    
    setPageInert(isInert) {
        // فقط روی بخش‌های محتوایی صفحه (view-section ها و سایدبار) اعمال می‌کنیم، نه روی مودال‌ها
        const elements = document.querySelectorAll('.view-section, aside');
        elements.forEach(el => {
            if (isInert) {
                el.setAttribute('aria-hidden', 'true');
                el.classList.add('bg-inert-active');
            } else {
                el.removeAttribute('aria-hidden');
                el.classList.remove('bg-inert-active');
            }
        });
        console.log('setPageInert', isInert, 'applied on', elements.length, 'elements');
    },
    
    // NOTIFICATION FUNCTIONS
    async loadNotifications() {
        try {
            const res = await this.req('get_notifications', { unread_only: '1', limit: 20 });
            if (res.status === 'success') {
                this.renderNotifications(res.notifications || []);
                this.updateNotificationBadge(res.unread_count || 0);
            }
        } catch (error) {
            console.error('Error loading notifications:', error);
        }
    },
    
    renderNotifications(notifications) {
        const container = document.getElementById('notifications-list');
        if (!container) return;
        
        if (notifications.length === 0) {
            container.innerHTML = '<div class="text-center text-gray-500 text-sm py-8">اعلانی وجود ندارد</div>';
            return;
        }
        
        container.innerHTML = notifications.map(notif => {
            const isRead = notif.is_read == 1;
            const timeAgo = this.getTimeAgo(notif.created_at);
            const icon = this.getNotificationIcon(notif.type);
            // Shared accent-stripe by notification type (mobile-only; inert on desktop).
            const notifSMap = { lead_assigned: 's--info', activity_new: 's--neutral', activity_reminder: 's--pending', deal_created: 's--success', deal_status_changed: 's--info', satisfaction_new: 's--warning' };
            const notifSClass = notifSMap[notif.type] || 's--neutral';

            return `
                <div class="p-3 border-b border-gray-700 hover:bg-gray-700/50 cursor-pointer accent-card ${notifSClass} ${!isRead ? 'bg-blue-500/10' : ''}"
                     onclick="app.handleNotificationClick('${notif.id}', '${notif.entity_type || ''}', '${notif.entity_id || ''}')">
                    <div class="flex items-start gap-3">
                        <div class="text-2xl">${icon}</div>
                        <div class="flex-1">
                            <div class="flex items-center justify-between mb-1">
                                <h4 class="text-sm font-bold text-white ${!isRead ? '' : 'text-gray-400'}">${this.escapeHtml(notif.title)}</h4>
                                ${!isRead ? '<span class="w-2 h-2 bg-blue-500 rounded-full"></span>' : ''}
                            </div>
                            <p class="text-xs text-gray-400 mb-1">${this.escapeHtml(notif.message || '')}</p>
                            <div class="flex items-center justify-between">
                                <span class="text-[10px] text-gray-500">${timeAgo}</span>
                                <button onclick="event.stopPropagation(); app.deleteNotification('${notif.id}')" 
                                        class="text-red-400 hover:text-red-300 text-xs">
                                    <i class="fa-solid fa-trash"></i>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            `;
        }).join('');
    },
    
    getNotificationIcon(type) {
        const icons = {
            'lead_assigned': '👤',
            'activity_new': '📝',
            'activity_reminder': '⏰',
            'deal_created': '💼',
            'deal_status_changed': '🔄',
            'satisfaction_new': '⭐'
        };
        return icons[type] || '🔔';
    },
    
    getTimeAgo(dateString) {
        if (!dateString) return '';
        try {
            const date = new Date(dateString);
            const now = new Date();
            const diff = now - date;
            const minutes = Math.floor(diff / 60000);
            const hours = Math.floor(minutes / 60);
            const days = Math.floor(hours / 24);
            
            if (minutes < 1) return 'همین الان';
            if (minutes < 60) return `${minutes} دقیقه پیش`;
            if (hours < 24) return `${hours} ساعت پیش`;
            if (days < 7) return `${days} روز پیش`;
            
            return toPersianDateTime(dateString);
        } catch (e) {
            return dateString;
        }
    },
    
    updateNotificationBadge(count) {
        const badge = document.getElementById('notification-badge');
        if (badge) {
            if (count > 0) {
                badge.textContent = count > 99 ? '99+' : count;
                badge.classList.remove('hidden');
            } else {
                badge.classList.add('hidden');
            }
        }
    },
    
    toggleNotifications() {
        const dropdown = document.getElementById('notifications-dropdown');
        if (!dropdown) return;
        
        if (dropdown.classList.contains('hidden')) {
            dropdown.classList.remove('hidden');
            this.loadNotifications();
        } else {
            dropdown.classList.add('hidden');
        }
    },
    
    async handleNotificationClick(notificationId, entityType, entityId) {
        // Mark as read
        await this.markNotificationRead(notificationId);
        
        // Navigate to entity if exists
        if (entityType && entityId) {
            if (entityType === 'lead' || entityType === 'person') {
                await this.openPersonProfile(entityId);
            } else if (entityType === 'activity') {
                // Could open activity details if needed
            } else if (entityType === 'deal') {
                // Could open deal details if needed
            }
        }
        
        // Reload notifications
        await this.loadNotifications();
    },
    
    async markNotificationRead(notificationId) {
        try {
            await this.req('mark_notification_read', { notification_id: notificationId });
        } catch (error) {
            console.error('Error marking notification as read:', error);
        }
    },
    
    async markAllNotificationsRead() {
        try {
            const res = await this.req('mark_all_notifications_read', {});
            if (res.status === 'success') {
                await this.loadNotifications();
            }
        } catch (error) {
            console.error('Error marking all notifications as read:', error);
        }
    },
    
    async deleteNotification(notificationId) {
        try {
            const res = await this.req('delete_notification', { notification_id: notificationId });
            if (res.status === 'success') {
                await this.loadNotifications();
            }
        } catch (error) {
            console.error('Error deleting notification:', error);
        }
    },
    
    // POLLING SYSTEM
    notificationPollTimer: null,
    notificationPollingStarted: false,
    notificationPollingInFlight: false,
    notificationPollBaseDelayMs: 30000,
    notificationPollMaxDelayMs: 180000,
    notificationPollErrorCount: 0,
    notificationVisibilityHandlerBound: false,

    getNotificationPollDelay() {
        const multiplier = Math.min(Math.max(this.notificationPollErrorCount || 0, 0), 3);
        return Math.min(this.notificationPollBaseDelayMs * Math.pow(2, multiplier), this.notificationPollMaxDelayMs);
    },

    scheduleNotificationPoll(delayMs) {
        if (!this.notificationPollingStarted) {
            return;
        }
        if (this.notificationPollTimer) {
            clearTimeout(this.notificationPollTimer);
            this.notificationPollTimer = null;
        }
        const delay = Math.max(1000, Number(delayMs) || this.notificationPollBaseDelayMs);
        this.notificationPollTimer = setTimeout(() => {
            this.runNotificationPollTick();
        }, delay);
    },

    async runNotificationPollTick(force = false) {
        if (this.notificationPollingInFlight) {
            this.scheduleNotificationPoll(this.getNotificationPollDelay());
            return;
        }

        if (!force && document.visibilityState === 'hidden') {
            this.scheduleNotificationPoll(this.notificationPollBaseDelayMs);
            return;
        }

        this.notificationPollingInFlight = true;
        try {
            const res = await this.req('check_reminders', {});
            if (res.status === 'success') {
                this.notificationPollErrorCount = 0;

                if (res.notifications && res.notifications.length > 0) {
                    await this.loadNotifications();
                }

                if (res.new_notifications && res.new_notifications.length > 0) {
                    res.new_notifications.forEach(notif => {
                        this.showBrowserNotification(notif);
                    });
                }

                if (res.popup_notifications && res.popup_notifications.length > 0) {
                    res.popup_notifications.forEach(notif => {
                        this.showBrowserNotification(notif);
                    });
                }

                if (res.popup_notifications && res.popup_notifications.length > 0) {
                    console.log('Showing popup notifications:', res.popup_notifications);
                    res.popup_notifications.forEach(notif => {
                        if (this.notificationPopupEnabled) {
                            this.showNotificationPopup(notif);
                        }
                        if (this.notificationBrowserEnabled) {
                            this.showBrowserNotification(notif);
                        }
                    });
                }
            } else {
                this.notificationPollErrorCount = Math.min((this.notificationPollErrorCount || 0) + 1, 10);
            }
        } catch (error) {
            this.notificationPollErrorCount = Math.min((this.notificationPollErrorCount || 0) + 1, 10);
            console.error('Error checking reminders:', error);
        } finally {
            this.notificationPollingInFlight = false;
            if (this.notificationPollingStarted) {
                this.scheduleNotificationPoll(this.getNotificationPollDelay());
            }
        }
    },

    startNotificationPolling() {
        if (this.notificationPollingStarted) {
            return;
        }
        this.notificationPollingStarted = true;

        if (!this.notificationVisibilityHandlerBound) {
            this.notificationVisibilityHandlerBound = true;
            document.addEventListener('visibilitychange', () => {
                if (document.visibilityState === 'visible') {
                    this.scheduleNotificationPoll(1000);
                }
            });
        }

        this.scheduleNotificationPoll(this.notificationPollBaseDelayMs);
    },

    stopNotificationPolling() {
        this.notificationPollingStarted = false;
        if (this.notificationPollTimer) {
            clearTimeout(this.notificationPollTimer);
            this.notificationPollTimer = null;
        }
    },
    
    // BROWSER NOTIFICATIONS
    requestNotificationPermission() {
        if ('Notification' in window && Notification.permission === 'default') {
            Notification.requestPermission().then(permission => {
                if (permission === 'granted') {
                    console.log('Notification permission granted');
                }
            });
        }
    },

    // NOTIFICATION SOUND FUNCTIONS
    notificationSoundEnabled: true,

    playNotificationSound() {
        if (!this.notificationSoundEnabled) return;

        try {
            // Create audio context if not exists
            if (!this.audioContext) {
                this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
            }

            // Create a simple beep sound
            const oscillator = this.audioContext.createOscillator();
            const gainNode = this.audioContext.createGain();

            oscillator.connect(gainNode);
            gainNode.connect(this.audioContext.destination);

            oscillator.frequency.setValueAtTime(800, this.audioContext.currentTime); // Frequency in Hz
            oscillator.frequency.setValueAtTime(600, this.audioContext.currentTime + 0.1);

            gainNode.gain.setValueAtTime(0.1, this.audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.5);

            oscillator.start(this.audioContext.currentTime);
            oscillator.stop(this.audioContext.currentTime + 0.5);
        } catch (error) {
            console.log('Sound not supported, trying HTML5 audio');
            // Fallback to HTML5 audio
            try {
                const audio = new Audio();
                audio.src = 'data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmUhBzqL1fLNfTEFIHDF8N6UWgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfTMFIHDF8N6UXgpUX7Hq66xYFw5Gn+DyvmciBzqL1fLNfLM';
                audio.play().catch(e => console.log('Audio play failed:', e));
            } catch (e) {
                console.log('Audio not supported');
            }
        }
    },

    toggleNotificationSound() {
        this.notificationSoundEnabled = !this.notificationSoundEnabled;
        this.updateSoundButton();
        localStorage.setItem('notificationSoundEnabled', this.notificationSoundEnabled);

        // Show feedback
        const message = this.notificationSoundEnabled ? 'صدا نوتیفیکیشن فعال شد' : 'صدا نوتیفیکیشن غیرفعال شد';
        Swal.fire({
            toast: true,
            position: 'bottom-end',
            icon: 'info',
            title: message,
            timer: 2000,
            showConfirmButton: false
        });
    },

    // NOTIFICATION SETTINGS
    showNotificationSettings() {
        Swal.fire({
            title: 'تنظیمات نوتیفیکیشن',
            html: `
                <div class="text-right space-y-4">
                    <div class="flex items-center justify-between">
                        <label class="flex items-center">
                            <input type="checkbox" id="sound-enabled" class="ml-2">
                            <span>فعال کردن صدا</span>
                        </label>
                    </div>
                    <div class="flex items-center justify-between">
                        <label class="flex items-center">
                            <input type="checkbox" id="popup-enabled" class="ml-2" checked>
                            <span>نمایش پاپ‌آپ</span>
                        </label>
                    </div>
                    <div class="flex items-center justify-between">
                        <label class="flex items-center">
                            <input type="checkbox" id="browser-notif-enabled" class="ml-2" checked>
                            <span>نوتیفیکیشن مرورگر (دسکتاپ)</span>
                        </label>
                        <div id="browser-notif-status" class="text-sm"></div>
                    </div>
                    <div class="text-center mt-3">
                        <button type="button" onclick="app.requestNotificationPermission()" class="px-3 py-1 bg-blue-600 text-white text-sm rounded hover:bg-blue-700 transition">
                            درخواست دسترسی نوتیفیکیشن
                        </button>
                    </div>
                </div>
            `,
            confirmButtonText: 'ذخیره',
            cancelButtonText: 'لغو',
            showCancelButton: true,
            preConfirm: () => {
                const soundEnabled = document.getElementById('sound-enabled').checked;
                const popupEnabled = document.getElementById('popup-enabled').checked;
                const browserNotifEnabled = document.getElementById('browser-notif-enabled').checked;

                // Save settings
                localStorage.setItem('notificationSoundEnabled', soundEnabled);
                localStorage.setItem('notificationPopupEnabled', popupEnabled);
                localStorage.setItem('notificationBrowserEnabled', browserNotifEnabled);

                // Update current settings
                this.notificationSoundEnabled = soundEnabled;
                this.notificationPopupEnabled = popupEnabled;
                this.notificationBrowserEnabled = browserNotifEnabled;

                // Update status display
                this._updateNotificationStatus();

                this.updateSoundButton();

                return {
                    soundEnabled,
                    popupEnabled,
                    browserNotifEnabled
                };
            }
        }).then((result) => {
            if (result.isConfirmed) {
                Swal.fire({
                    toast: true,
                    position: 'bottom-end',
                    icon: 'success',
                    title: 'تنظیمات ذخیره شد',
                    timer: 2000,
                    showConfirmButton: false
                });
            }
        });

        // Load current settings
        setTimeout(() => {
            document.getElementById('sound-enabled').checked = this.notificationSoundEnabled;
            document.getElementById('popup-enabled').checked = localStorage.getItem('notificationPopupEnabled') !== 'false';
            document.getElementById('browser-notif-enabled').checked = localStorage.getItem('notificationBrowserEnabled') !== 'false';

            // Show current browser permission status
            this._updateNotificationStatus();
        }, 100);
    },

    updateSoundButton() {
        const button = document.getElementById('sound-toggle-btn');
        if (button) {
            const icon = button.querySelector('i');
            if (icon) {
                icon.className = this.notificationSoundEnabled ?
                    'fa-solid fa-volume-up text-xl' :
                    'fa-solid fa-volume-mute text-xl';
            }
            button.className = this.notificationSoundEnabled ?
                'text-green-400 hover:text-green-300 transition p-2 rounded-full hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-gray-700' :
                'text-gray-400 hover:text-gray-300 transition p-2 rounded-full hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-gray-700';
            button.title = this.notificationSoundEnabled ? 'صدا فعال است - کلیک برای غیرفعال کردن' : 'صدا غیرفعال است - کلیک برای فعال کردن';
        }
    },

    // NOTIFICATION POPUP FUNCTIONS
    notificationPopups: [],

    showNotificationPopup(notification) {
        // Check if popup should be shown
        console.log('Show notification popup called:', notification);

        // Check user settings
        if (!this.notificationPopupEnabled) {
            console.log('Popup notifications disabled by user settings');
            return;
        }

        // Default to show popup if not specified
        if (notification.show_popup !== undefined && !notification.show_popup) {
            console.log('Popup disabled for this notification');
            return;
        }

        // Play sound if enabled
        if (this.notificationSoundEnabled) {
            this.playNotificationSound();
        }

        const popupId = 'popup-' + notification.id + '-' + Date.now();

        // Create popup element
        const popup = document.createElement('div');
        popup.className = 'notification-popup';
        popup.id = popupId;

        const icon = this.getNotificationIcon(notification.type);
        const timeAgo = this.getTimeAgo(notification.created_at);

        popup.innerHTML = `
            <div class="notification-popup-content">
                <div class="notification-popup-header">
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <span style="font-size: 18px;">${icon}</span>
                        <h4 class="notification-popup-title">${this.escapeHtml(notification.title)}</h4>
                    </div>
                        <div class="flex items-center gap-2">
                            <button class="notification-popup-close" onclick="app.closeNotificationPopup('${popupId}')" title="بستن این اعلان">
                                <i class="fa-solid fa-times"></i>
                            </button>
                            ${this.notificationPopups.length > 1 ? `<button class="notification-popup-close-all" onclick="app.closeAllNotificationPopups()" title="بستن همه اعلان‌ها">
                                <i class="fa-solid fa-times-circle"></i>
                            </button>` : ''}
                        </div>
                </div>
                <p class="notification-popup-message">${this.escapeHtml(notification.message || '')}</p>
                <div class="notification-popup-footer">
                    <span class="notification-popup-time">${timeAgo}</span>
                    <div class="notification-popup-actions">
                        <button class="notification-popup-action primary" onclick="app.handleNotificationClick('${notification.id}', '${notification.entity_type || ''}', '${notification.entity_id || ''}'); app.closeNotificationPopup('${popupId}')">
                            مشاهده
                        </button>
                        <button class="notification-popup-action secondary" onclick="app.closeNotificationPopup('${popupId}')">
                            رد
                        </button>
                    </div>
                </div>
            </div>
        `;

        // Add to page
        document.body.appendChild(popup);

        // Add to tracking array
        this.notificationPopups.push({
            id: popupId,
            element: popup,
            notification: notification
        });

        // Show with animation
        console.log('Adding popup to page:', popupId);
        setTimeout(() => {
            console.log('Adding show class to popup:', popupId);
            popup.classList.add('show');
        }, 100);

        // Remove auto-close timer - popups stay until user interaction

        // No limit on popup count - all notifications stay visible until user interaction
    },

    closeNotificationPopup(popupId) {
        const popup = document.getElementById(popupId);
        if (!popup) return;

        popup.classList.remove('show');
        popup.classList.add('hide');

        setTimeout(() => {
            if (popup.parentNode) {
                popup.parentNode.removeChild(popup);
            }
        }, 300);

        // Remove from tracking array
        this.notificationPopups = this.notificationPopups.filter(p => p.id !== popupId);
    },

    closeAllNotificationPopups() {
        const currentPopups = [...this.notificationPopups]; // Copy array to avoid modification during iteration
        currentPopups.forEach(popup => {
            this.closeNotificationPopup(popup.id);
        });
    },

    // Get count of active popups
    getActivePopupCount() {
        return this.notificationPopups.length;
    },

    showBrowserNotification(notification) {
        console.log('Attempting to show browser notification:', notification);

        // Check user settings
        if (!this.notificationBrowserEnabled) {
            console.log('Browser notifications disabled by user settings');
            return;
        }

        if (!('Notification' in window)) {
            console.log('This browser does not support notifications');
            return;
        }

        // Check permission and show notification
        if (Notification.permission === 'granted') {
            this._createBrowserNotification(notification);
        } else if (Notification.permission === 'default') {
            console.log('Requesting notification permission...');
            this.requestNotificationPermission().then((permission) => {
                if (permission === 'granted') {
                    console.log('Permission granted, creating notification');
                    this._createBrowserNotification(notification);
                } else {
                    console.log('Permission denied or failed');
                    this._showPermissionDeniedMessage();
                }
            });
        } else {
            console.log('Notifications are blocked by user');
            this._showPermissionDeniedMessage();
        }
    },

    _createBrowserNotification(notification) {
        try {
            const browserNotif = new Notification(notification.title, {
                body: notification.message,
                icon: '/public/logo.png',
                badge: '/public/logo.png',
                tag: 'saleos-' + notification.id,
                requireInteraction: true, // Keep notification until user interacts
                silent: false,
                dir: 'rtl',
                lang: 'fa',
                silent: false,
                timestamp: Date.now()
            });

            console.log('Browser notification created:', browserNotif);

            // No auto-close - notification stays until user interacts with it
            let autoCloseTimeout;

            // Handle click - focus window and navigate
            browserNotif.onclick = () => {
                console.log('Notification clicked');
                if (autoCloseTimeout) {
                    clearTimeout(autoCloseTimeout);
                }
                window.focus();

                // Try to bring window to front
                if (window.focus) {
                    window.focus();
                }

                // Navigate to relevant page if possible
                if (notification.entity_type && notification.entity_id) {
                    console.log('Should navigate to:', notification.entity_type, notification.entity_id);
                    // You can add navigation logic here
                    // For example: window.location.href = `/activities/${notification.entity_id}`;
                }

                browserNotif.close();
            };

            // Handle close
            browserNotif.onclose = () => {
                console.log('Notification closed');
                clearTimeout(autoCloseTimeout);
            };

            // Handle show
            browserNotif.onshow = () => {
                console.log('Notification shown successfully');
            };

            // Handle error
            browserNotif.onerror = (error) => {
                console.error('Notification error:', error);
            };

        } catch (error) {
            console.error('Failed to create browser notification:', error);
        }
    },

    _showPermissionDeniedMessage() {
        const browser = this._getBrowserName();
        const instructions = this._getBrowserInstructions(browser);

        Swal.fire({
            title: 'دسترسی نوتیفیکیشن مرورگر',
            text: 'برای دریافت نوتیفیکیشن‌های دسکتاپ، لطفاً دسترسی نوتیفیکیشن مرورگر را فعال کنید.',
            icon: 'warning',
            confirmButtonText: 'راهنمایی',
            showCancelButton: true,
            cancelButtonText: 'بعداً'
        }).then((result) => {
            if (result.isConfirmed) {
                Swal.fire({
                    title: `فعال کردن نوتیفیکیشن در ${browser}`,
                    html: instructions,
                    confirmButtonText: 'باشه',
                    width: 600
                });
            }
        });
    },

    _getBrowserName() {
        const userAgent = navigator.userAgent;
        if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) {
            return 'Chrome';
        } else if (userAgent.includes('Firefox')) {
            return 'Firefox';
        } else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
            return 'Safari';
        } else if (userAgent.includes('Edg')) {
            return 'Edge';
        }
        return 'مرورگر';
    },

    _getBrowserInstructions(browser) {
        const instructions = {
            'Chrome': `
                <div class="text-right">
                    <p class="mb-2">در مرورگر Chrome:</p>
                    <ol class="text-sm text-gray-600 mb-3" style="direction: rtl; text-align: right;">
                        <li>1. روی آیکون قفل (🔒) در نوار آدرس کلیک کنید</li>
                        <li>2. گزینه "Site settings" را انتخاب کنید</li>
                        <li>3. بخش "Notifications" را پیدا کنید</li>
                        <li>4. روی "Allow" کلیک کنید</li>
                    </ol>
                    <p class="text-xs text-gray-500">یا در تنظیمات مرورگر: chrome://settings/content/notifications</p>
                </div>
            `,
            'Firefox': `
                <div class="text-right">
                    <p class="mb-2">در مرورگر Firefox:</p>
                    <ol class="text-sm text-gray-600 mb-3" style="direction: rtl; text-align: right;">
                        <li>1. روی آیکون قفل (🔒) در نوار آدرس کلیک کنید</li>
                        <li>2. گزینه "Allow notifications" را فعال کنید</li>
                    </ol>
                    <p class="text-xs text-gray-500">یا در تنظیمات: about:preferences#privacy</p>
                </div>
            `,
            'Edge': `
                <div class="text-right">
                    <p class="mb-2">در مرورگر Edge:</p>
                    <ol class="text-sm text-gray-600 mb-3" style="direction: rtl; text-align: right;">
                        <li>1. روی آیکون قفل (🔒) در نوار آدرس کلیک کنید</li>
                        <li>2. گزینه "Site permissions" را انتخاب کنید</li>
                        <li>3. بخش "Notifications" را پیدا کنید</li>
                        <li>4. روی "Allow" کلیک کنید</li>
                    </ol>
                </div>
            `,
            'Safari': `
                <div class="text-right">
                    <p class="mb-2">در مرورگر Safari:</p>
                    <ol class="text-sm text-gray-600 mb-3" style="direction: rtl; text-align: right;">
                        <li>1. Safari → Preferences را انتخاب کنید</li>
                        <li>2. تب Websites را انتخاب کنید</li>
                        <li>3. از منوی سمت چپ "Notifications" را انتخاب کنید</li>
                        <li>4. این سایت را پیدا کرده و روی "Allow" کلیک کنید</li>
                    </ol>
                </div>
            `
        };

        return instructions[browser] || `
            <div class="text-right">
                <p class="mb-2">در مرورگر شما:</p>
                <ol class="text-sm text-gray-600" style="direction: rtl; text-align: right;">
                    <li>1. روی آیکون قفل در نوار آدرس کلیک کنید</li>
                    <li>2. تنظیمات نوتیفیکیشن را پیدا کنید</li>
                    <li>3. دسترسی را فعال کنید</li>
                </ol>
            </div>
        `;
    },

    _updateNotificationStatus() {
        const statusEl = document.getElementById('browser-notif-status');
        if (!statusEl) return;

        let status = 'نامشخص';
        let color = 'text-gray-500';

        if (!('Notification' in window)) {
            status = 'مرورگر پشتیبانی نمی‌کند';
            color = 'text-red-500';
        } else {
            switch (Notification.permission) {
                case 'granted':
                    status = '✅ فعال';
                    color = 'text-green-500';
                    break;
                case 'denied':
                    status = '❌ بلاک شده';
                    color = 'text-red-500';
                    break;
                case 'default':
                    status = '⏳ نیاز به اجازه';
                    color = 'text-yellow-500';
                    break;
            }
        }

        statusEl.innerHTML = `<span class="${color} text-sm">${status}</span>`;
    },

    async requestNotificationPermission() {
        if (!('Notification' in window)) {
            console.log('Notifications not supported');
            return 'denied';
        }

        if (Notification.permission === 'granted') {
            return 'granted';
        }

        if (Notification.permission === 'denied') {
            return 'denied';
        }

            try {
                console.log('Requesting permission...');
                const permission = await Notification.requestPermission();
                console.log('Permission result:', permission);

                if (permission === 'granted') {
                    console.log('✅ Browser notification permission granted');
                    Swal.fire({
                        toast: true,
                        position: 'bottom-end',
                        icon: 'success',
                        title: 'دسترسی نوتیفیکیشن فعال شد',
                        timer: 2000,
                        showConfirmButton: false
                    });
                } else if (permission === 'denied') {
                    console.log('❌ Browser notification permission denied');
                    Swal.fire({
                        toast: true,
                        position: 'bottom-end',
                        icon: 'error',
                        title: 'دسترسی نوتیفیکیشن رد شد',
                        timer: 3000,
                        showConfirmButton: false
                    });
                } else {
                    console.log('⚠️ Browser notification permission default');
                }

                // Update status display if settings modal is open
                this._updateNotificationStatus();

                return permission;
            } catch (error) {
                console.error('❌ Error requesting notification permission:', error);
                return 'denied';
            }
    },

    // REMINDER CALCULATION FUNCTIONS
    calculateReminderOffset(form) {
        const hours = parseInt(form.reminder_offset_hours?.value || '24') || 0;
        const minutes = parseInt(form.reminder_offset_minutes?.value || '0') || 0;
        const totalMinutes = (hours * 60) + minutes;
        return totalMinutes > 0 ? totalMinutes : 1440; // Default to 24 hours if invalid
    },

    updateReminderOffsetTotal(formPrefix = '') {
        const hoursSelect = document.getElementById(`${formPrefix}activity-reminder-hours`);
        const minutesSelect = document.getElementById(`${formPrefix}activity-reminder-minutes`);
        const totalInput = document.getElementById(`${formPrefix}reminder-offset-total`);

        if (hoursSelect && minutesSelect && totalInput) {
            const hours = parseInt(hoursSelect.value) || 0;
            const minutes = parseInt(minutesSelect.value) || 0;
            const totalMinutes = (hours * 60) + minutes;
            totalInput.value = totalMinutes > 0 ? totalMinutes : 1440;
        }
    },

    initReminderOffsetListeners() {
        // Create activity reminder listeners
        const createHours = document.getElementById('activity-reminder-hours');
        const createMinutes = document.getElementById('activity-reminder-minutes');

        if (createHours) {
            createHours.addEventListener('change', () => this.updateReminderOffsetTotal(''));
        }
        if (createMinutes) {
            createMinutes.addEventListener('change', () => this.updateReminderOffsetTotal(''));
        }

        // Edit activity reminder listeners
        const editHours = document.getElementById('edit-activity-reminder-hours');
        const editMinutes = document.getElementById('edit-activity-reminder-minutes');

        if (editHours) {
            editHours.addEventListener('change', () => this.updateReminderOffsetTotal('edit-'));
        }
        if (editMinutes) {
            editMinutes.addEventListener('change', () => this.updateReminderOffsetTotal('edit-'));
        }
    },

    // ACTIVITY SCHEDULE FUNCTIONS
    toggleActivityScheduleFields() {
        const checkbox = document.getElementById('activity-is-scheduled');
        const fields = document.getElementById('activity-schedule-fields');
        const virtualStageSelect = document.getElementById('activity-virtual-stage');
        const resultNoteTextarea = document.getElementById('activity-result-note');
        const virtualStageRequired = document.getElementById('virtual-stage-required');
        
        if (!checkbox || !fields) return;
        
        if (checkbox.checked) {
            fields.classList.remove('hidden');
            // Initialize date picker
            this.initActivityDatePicker();
            
            // Make result_note optional for scheduled activities
            if (virtualStageSelect) {
                virtualStageSelect.removeAttribute('required');
            }
            if (resultNoteTextarea) {
                resultNoteTextarea.removeAttribute('required');
            }
            if (virtualStageRequired) {
                virtualStageRequired.classList.add('hidden');
            }
        } else {
            fields.classList.add('hidden');
            
            // Make result_note required again
            if (virtualStageSelect) {
                virtualStageSelect.setAttribute('required', 'required');
            }
            if (resultNoteTextarea) {
                resultNoteTextarea.setAttribute('required', 'required');
            }
            if (virtualStageRequired) {
                virtualStageRequired.classList.remove('hidden');
            }
        }
    },
    
    toggleActivityReminderFields() {
        const checkbox = document.getElementById('activity-reminder-enabled');
        const fields = document.getElementById('activity-reminder-fields');
        if (!checkbox || !fields) return;

        if (checkbox.checked) {
            fields.classList.remove('hidden');
            // Set default values if not already set
            const hoursSelect = document.getElementById('activity-reminder-hours');
            const minutesSelect = document.getElementById('activity-reminder-minutes');
            if (hoursSelect && hoursSelect.value === '') {
                hoursSelect.value = '24'; // 1 day default
            }
            if (minutesSelect && minutesSelect.value === '') {
                minutesSelect.value = '0';
            }
            // Update total
            this.updateReminderOffsetTotal('');
        } else {
            fields.classList.add('hidden');
        }
    },
    
    initActivityDatePicker() {
        const dateInput = document.getElementById('activity-due-date');
        const hourSelect = document.getElementById('activity-due-hour');
        const minuteSelect = document.getElementById('activity-due-minute');
        
        if (!dateInput) return;
        
        // Initialize hour and minute selects
        if (hourSelect) {
            hourSelect.innerHTML = '<option value="">ساعت</option>';
            for (let h = 0; h < 24; h++) {
                const hour = String(h).padStart(2, '0');
                const hourLabel = h < 12 ? `${hour} صبح` : `${hour} بعدازظهر`;
                hourSelect.innerHTML += `<option value="${hour}">${hourLabel}</option>`;
            }
            // Set default to current hour + 1 (next hour)
            const now = getTehranNow();
            const nextHour = (now.getHours() + 1) % 24;
            hourSelect.value = String(nextHour).padStart(2, '0');
        }
        
        if (minuteSelect) {
            minuteSelect.innerHTML = '<option value="">دقیقه</option>';
            for (let m = 0; m < 60; m += 5) {
                const minute = String(m).padStart(2, '0');
                minuteSelect.innerHTML += `<option value="${minute}">${minute}</option>`;
            }
            // Set default to 00
            minuteSelect.value = '00';
        }
        
        // Wait for jQuery and persianDatepicker
        if (typeof $ === 'undefined' || typeof $.fn.persianDatepicker === 'undefined') {
            setTimeout(() => this.initActivityDatePicker(), 200);
            return;
        }
        
        // Destroy existing picker
        try {
            if ($(dateInput).data('persianDatepicker')) {
                $(dateInput).persianDatepicker('destroy');
            }
        } catch(e) {
            console.log('No existing picker to destroy');
        }
        
        // Get today's date in Tehran timezone using moment-jalaali (accurate conversion)
        let todayFormatted = '';
        
        if (typeof moment !== 'undefined') {
            const testMoment = moment();
            if (typeof testMoment.jYear === 'function') {
                // Use moment-jalaali for accurate date conversion (fixes leap year bug)
                const now = new Date();
                const formatter = new Intl.DateTimeFormat('en-CA', {
                    timeZone: 'Asia/Tehran',
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit'
                });
                const parts = formatter.formatToParts(now);
                const gYear = parseInt(parts.find(p => p.type === 'year').value);
                const gMonth = parseInt(parts.find(p => p.type === 'month').value);
                const gDay = parseInt(parts.find(p => p.type === 'day').value);
                
                const gregorianMoment = moment(`${gYear}-${String(gMonth).padStart(2, '0')}-${String(gDay).padStart(2, '0')}`, 'YYYY-MM-DD');
                todayFormatted = gregorianMoment.format('jYYYY/jMM/jDD').replace(/\//g, '/');
            } else {
                // Fallback to persianDate
                const todayTehran = getTehranPersianDate();
                if (todayTehran) {
                    todayFormatted = todayTehran.format('YYYY/MM/DD');
                }
            }
        }
        
        // Set default value to today if input is empty
        if (!dateInput.value || dateInput.value.trim() === '') {
            dateInput.value = todayFormatted;
        }
        
        // Initialize Persian date picker with better styling
        try {
            $(dateInput).persianDatepicker({
                observer: true,
                format: 'YYYY/MM/DD',
                altField: '#activity-due-date-gregorian',
                altFormat: 'YYYY-MM-DD',
                calendarType: 'persian',
                timePicker: {
                    enabled: false  // We'll use separate hour/minute selects
                },
                initialValue: dateInput.value || todayFormatted, // Use current value or today
                autoClose: true,
                toolbox: {
                    calendarSwitch: {
                        enabled: false
                    }
                },
                onlyTimePicker: false,
                onlySelectOnDate: true,
                calendar: {
                    persian: {
                        locale: 'fa',
                        dow: [0, 1, 2, 3, 4, 5, 6]  // Start week from Saturday (0=Saturday in persianDate)
                    }
                },
                onSelect: function(unixDate) {
                    // Update the input with formatted date
                    const pDate = new persianDate(unixDate);
                    dateInput.value = pDate.format('YYYY/MM/DD');
                }
            });
            this.syncActivityDatePickerToTehran();
            
            // Apply custom styles to the date picker
            setTimeout(() => {
                const pickerContainer = $('.pdp-container');
                if (pickerContainer.length) {
                    pickerContainer.css({
                        'background': 'linear-gradient(135deg, #1e293b 0%, #0f172a 100%)',
                        'border': '1px solid rgba(59, 130, 246, 0.3)',
                        'border-radius': '12px',
                        'box-shadow': '0 10px 40px rgba(0, 0, 0, 0.5)',
                        'padding': '15px',
                        'z-index': '10000'
                    });
                    
                    // Style calendar cells
                    pickerContainer.find('.pdp-day').css({
                        'color': '#e2e8f0',
                        'border-radius': '6px',
                        'transition': 'all 0.2s'
                    });
                    
                    pickerContainer.find('.pdp-day-selected').css({
                        'background': 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
                        'color': '#fff',
                        'font-weight': 'bold',
                        'box-shadow': '0 2px 8px rgba(59, 130, 246, 0.4)'
                    });
                    
                    pickerContainer.find('.pdp-day-today').css({
                        'border': '2px solid #3b82f6',
                        'font-weight': 'bold'
                    });
                    
                    pickerContainer.find('.pdp-day:hover').css({
                        'background': 'rgba(59, 130, 246, 0.2)',
                        'transform': 'scale(1.05)'
                    });
                }
            }, 100);
            
        } catch(e) {
            console.error('Error initializing date picker:', e);
        }
    },

    syncActivityDatePickerToTehran() {
        const dateInput = document.getElementById('activity-due-date');
        if (!dateInput) return;
        if (typeof $ === 'undefined' || typeof $.fn.persianDatepicker === 'undefined') return;

        try {
            const tehranTime = getTehranNow().valueOf();
            $(dateInput).persianDatepicker('setDate', tehranTime);
        } catch (error) {
            console.error('Error syncing activity date picker to Tehran time:', error);
        }
    },
    
    // EDIT SCHEDULED ACTIVITY FUNCTIONS
    async openEditScheduledActivityModal(activityId) {
        try {
            // Get activity details from Didar API
            const res = await this.req('get_activity_details', { activity_id: activityId });
            if (res.status !== 'success' || !res.activity) {
                Swal.fire('خطا', 'فعالیت یافت نشد', 'error');
                return;
            }
            
            const activity = res.activity;
            
            // Fill form
            document.getElementById('edit-activity-id').value = activityId;
            document.getElementById('edit-activity-contact-id').value = activity.ContactIds?.[0] || activity.contact_didar_id || '';
            
            // Load activity types
            await this.loadActivityTypesForEditModal();
            
            // Set activity type
            const typeSelect = document.getElementById('edit-activity-type-id');
            if (typeSelect && activity.ActivityTypeId) {
                typeSelect.value = activity.ActivityTypeId;
            }
            
            // Set due date if exists
            if (activity.DueDate) {
                const dueDate = new Date(activity.DueDate);
                const pDate = new persianDate(dueDate);
                const dateStr = pDate.format('YYYY/MM/DD');
                const hour = String(dueDate.getHours()).padStart(2, '0');
                const minute = String(dueDate.getMinutes()).padStart(2, '0');
                
                document.getElementById('edit-activity-due-date').value = dateStr;
                document.getElementById('edit-activity-due-hour').value = hour;
                document.getElementById('edit-activity-due-minute').value = minute;
                
                // Initialize date picker (will update with current date)
                setTimeout(() => {
                    this.initEditActivityDatePicker();
                }, 100);
            } else {
                // If no due date, initialize with today's date
                setTimeout(() => {
                    this.initEditActivityDatePicker();
                }, 100);
            }
            
            // Set reminder
            if (activity.Notifies && activity.Notifies.length > 0) {
                document.getElementById('edit-activity-reminder-enabled').checked = true;
                this.toggleEditActivityReminderFields();
                const offset = activity.Notifies[0].NotifyOffset || 1440;

                // Convert minutes to hours and minutes
                const hours = Math.floor(offset / 60);
                const minutes = offset % 60;

                document.getElementById('edit-activity-reminder-hours').value = hours;
                document.getElementById('edit-activity-reminder-minutes').value = minutes;
                document.getElementById('edit-reminder-offset-total').value = offset;
            }
            
            this.openModal('modalEditScheduledActivity');
        } catch (error) {
            console.error('Error opening edit modal:', error);
            Swal.fire('خطا', 'خطا در بارگذاری اطلاعات فعالیت', 'error');
        }
    },
    
    async loadActivityTypesForEditModal() {
        try {
            const res = await this.req('get_activity_types', {});
            if (res.status === 'success' && res.types) {
                const select = document.getElementById('edit-activity-type-id');
                if (select) {
                    select.innerHTML = '<option value="">-- انتخاب نوع فعالیت --</option>';
                    res.types.forEach(type => {
                        const option = document.createElement('option');
                        option.value = type.Id || type.ActivityTypeId;
                        option.textContent = type.Title || 'فعالیت';
                        select.appendChild(option);
                    });
                }
            }
        } catch (error) {
            console.error('Error loading activity types:', error);
        }
    },
    
    toggleEditActivityReminderFields() {
        const checkbox = document.getElementById('edit-activity-reminder-enabled');
        const fields = document.getElementById('edit-activity-reminder-fields');
        if (!checkbox || !fields) return;

        if (checkbox.checked) {
            fields.classList.remove('hidden');
            // Set default values if not already set
            const hoursSelect = document.getElementById('edit-activity-reminder-hours');
            const minutesSelect = document.getElementById('edit-activity-reminder-minutes');
            if (hoursSelect && hoursSelect.value === '') {
                hoursSelect.value = '24'; // 1 day default
            }
            if (minutesSelect && minutesSelect.value === '') {
                minutesSelect.value = '0';
            }
            // Update total
            this.updateReminderOffsetTotal('edit-');
        } else {
            fields.classList.add('hidden');
        }
    },
    
    initEditActivityDatePicker() {
        const dateInput = document.getElementById('edit-activity-due-date');
        const hourSelect = document.getElementById('edit-activity-due-hour');
        const minuteSelect = document.getElementById('edit-activity-due-minute');
        
        if (!dateInput) return;
        
        // Initialize hour and minute selects
        if (hourSelect) {
            hourSelect.innerHTML = '<option value="">ساعت</option>';
            for (let h = 0; h < 24; h++) {
                const hour = String(h).padStart(2, '0');
                const hourLabel = h < 12 ? `${hour} صبح` : `${hour} بعدازظهر`;
                hourSelect.innerHTML += `<option value="${hour}">${hourLabel}</option>`;
            }
        }
        
        if (minuteSelect) {
            minuteSelect.innerHTML = '<option value="">دقیقه</option>';
            for (let m = 0; m < 60; m += 5) {
                const minute = String(m).padStart(2, '0');
                minuteSelect.innerHTML += `<option value="${minute}">${minute}</option>`;
            }
        }
        
        // Wait for jQuery and persianDatepicker
        if (typeof $ === 'undefined' || typeof $.fn.persianDatepicker === 'undefined') {
            setTimeout(() => this.initEditActivityDatePicker(), 200);
            return;
        }
        
        // Destroy existing picker
        try {
            if ($(dateInput).data('persianDatepicker')) {
                $(dateInput).persianDatepicker('destroy');
            }
        } catch(e) {
            console.log('No existing picker to destroy');
        }
        
        // Get today's date in Tehran timezone (as fallback if no value set)
        const todayTehran = getTehranPersianDate();
        const todayFormatted = todayTehran.format('YYYY/MM/DD');
        
        // Initialize Persian date picker
        try {
            $(dateInput).persianDatepicker({
                observer: true,
                format: 'YYYY/MM/DD',
                altField: '#edit-activity-due-date-gregorian',
                altFormat: 'YYYY-MM-DD',
                calendarType: 'persian',
                timePicker: {
                    enabled: false
                },
                initialValue: dateInput.value || todayFormatted, // Use current value or today
                autoClose: true,
                calendar: {
                    persian: {
                        locale: 'fa',
                        dow: [0, 1, 2, 3, 4, 5, 6]  // Start week from Saturday (0=Saturday in persianDate)
                    }
                },
                onSelect: function(unixDate) {
                    const pDate = new persianDate(unixDate);
                    dateInput.value = pDate.format('YYYY/MM/DD');
                }
            });
        } catch(e) {
            console.error('Error initializing date picker:', e);
        }
    },
    
    async updateScheduledActivity(e) {
        e.preventDefault();
        
        try {
            const form = e.target;
            const activityId = form.activity_id.value;
            const activityTypeId = form.activity_type_id.value;
            const dueDateInput = form.due_date.value;
            const hour = document.getElementById('edit-activity-due-hour').value;
            const minute = document.getElementById('edit-activity-due-minute').value;
            
            if (!activityId || !activityTypeId) {
                Swal.fire('خطا', 'نوع فعالیت الزامی است', 'error');
                return;
            }
            
            if (!dueDateInput || !hour || !minute) {
                Swal.fire('خطا', 'لطفاً تاریخ و زمان را کامل وارد کنید', 'error');
                return;
            }
            
            // Convert Persian date to Gregorian
            const persianToEnglish = (str) => {
                const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
                const englishDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
                let result = str;
                for (let i = 0; i < persianDigits.length; i++) {
                    result = result.replace(new RegExp(persianDigits[i], 'g'), englishDigits[i]);
                }
                return result;
            };
            
            const englishDateValue = persianToEnglish(dueDateInput.trim());
            const timeString = `${hour}:${minute}`;
            let dueDate = persianToGregorian(englishDateValue, timeString);
            
            if (!dueDate) {
                Swal.fire('خطا', 'تاریخ وارد شده معتبر نیست', 'error');
                return;
            }
            
            dueDate = dueDate.replace('T', ' ') + ':00';
            
            const data = {
                activity_id: activityId,
                activity_type_id: activityTypeId,
                due_date: dueDate,
                reminder_enabled: form.reminder_enabled.checked ? '1' : '0',
                reminder_offset_minutes: this.calculateReminderOffset(form)
            };
            
            Swal.showLoading();
            const res = await this.req('update_activity', data);
            this.closeBlockingSwal();
            
            if (res.status === 'success') {
                Swal.fire({toast:true, position:'bottom-end', icon:'success', title:'فعالیت با موفقیت به‌روزرسانی شد', showConfirmButton:false, timer:2000});
                this.closeModal('modalEditScheduledActivity');
                
                // Reload activities if we're viewing a person profile
                if (window.currentContactId) {
                    await this.openPersonProfile(window.currentContactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطای نامشخص', 'error');
            }
        } catch (error) {
            console.error('Error updating activity:', error);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در به‌روزرسانی فعالیت: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    // OPEN ACTIVITY DETAILS MODAL
    async openActivityDetailsModal(activityId) {
        if (!activityId) {
            console.error('Activity ID is required');
            return;
        }
        
        try {
            Swal.fire({
                title: 'در حال بارگذاری...',
                allowOutsideClick: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });
            
            const res = await this.req('get_activity_details', {activity_id: activityId});
            
            if (res.status === 'success' && res.activity) {
                const act = res.activity;
                const isDone = act.IsDone === true || act.IsDone === 1;
                const isNote = act.ActivityTypeId === '00000000-0000-0000-0000-000000000000';
                const activityType = act.ActivityType?.Title || act.ActivityTypeTitle || (isNote ? 'یادداشت' : 'فعالیت');
                const creator = act.Creator?.DisplayName || act.CreatorName || 'نامشخص';
                const owner = act.Owner?.DisplayName || act.OwnerName || 'نامشخص';
                const note = act.Note || '';
                const resultNote = act.ResultNote || '';
                const registerDate = act.RegisterDate ? toPersianDateTime(act.RegisterDate) : '';
                const doneDate = act.DoneDate ? toPersianDateTime(act.DoneDate) : '';
                
                // Check for due date
                let dueDate = '';
                if (act.DueDate) {
                    try {
                        dueDate = toPersianDateTime(act.DueDate);
                    } catch (e) {
                        dueDate = act.DueDate;
                    }
                } else if (act.due_date) {
                    try {
                        dueDate = toPersianDateTime(act.due_date);
                    } catch (e) {
                        dueDate = act.due_date;
                    }
                }
                
                const hasDueDate = dueDate && dueDate.trim() !== '';
                const isScheduledFlag = act.is_scheduled === 1 || act.is_scheduled === true || act.is_scheduled === '1';
                const isScheduled = isScheduledFlag || (!isDone && hasDueDate);
                
                let statusText = '';
                if (isDone) {
                    statusText = '<span class="text-green-300">✓ انجام شده</span>';
                } else if (isScheduled) {
                    statusText = '<span class="text-yellow-300">⏰ برنامه‌ریزی شده</span>';
                } else {
                    statusText = '<span class="text-blue-300">📝 ثبت شده</span>';
                }
                
                Swal.fire({
                    title: 'جزئیات فعالیت',
                    html: `
                        <div class="text-right space-y-3">
                            <div class="border-b border-gray-700 pb-2">
                                <div class="flex items-center justify-between mb-2">
                                    <span class="text-lg font-bold text-blue-300">
                                        <i class="fa-solid fa-${isNote ? 'sticky-note' : (isDone ? 'check-circle' : isScheduled ? 'clock' : 'calendar-check')} ml-1"></i>
                                        ${this.escapeHtml(activityType)}
                                    </span>
                                    ${statusText}
                                </div>
                                <div class="text-sm text-gray-400">${this.escapeHtml(act.Title || 'بدون عنوان')}</div>
                            </div>
                            
                            <div class="space-y-2 text-sm">
                                ${registerDate ? `<div><span class="text-gray-400">تاریخ ثبت:</span> <span class="text-gray-300">${registerDate}</span></div>` : ''}
                                ${dueDate && isScheduled ? `<div><span class="text-gray-400">تاریخ/ساعت برنامه‌ریزی:</span> <span class="text-yellow-300">${dueDate}</span></div>` : ''}
                                ${doneDate && isDone ? `<div><span class="text-gray-400">تاریخ انجام:</span> <span class="text-green-300">${doneDate}</span></div>` : ''}
                                <div><span class="text-gray-400">ایجاد کننده:</span> <span class="text-gray-300">${this.escapeHtml(creator)}</span></div>
                                ${owner !== creator ? `<div><span class="text-gray-400">مسئول:</span> <span class="text-gray-300">${this.escapeHtml(owner)}</span></div>` : ''}
                            </div>
                            
                            ${note ? `
                                <div class="border-t border-gray-700 pt-2">
                                    <div class="text-sm text-gray-400 mb-1">یادداشت:</div>
                                    <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(note))}</div>
                                </div>
                            ` : ''}
                            
                            ${resultNote ? `
                                <div class="border-t border-gray-700 pt-2">
                                    <div class="text-sm text-gray-400 mb-1">نتیجه:</div>
                                    <div class="text-sm text-gray-300 bg-gray-800/50 p-2 rounded whitespace-pre-wrap">${this.escapeHtml(this.stripHtml(resultNote))}</div>
                                </div>
                            ` : ''}
                            
                            ${!note && !resultNote ? `
                                <div class="text-sm text-gray-500 italic text-center py-2">بدون توضیحات</div>
                            ` : ''}
                        </div>
                    `,
                    width: '600px',
                    confirmButtonText: 'بستن',
                    customClass: {
                        popup: 'swal2-popup-dark',
                        htmlContainer: 'text-right'
                    }
                });
            } else {
                Swal.fire('خطا', res.message || 'فعالیت یافت نشد', 'error');
            }
        } catch (error) {
            console.error('Error loading activity details:', error);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در دریافت جزئیات فعالیت: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    // MANUAL POLLING TEST (Admin only)
    async testPolling() {
        if (!this.canCurrentUserCapabilityClient('notifications.admin.test')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند polling را تست کند', 'error');
            return;
        }

        try {
            Swal.fire({
                title: 'در حال چک کردن یادآوری‌ها...',
                allowOutsideClick: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });

            const res = await this.req('check_reminders', {});

            this.closeBlockingSwal();

            if (res.status === 'success') {
                let message = 'سیستم polling چک شد\n';
                message += `🔔 نوتیفیکیشن‌های جدید: ${res.new_notifications ? res.new_notifications.length : 0}\n`;
                message += `🗯️ پاپ‌آپ نوتیفیکیشن‌ها: ${res.popup_notifications ? res.popup_notifications.length : 0}\n`;
                message += `📊 تعداد نوتیفیکیشن‌های خوانده نشده: ${res.unread_count || 0}`;

                Swal.fire({
                    icon: 'info',
                    title: 'نتیجه تست polling',
                    text: message,
                    confirmButtonText: 'باشه'
                });

                // Show any popup notifications found
                if (res.popup_notifications && res.popup_notifications.length > 0) {
                    res.popup_notifications.forEach(notif => {
                        this.showNotificationPopup(notif);
                    });
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در چک کردن یادآوری‌ها', 'error');
            }
        } catch (error) {
            console.error('Error testing polling:', error);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در تست polling', 'error');
        }
    },

    // TEST POPUP DIRECTLY (Admin only)
    testPopupDirect() {
        if (!this.canCurrentUserCapabilityClient('notifications.admin.test')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند پاپ‌آپ را تست کند', 'error');
            return;
        }

        console.log('Testing popup directly');
        const testNotif = {
            id: 'test-' + Date.now(),
            title: 'تست کامل نوتیفیکیشن',
            message: 'این تست شامل پاپ‌آپ، صدا و نوتیفیکیشن مرورگر است',
            type: 'test',
            show_popup: 1,
            created_at: new Date().toISOString()
        };
        console.log('Test notification object:', testNotif);

        // Test all notification methods
        this.showNotificationPopup(testNotif);
        this.showBrowserNotification(testNotif);
        this.playNotificationSound();
    },

    // TEST BROWSER NOTIFICATION ONLY (Admin only)
    testBrowserNotification() {
        if (!this.canCurrentUserCapabilityClient('notifications.admin.test')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند نوتیفیکیشن مرورگر را تست کند', 'error');
            return;
        }

        console.log('Testing browser notification only');
        const testNotif = {
            id: 'browser-test-' + Date.now(),
            title: 'تست نوتیفیکیشن مرورگر',
            message: 'این نوتیفیکیشن باید روی دسکتاپ نمایش داده شود',
            type: 'browser_test',
            created_at: new Date().toISOString()
        };

        this.showBrowserNotification(testNotif);
    },

    // TEST NOTIFICATION (Admin only)
    async testNotification() {
        if (!this.canCurrentUserCapabilityClient('notifications.admin.test')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند اعلان را تست کند', 'error');
            return;
        }
        
        try {
            Swal.fire({
                title: 'در حال ایجاد اعلان تستی...',
                allowOutsideClick: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });
            
            const res = await this.req('test_notification', {});
            
            if (res.status === 'success') {
                Swal.fire({
                    icon: 'success',
                    title: 'موفق',
                    text: 'اعلان تستی با موفقیت ایجاد شد. پاپ‌آپ را بررسی کنید.',
                    confirmButtonText: 'باشه'
                });

                // Refresh notifications and show popup
                await this.loadNotifications();

                // Show popup notification immediately for testing
                if (res.notification) {
                    const testNotif = {
                        id: res.notification.id,
                        title: res.notification.title,
                        message: res.notification.message,
                        type: res.notification.type,
                        entity_type: res.notification.entity_type,
                        entity_id: res.notification.entity_id,
                        created_at: res.notification.created_at,
                        show_popup: 1
                    };

                    // Show popup
                    this.showNotificationPopup(testNotif);

                    // Show browser notification
                    this.showBrowserNotification(testNotif);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در ایجاد اعلان تستی', 'error');
            }
        } catch (error) {
            console.error('Error testing notification:', error);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در ایجاد اعلان تستی: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },
    
    // SYNC External CRM users (Admin only)
    async syncDidarUsers() {
        if (!this.isDidarUiEnabled()) {
            this.showDidarUiDisabledMessage();
            return;
        }
        if (!this.canCurrentUserCapabilityClient('users.sync_didar')) {
            Swal.fire('خطا', 'فقط مدیر سیستم می‌تواند کاربران را همگام‌سازی کند', 'error');
            return;
        }
        
        try {
            const result = await Swal.fire({
                title: 'همگام‌سازی کاربران',
                text: 'آیا می‌خواهید کاربران خارجی همگام‌سازی شوند؟ این عملیات باعث بروزرسانی شناسه‌های کاربران می‌شود.',
                icon: 'question',
                showCancelButton: true,
                confirmButtonText: 'بله، همگام‌سازی کن',
                cancelButtonText: 'انصراف'
            });
            
            if (!result.isConfirmed) return;
            
            Swal.fire({
                title: 'در حال همگام‌سازی...',
                allowOutsideClick: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });
            
            const res = await this.req('sync_didar_users', {});
            
            if (res.status === 'success') {
                Swal.fire({
                    icon: 'success',
                    title: 'موفق',
                    html: `
                        <div class="text-right">
                            <p>کاربران با موفقیت همگام‌سازی شدند</p>
                            <div class="mt-3 space-y-1 text-sm">
                                <p>جمع کل: <strong>${res.total_didar_users}</strong></p>
                                <p>به‌روزرسانی شده: <strong class="text-green-600">${res.updated}</strong></p>
                                <p>یافت نشده: <strong class="text-yellow-600">${res.not_found}</strong></p>
                            </div>
                        </div>
                    `,
                    confirmButtonText: 'باشه'
                });
            } else {
                Swal.fire('خطا', res.message || 'خطا در همگام‌سازی کاربران', 'error');
            }
        } catch (error) {
            console.error('Error syncing External CRM users:', error);
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در همگام‌سازی کاربران: ' + (error.message || 'خطای نامشخص'), 'error');
        }
    },

    // IMPORT LEADS FUNCTIONS
    importLeadsState: {
        file: null,
        preview: null,
        columnMapping: null
    },

    openImportLeadsModal() {
        if (!this.canCurrentUserCapabilityClient('imports.admin.leads')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند لیدها را وارد کند', 'error');
            return;
        }
        this.openModal('importLeadsModal');
        document.getElementById('import-step-1').classList.remove('hidden');
        document.getElementById('import-step-2').classList.add('hidden');
        document.getElementById('import-step-3').classList.add('hidden');
        document.getElementById('import-btn-preview').classList.add('hidden');
        document.getElementById('import-btn-execute').classList.add('hidden');
        document.getElementById('import-btn-done').classList.add('hidden');
        document.getElementById('import-file-selected').classList.add('hidden');
        document.getElementById('import-file-input').value = '';
        this.importLeadsState = { file: null, preview: null, columnMapping: null };
    },

    downloadImportLeadsTemplate() {
        if (!this.canCurrentUserCapabilityClient('imports.admin.leads')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند فایل نمونه را دانلود کند', 'error');
            return;
        }
        window.open('?api=download_leads_import_template', '_blank');
    },

    handleImportFileSelect(event) {
        const file = event.target.files?.[0];
        if (!file) return;

        const ext = (file.name.split('.').pop() || '').toLowerCase();
        if (!['xlsx', 'xls', 'csv'].includes(ext)) {
            Swal.fire('خطا', 'فقط فایل‌های Excel یا CSV قابل قبول هستند', 'error');
            return;
        }

        if (file.size > 5 * 1024 * 1024) {
            Swal.fire('خطا', 'حجم فایل نباید بیشتر از 5 مگابایت باشد', 'error');
            return;
        }

        this.importLeadsState.file = file;
        document.getElementById('import-file-name').textContent = file.name;
        document.getElementById('import-file-selected').classList.remove('hidden');
        document.getElementById('import-btn-preview').classList.remove('hidden');

        Swal.fire({
            toast: true,
            position: 'bottom-end',
            icon: 'success',
            title: 'فایل انتخاب شد',
            timer: 2000,
            showConfirmButton: false
        });
    },

    async previewImportLeads() {
        if (!this.canCurrentUserCapabilityClient('imports.admin.leads')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند لیدها را وارد کند', 'error');
            return;
        }
        if (!this.importLeadsState.file) {
            Swal.fire('خطا', 'فایلی انتخاب نشده است', 'error');
            return;
        }

        const formData = new FormData();
        formData.append('file', this.importLeadsState.file);

        Swal.fire({
            title: 'در حال بررسی فایل...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await fetch('?api=import_leads_preview', {
                method: 'POST',
                body: formData
            });

            let data;
            try {
                data = await res.json();
            } catch (_) {
                const raw = await res.text();
                throw new Error(raw || 'پاسخ نامعتبر از سرور دریافت شد');
            }
            this.closeBlockingSwal();

            if (data.status !== 'success') {
                Swal.fire('خطا', data.message || 'خطای نامشخص', 'error');
                return;
            }

            this.importLeadsState.preview = data;
            this.renderImportPreview(data);

            document.getElementById('import-step-1').classList.add('hidden');
            document.getElementById('import-step-2').classList.remove('hidden');
            document.getElementById('import-btn-preview').classList.add('hidden');
            document.getElementById('import-btn-execute').classList.remove('hidden');

        } catch (error) {
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در بررسی فایل: ' + error.message, 'error');
        }
    },

    renderImportPreview(data) {
        document.getElementById('import-preview-success').textContent = data.valid_count || 0;
        document.getElementById('import-preview-error').textContent = data.error_count || 0;

        const tbody = document.getElementById('import-preview-table');
        tbody.innerHTML = (data.preview || []).map((row) => {
            const hasError = row.errors && row.errors.length > 0;
            return `
                <tr class="${hasError ? 'bg-red-900/20' : 'bg-green-900/20'}">
                    <td class="border border-gray-600 p-2">${row.row_number}</td>
                    <td class="border border-gray-600 p-2">${this.escapeHtml(row.mobile_phone || '')}</td>
                    <td class="border border-gray-600 p-2 text-xs">${this.escapeHtml((row.owner_username || '').substring(0, 24))}</td>
                    <td class="border border-gray-600 p-2 text-xs">
                        ${hasError ? `<span class="text-red-400">${this.escapeHtml(row.errors[0])}</span>` : '<span class="text-green-400">✓</span>'}
                    </td>
                </tr>
            `;
        }).join('');
    },

    async executeImportLeads() {
        if (!this.canCurrentUserCapabilityClient('imports.admin.leads')) {
            Swal.fire('خطا', 'فقط مدیر می‌تواند لیدها را وارد کند', 'error');
            return;
        }
        if (!this.importLeadsState.file) {
            Swal.fire('خطا', 'فایلی انتخاب نشده است', 'error');
            return;
        }

        const result = await Swal.fire({
            title: 'آیا مطمئن هستید؟',
            text: `${this.importLeadsState.preview?.valid_count || 0} لید وارد خواهد شد`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، ادامه بده',
            cancelButtonText: 'لغو'
        });

        if (!result.isConfirmed) return;

        const formData = new FormData();
        formData.append('file', this.importLeadsState.file);

        Swal.fire({
            title: 'در حال وارد کردن لیدها...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });

        try {
            const res = await fetch('?api=import_leads_execute', {
                method: 'POST',
                body: formData
            });

            let data;
            try {
                data = await res.json();
            } catch (_) {
                const raw = await res.text();
                throw new Error(raw || 'پاسخ نامعتبر از سرور دریافت شد');
            }
            this.closeBlockingSwal();

            if (data.status !== 'success') {
                Swal.fire('خطا', data.message || 'خطای نامشخص', 'error');
                return;
            }

            this.renderImportResults(data);

            document.getElementById('import-step-2').classList.add('hidden');
            document.getElementById('import-step-3').classList.remove('hidden');
            document.getElementById('import-btn-execute').classList.add('hidden');
            document.getElementById('import-btn-done').classList.remove('hidden');

        } catch (error) {
            this.closeBlockingSwal();
            Swal.fire('خطا', 'خطا در وارد کردن لیدها: ' + error.message, 'error');
        }
    },

    renderImportResults(data) {
        document.getElementById('import-result-success').textContent = data.success_count || 0;
        document.getElementById('import-result-error').textContent = data.failure_count || 0;

        const resultsDiv = document.getElementById('import-result-table');
        resultsDiv.innerHTML = (data.results || []).map(r => {
            const isSuccess = r.status === 'success';
            const actionLabelMap = {
                lead_created: 'ایجاد لید',
                deal_created: 'ایجاد معامله',
                skip: 'رد شد'
            };
            const actionLabel = actionLabelMap[r.action] || (r.action || '-');
            return `
                <div class="p-2 rounded ${isSuccess ? 'bg-green-900/20 border-l-2 border-green-600' : 'bg-red-900/20 border-l-2 border-red-600'}">
                    <p class="text-xs font-mono text-gray-300">ردیف ${r.row} | موبایل: ${this.escapeHtml(r.mobile_phone || '')} | مسئول: ${this.escapeHtml(r.owner_username || '-')}</p>
                    <p class="text-[11px] text-gray-400">${this.escapeHtml(actionLabel)}</p>
                    <p class="text-xs ${isSuccess ? 'text-green-300' : 'text-red-300'}">${this.escapeHtml(r.message)}</p>
                </div>
            `;
        }).join('');
    },

    // FIELD CONFIGURATION MANAGER (Admin)
    switchLookupTab(tab) {
        // Hide all tabs
        document.querySelectorAll('.lookup-tab-content').forEach(el => el.classList.add('hidden'));
        document.querySelectorAll('.lookup-tab').forEach(el => el.classList.remove('active', 'border-blue-500'));

        // Show selected tab
        const tabEl = document.getElementById(`lookup-${tab}-tab`);
        if (tabEl) {
            tabEl.classList.remove('hidden');
        }

        // Mark button as active
        const btn = document.querySelector(`.lookup-tab[data-tab="${tab}"]`);
        if (btn) {
            btn.classList.add('active', 'border-blue-500');
            btn.classList.remove('border-transparent');
        }

        // Load data if needed
        if (tab === 'fields') {
            this.loadFieldConfigurations();
        }
    },

    async loadFieldConfigurations(forceRefresh = false) {
        try {
            const res = await this.req('get_field_configurations');
            if (res.status === 'success') {
                this.state.fieldConfigurations = res.configurations || [];
                this.renderFieldConfigurationList();
                this.populateFieldConfigSelect();
            }
        } catch (error) {
            console.error('Error loading field configurations:', error);
            Swal.fire('خطا', 'خطا در بارگذاری تنظیمات فیلدها', 'error');
        }
    },

    renderFieldConfigurationList() {
        const container = document.getElementById('field-config-list');
        if (!container) return;

        container.innerHTML = (this.state.fieldConfigurations || []).map(config => `
            <div class="bg-gray-800/30 p-3 rounded border border-gray-700/50 hover:border-gray-600 cursor-pointer transition" onclick="app.editFieldConfiguration('${config.field_name}')">
                <div class="flex justify-between items-start">
                    <div class="flex-1">
                        <h4 class="font-medium text-white text-sm">${this.escapeHtml(config.field_label)}</h4>
                        <p class="text-xs text-gray-400 mt-1">
                            <span class="bg-gray-700/50 px-2 py-1 rounded">${config.field_name}</span>
                            <span class="ml-2 text-gray-500">نوع: ${config.field_type}</span>
                        </p>
                        <p class="text-xs text-gray-500 mt-1">
                            ${config.is_required ? '✓ الزامی' : '○ اختیاری'}
                            ${config.is_editable ? ' | ✓ قابل ویرایش' : ' | ✗ فقط خواندنی'}
                            ${config.is_active ? ' | ✓ فعال' : ' | ✗ غیرفعال'}
                        </p>
                    </div>
                    <button onclick="event.stopPropagation(); app.deleteFieldConfiguration('${config.field_name}')" class="text-red-400 hover:text-red-300 text-xs p-2">
                        <i class="fa-solid fa-trash"></i>
                    </button>
                </div>
            </div>
        `).join('');
    },

    populateFieldConfigSelect() {
        const select = document.getElementById('field-config-select');
        if (!select) return;

        select.innerHTML = '<option value="">-- انتخاب فیلد --</option>' +
            (this.state.fieldConfigurations || []).map(config =>
                `<option value="${config.field_name}">${this.escapeHtml(config.field_label)}</option>`
            ).join('');
    },

    onFieldConfigSelect() {
        const fieldName = document.getElementById('field-config-select')?.value;
        if (!fieldName) {
            document.getElementById('field-config-editor').classList.add('hidden');
            return;
        }

        this.editFieldConfiguration(fieldName);
    },

    editFieldConfiguration(fieldName) {
        const config = (this.state.fieldConfigurations || []).find(c => c.field_name === fieldName);
        if (!config) return;

        // Populate form
        document.getElementById('fc-field-name').value = config.field_name;
        document.getElementById('fc-field-label').value = config.field_label || '';
        document.getElementById('fc-field-type').value = config.field_type || 'text';
        document.getElementById('fc-entity-type').value = config.entity_type || 'deal';
        document.getElementById('fc-lookup-group').value = config.lookup_group_code || '';
        document.getElementById('fc-sort-order').value = config.sort_order || 0;
        document.getElementById('fc-is-required').checked = config.is_required === 1;
        document.getElementById('fc-is-editable').checked = config.is_editable === 1;
        document.getElementById('fc-is-active').checked = config.is_active === 1;

        // Set roles
        const roles = config.edit_allow_roles || [];
        document.querySelectorAll('#fc-edit-allow-roles option').forEach(opt => {
            opt.selected = roles.includes(opt.value);
        });

        // Show editor
        document.getElementById('field-config-editor').classList.remove('hidden');
        document.getElementById('field-config-select').value = fieldName;
    },

    resetFieldConfigEditor() {
        document.getElementById('field-config-editor').classList.add('hidden');
        document.getElementById('field-config-select').value = '';
        document.getElementById('fc-field-name').value = '';
        document.getElementById('fc-field-label').value = '';
        document.getElementById('fc-is-required').checked = false;
        document.getElementById('fc-is-editable').checked = true;
        document.getElementById('fc-is-active').checked = true;
    },

    async saveFieldConfiguration() {
        const fieldName = document.getElementById('fc-field-name').value;
        if (!fieldName) {
            Swal.fire('خطا', 'نام فیلد الزامی است', 'error');
            return;
        }

        // Get selected roles
        const selectedRoles = Array.from(
            document.querySelectorAll('#fc-edit-allow-roles option:checked')
        ).map(opt => opt.value);

        const data = {
            field_name: fieldName,
            field_label: document.getElementById('fc-field-label').value,
            field_type: document.getElementById('fc-field-type').value,
            entity_type: document.getElementById('fc-entity-type').value,
            is_required: document.getElementById('fc-is-required').checked ? 1 : 0,
            is_editable: document.getElementById('fc-is-editable').checked ? 1 : 0,
            is_active: document.getElementById('fc-is-active').checked ? 1 : 0,
            edit_allow_roles: JSON.stringify(selectedRoles),
            lookup_group_code: document.getElementById('fc-lookup-group').value || null,
            sort_order: parseInt(document.getElementById('fc-sort-order').value) || 0
        };

        try {
            const res = await this.req('save_field_configuration', data);
            if (res.status === 'success') {
                Swal.fire('موفق', 'تنظیمات فیلد ذخیره شد', 'success');
                this.loadFieldConfigurations(true);
                this.resetFieldConfigEditor();
            } else {
                Swal.fire('خطا', res.message || 'خطا در ذخیره‌سازی', 'error');
            }
        } catch (error) {
            console.error('Error saving field configuration:', error);
            Swal.fire('خطا', 'خطا در ذخیره‌سازی تنظیمات: ' + error.message, 'error');
        }
    },

    async deleteFieldConfiguration(fieldName) {
        const confirm = await Swal.fire({
            title: 'حذف تنظیمات فیلد',
            text: `آیا مطمئن هستید که می‌خواهید ${fieldName} را حذف کنید؟`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'بله، حذف کن',
            cancelButtonText: 'خیر'
        });

        if (!confirm.isConfirmed) return;

        try {
            const res = await this.req('delete_field_configuration', { field_name: fieldName });
            if (res.status === 'success') {
                Swal.fire('موفق', 'تنظیمات فیلد حذف شد', 'success');
                this.loadFieldConfigurations(true);
                this.resetFieldConfigEditor();
            } else {
                Swal.fire('خطا', res.message || 'خطا در حذف', 'error');
            }
        } catch (error) {
            console.error('Error deleting field configuration:', error);
            Swal.fire('خطا', 'خطا در حذف: ' + error.message, 'error');
        }
    },

    // ========== PERFORMANCE DASHBOARD ==========
    getPerformanceAllowedRange() {
        const start = new Date();
        start.setDate(1);
        start.setMonth(start.getMonth() - 1);
        const end = new Date();
        end.setMonth(end.getMonth() + 1, 0);
        return {
            date_from: start.toISOString().slice(0, 10),
            date_to: end.toISOString().slice(0, 10)
        };
    },

    getCurrentMonthRangeGregorian() {
        const jalaliRange = this.getCurrentJalaliMonthRangeGregorian?.();
        if (jalaliRange?.date_from && jalaliRange?.date_to) {
            return jalaliRange;
        }
        const d = new Date();
        const from = new Date(d.getFullYear(), d.getMonth(), 1);
        const to = new Date(d.getFullYear(), d.getMonth() + 1, 0);
        return { date_from: from.toISOString().slice(0, 10), date_to: to.toISOString().slice(0, 10) };
    },

    getCurrentJalaliParts() {
        try {
            const formatter = new Intl.DateTimeFormat('en-US-u-ca-persian', {
                timeZone: 'Asia/Tehran',
                numberingSystem: 'latn',
                year: 'numeric',
                month: 'numeric',
                day: 'numeric'
            });
            const parts = formatter.formatToParts(new Date());
            const year = Number(parts.find(p => p.type === 'year' || p.type === 'relatedYear')?.value || 0);
            const month = Number(parts.find(p => p.type === 'month')?.value || 0);
            const day = Number(parts.find(p => p.type === 'day')?.value || 0);
            if (year >= 1300 && year <= 1599 && month >= 1 && month <= 12 && day >= 1 && day <= 31) {
                return { year, month, day };
            }
        } catch (e) {}
        const todayPersian = toPersianDate(this.getTodayGregorianDateOnly());
        const m = String(todayPersian || '').match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
        if (!m) return null;
        return { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) };
    },

    getCurrentJalaliMonthKey() {
        const parts = this.getCurrentJalaliParts?.();
        if (!parts) return '';
        return `${String(parts.year).padStart(4, '0')}-${String(parts.month).padStart(2, '0')}`;
    },

    getJalaliMonthLength(year, month) {
        if (month >= 1 && month <= 6) return 31;
        if (month >= 7 && month <= 11) return 30;
        if (month !== 12) return 0;
        return this.jalaliToGregorianDateOnly(`${year}/12/30`, false) ? 30 : 29;
    },

    getCurrentJalaliMonthRangeGregorian() {
        const parts = this.getCurrentJalaliParts?.();
        if (!parts) return null;
        const lastDay = this.getJalaliMonthLength(parts.year, parts.month);
        const fromJ = `${String(parts.year).padStart(4, '0')}/${String(parts.month).padStart(2, '0')}/01`;
        const toJ = `${String(parts.year).padStart(4, '0')}/${String(parts.month).padStart(2, '0')}/${String(lastDay).padStart(2, '0')}`;
        const dateFrom = this.jalaliToGregorianDateOnly(fromJ, false);
        const dateTo = this.jalaliToGregorianDateOnly(toJ, true);
        if (!dateFrom || !dateTo) return null;
        return { date_from: dateFrom, date_to: dateTo, jalali_month: this.getCurrentJalaliMonthKey() };
    },

    getTodayGregorianDateOnly() {
        const d = new Date();
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    },

    getPreviousMonthRangeGregorian() {
        const d = new Date();
        const from = new Date(d.getFullYear(), d.getMonth() - 1, 1);
        const to = new Date(d.getFullYear(), d.getMonth(), 0);
        return { date_from: from.toISOString().slice(0, 10), date_to: to.toISOString().slice(0, 10) };
    },

    isDateWithinRange(dateStr, minDateStr, maxDateStr) {
        if (!dateStr) return false;
        const d = new Date(dateStr + 'T00:00:00');
        const min = new Date(minDateStr + 'T00:00:00');
        const max = new Date(maxDateStr + 'T23:59:59');
        return d >= min && d <= max;
    },

    normalizeJalaliInputValue(value = '') {
        const raw = this.toEnglishDigits(String(value || '').trim())
            .replace(/[-.]/g, '/')
            .replace(/\s+/g, '');
        const match = raw.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
        if (!match) return raw;
        return `${match[1]}/${String(Number(match[2])).padStart(2, '0')}/${String(Number(match[3])).padStart(2, '0')}`;
    },

    jalaliToGregorianDateOnly(value = '', endOfDay = false) {
        const normalized = this.normalizeJalaliInputValue(value);
        if (!normalized) return '';
        const converted = persianToGregorian(normalized, endOfDay ? '23:59' : '00:00');
        if (!converted) return '';
        return String(converted).split(/[ T]/)[0] || '';
    },

    syncPerformanceDateFilterFromInput(inputEl) {
        if (!inputEl) return;
        const key = inputEl.id === 'performance-date-to' ? 'date_to' : 'date_from';
        const normalized = this.normalizeJalaliInputValue(inputEl.value);
        if (normalized && normalized !== inputEl.value) {
            inputEl.value = normalized;
        }
        let gregorian = this.jalaliToGregorianDateOnly(normalized, key === 'date_to');
        if (!gregorian) return;
        const today = this.getTodayGregorianDateOnly();
        if (gregorian > today) {
            gregorian = today;
            inputEl.value = toPersianDate(today) || inputEl.value;
        }
        const currentFilters = this.state.performanceFilters || {};
        if (key === 'date_to' && currentFilters.date_from && gregorian < currentFilters.date_from) {
            gregorian = currentFilters.date_from;
            inputEl.value = toPersianDate(gregorian) || inputEl.value;
        }
        this.state.performanceFilters = {
            ...currentFilters,
            [key]: gregorian
        };
        if (key === 'date_from') {
            const toInput = document.getElementById('performance-date-to');
            const dateTo = this.state.performanceFilters.date_to || '';
            if (dateTo && dateTo < gregorian) {
                this.state.performanceFilters.date_to = gregorian;
                if (toInput) {
                    toInput.value = toPersianDate(gregorian) || toInput.value;
                }
            }
        }
        // Refresh the live min/max limits of BOTH pickers so the counterpart picker
        // reflects the newly chosen date immediately (e.g. picking a new "از" must make
        // the "تا" picker allow [از .. today] right away, without a full dashboard re-render).
        this.applyPerformanceDatePickerLimits();
    },

    applyPerformanceDatePickerLimits() {
        if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') return;
        const self = this;
        const filters = this.state.performanceFilters || {};
        const today = this.getTodayGregorianDateOnly();
        const fromLimit = filters.date_from || '';
        const toLimit = filters.date_to || '';
        const apply = (id) => {
            const el = document.getElementById(id);
            if (!el || !el._neoJdp) return; // picker not initialized yet
            const maxDate = today; // "از" not capped by "تا" — always selectable up to today
            const minDate = id === 'performance-date-to' ? (fromLimit || null) : null;
            try {
                // Re-applying options on an already-initialized input only updates its
                // min/max/onSelect in place (no destroy, no re-render) — see jalali-datepicker init early-return.
                $(el).persianDatepicker({
                    minDate,
                    maxDate,
                    onSelect: function () {
                        setTimeout(() => self.syncPerformanceDateFilterFromInput(el), 0);
                    }
                });
            } catch (e) {}
        };
        apply('performance-date-from');
        apply('performance-date-to');
    },

    isPerformanceDatePickerOpen() {
        return typeof $ !== 'undefined' && $('.neo-jdp:visible, .datepicker-container:not(.pwt-hide)').length > 0;
    },

    destroyPerformanceDatePickers() {
        if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') return;
        $('#performance-date-from, #performance-date-to').each(function () {
            try {
                $(this).persianDatepicker('destroy');
            } catch (e) {}
            $(this).removeData('perfDatepickerInit');
        });
    },

    async setPerformanceQuickRange(rangeType) {
        let range;
        if (rangeType === 'today') {
            const today = new Date().toISOString().slice(0, 10);
            range = { date_from: today, date_to: today };
        } else if (rangeType === 'yesterday') {
            const y = new Date();
            y.setDate(y.getDate() - 1);
            const day = y.toISOString().slice(0, 10);
            range = { date_from: day, date_to: day };
        } else if (rangeType === 'prev_month') {
            range = this.getPreviousMonthRangeGregorian();
        } else {
            range = this.getCurrentMonthRangeGregorian();
        }

        this.state.performanceFilters = {
            ...(this.state.performanceFilters || {}),
            date_from: range.date_from,
            date_to: range.date_to
        };

        await this.loadPerformanceDashboard({ useState: true });
    },

    initPerformanceDatePickers() {
        const self = this;
        setTimeout(function () {
            if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') return;
            const $from = $('#performance-date-from');
            const $to = $('#performance-date-to');
            const filters = self.state.performanceFilters || {};
            const initOnce = ($input, desiredValue) => {
                if (!$input || $input.length === 0) return;
                const inputEl = $input.get(0);
                if (!inputEl) return;
                const normalizedDesired = desiredValue ? (toPersianDate(desiredValue) || desiredValue) : '';
                const isOpen = self.isPerformanceDatePickerOpen();
                const today = self.getTodayGregorianDateOnly();
                const fromLimit = filters.date_from || '';
                const toLimit = filters.date_to || '';
                // Both pickers are capped at today. "از" is intentionally NOT capped by "تا":
                // any start date up to today is allowed; if it passes "تا", sync auto-bumps "تا".
                const maxDate = today;
                const minDate = inputEl.id === 'performance-date-to' && fromLimit ? fromLimit : null;
                if ($input.data('perfDatepickerInit') !== 1) {
                    try { $input.persianDatepicker('destroy'); } catch (e) {}
                    $input.persianDatepicker({
                        format: 'YYYY/MM/DD',
                        initialValue: false,
                        minDate,
                        maxDate,
                        autoClose: true,
                        observer: true,
                        onSelect: function () {
                            setTimeout(() => self.syncPerformanceDateFilterFromInput(inputEl), 0);
                        }
                    });
                    $input.on('change blur', function () {
                        self.syncPerformanceDateFilterFromInput(inputEl);
                    });
                    $input.data('perfDatepickerInit', 1);
                } else {
                    const instance = inputEl._neoJdp;
                    if (instance && typeof window.NeoJalaliDatePicker !== 'undefined') {
                        try {
                            $(inputEl).persianDatepicker({ minDate, maxDate, onSelect: function () {
                                setTimeout(() => self.syncPerformanceDateFilterFromInput(inputEl), 0);
                            } });
                            $input.data('perfDatepickerInit', 1);
                        } catch (e) {}
                    }
                }
                if (document.activeElement === inputEl || isOpen) return;
                if (normalizedDesired && String($input.val() || '') !== String(normalizedDesired)) {
                    $input.val(normalizedDesired);
                }
            };
            initOnce($from, filters.date_from || '');
            initOnce($to, filters.date_to || '');
        }, 150);
    },

    getPerformanceOwnerData() {
        const options = [{ value: '__all__', label: 'همه مسئول‌ها' }];
        if (this.state.kpiUsers && this.state.kpiUsers.length > 0) {
            this.state.kpiUsers
                .filter(user => Number(user.is_active) === 1)
                .forEach(user => {
                    const value = user.didar_user_id || user.owner_id || '';
                    if (!value) return;
                    options.push({ value: String(value), label: user.display_name || user.email || 'کاربر' });
                });
        }
        return options;
    },

    getPerformanceProductData() {
        const options = [{ value: '__all__', label: 'همه محصولات' }];
        (this.state.kpiProducts || []).forEach((product) => {
            if (!product?.id) return;
            options.push({ value: String(product.id), label: product.name || 'محصول' });
        });
        return options;
    },

    getDefaultKpiCriteria() {
        return {
            kpi_sales_date_basis: 'ui_payment_date',
            kpi_sales_count_rule: 'first_payment_only',
            kpi_payment_date_basis: 'ui_payment_date',
            kpi_payment_count_rule: 'all_payments',
            kpi_payment_status_scope: 'confirmed_only',
            legacy_deal_scope: 'exclude'
        };
    },

    normalizeKpiCriteria(criteria = {}) {
        const defaults = this.getDefaultKpiCriteria();
        return {
            ...defaults,
            ...(criteria || {})
        };
    },

    getKpiCriteriaSelectOptions(key) {
        const map = {
            kpi_sales_date_basis: [
                { value: 'ui_payment_date', label: 'فروش: تاریخ واریزی UI' },
                { value: 'db_payment_date', label: 'فروش: تاریخ پرداخت DB' },
                { value: 'system_created_at', label: 'فروش: تاریخ ثبت سیستمی' }
            ],
            kpi_sales_count_rule: [
                { value: 'first_payment_only', label: 'فروش: فقط اولین واریزی' },
                { value: 'all_payments', label: 'فروش: همه پرداخت‌ها' }
            ],
            kpi_payment_date_basis: [
                { value: 'ui_payment_date', label: 'مالی: تاریخ واریزی UI' },
                { value: 'db_payment_date', label: 'مالی: تاریخ پرداخت DB' },
                { value: 'system_created_at', label: 'مالی: تاریخ ثبت سیستمی' }
            ],
            kpi_payment_count_rule: [
                { value: 'all_payments', label: 'مالی: همه پرداخت‌ها' },
                { value: 'first_payment_only', label: 'مالی: فقط اولین واریزی' }
            ],
            kpi_payment_status_scope: [
                { value: 'confirmed_only', label: 'وضعیت پرداخت: فقط تاییدشده' },
                { value: 'all_statuses', label: 'وضعیت پرداخت: همه وضعیت‌ها' }
            ],
            legacy_deal_scope: [
                { value: 'exclude', label: 'معاملات legacy: حذف شود' },
                { value: 'all', label: 'معاملات legacy: همه' },
                { value: 'only', label: 'معاملات legacy: فقط legacy' }
            ]
        };
        return map[key] || [];
    },

    renderKpiCriteriaSelect(key, value, compact = false) {
        const options = this.getKpiCriteriaSelectOptions(key);
        const labelMap = {
            kpi_sales_date_basis: 'مبنای تاریخ فروش',
            kpi_sales_count_rule: 'قاعده احتساب فروش',
            kpi_payment_date_basis: 'مبنای تاریخ مالی',
            kpi_payment_count_rule: 'قاعده احتساب مالی',
            kpi_payment_status_scope: 'وضعیت پرداخت',
            legacy_deal_scope: 'فیلتر legacy deal'
        };
        const id = `${key}${compact ? '-perf' : ''}`;
        return `
            <div>
                <label class="block text-xs text-gray-400 mb-1">${labelMap[key] || key}</label>
                <select id="${id}" class="w-full bg-gray-800 border border-gray-700 rounded p-2 text-white text-xs">
                    ${options.map(opt => `<option value="${opt.value}" ${String(value) === String(opt.value) ? 'selected' : ''}>${this.escapeHtml(opt.label)}</option>`).join('')}
                </select>
            </div>
        `;
    },

    collectKpiCriteriaFromDom(compact = false) {
        const defaults = this.getDefaultKpiCriteria();
        const suffix = compact ? '-perf' : '';
        const out = { ...defaults };
        Object.keys(defaults).forEach((key) => {
            const element = document.getElementById(`${key}${suffix}`);
            if (element && element.value) {
                out[key] = element.value;
            }
        });
        return out;
    },

    appendCriteriaToKpiPayload(payload, mode = 'kpi') {
        const sourceFilters = mode === 'performance'
            ? (this.state.performanceFilters || {})
            : (this.state.kpiFilters || {});
        const criteria = this.normalizeKpiCriteria(sourceFilters.criteria || {});
        payload.kpi_sales_date_basis = criteria.kpi_sales_date_basis;
        payload.kpi_sales_count_rule = criteria.kpi_sales_count_rule;
        payload.kpi_payment_date_basis = criteria.kpi_payment_date_basis;
        payload.kpi_payment_count_rule = criteria.kpi_payment_count_rule;
        payload.kpi_payment_status_scope = criteria.kpi_payment_status_scope;
        payload.legacy_deal_scope = criteria.legacy_deal_scope;
        return payload;
    },

    renderDrilldownCriteriaControls() {
        if (!this.isAdminLikeForAnalytics()) return '';
        const ctx = this.state.kpiDrilldownContext || null;
        if (!ctx) return '';
        const mode = ctx.mode === 'performance' ? 'performance' : 'kpi';
        if (mode === 'performance') return '';
        const filters = mode === 'performance' ? (this.state.performanceFilters || {}) : (this.state.kpiFilters || {});
        const criteria = this.normalizeKpiCriteria(filters.criteria || {});
        return `
            <div class="bg-gray-900/60 border border-gray-700 rounded p-3 mb-4">
                <div class="text-xs text-gray-300 font-bold mb-2">فیلترهای مبنای تاریخ برای Drill-down</div>
                <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-6 gap-2">
                    ${this.renderKpiCriteriaSelect('kpi_sales_date_basis', criteria.kpi_sales_date_basis, true).replace('kpi_sales_date_basis-perf', 'kpi_sales_date_basis-drill')}
                    ${this.renderKpiCriteriaSelect('kpi_sales_count_rule', criteria.kpi_sales_count_rule, true).replace('kpi_sales_count_rule-perf', 'kpi_sales_count_rule-drill')}
                    ${this.renderKpiCriteriaSelect('kpi_payment_date_basis', criteria.kpi_payment_date_basis, true).replace('kpi_payment_date_basis-perf', 'kpi_payment_date_basis-drill')}
                    ${this.renderKpiCriteriaSelect('kpi_payment_count_rule', criteria.kpi_payment_count_rule, true).replace('kpi_payment_count_rule-perf', 'kpi_payment_count_rule-drill')}
                    ${this.renderKpiCriteriaSelect('kpi_payment_status_scope', criteria.kpi_payment_status_scope, true).replace('kpi_payment_status_scope-perf', 'kpi_payment_status_scope-drill')}
                    ${this.renderKpiCriteriaSelect('legacy_deal_scope', criteria.legacy_deal_scope, true).replace('legacy_deal_scope-perf', 'legacy_deal_scope-drill')}
                </div>
                <div class="mt-3 flex justify-end">
                    <button type="button" class="px-3 py-1.5 text-xs rounded bg-blue-600 hover:bg-blue-500 text-white" onclick="app.applyDrilldownCriteriaFilters()">اعمال فیلتر Drill-down</button>
                </div>
            </div>
        `;
    },

    collectKpiCriteriaFromDrilldownDom() {
        const defaults = this.getDefaultKpiCriteria();
        const out = { ...defaults };
        Object.keys(defaults).forEach((key) => {
            const element = document.getElementById(`${key}-drill`);
            if (element && element.value) out[key] = element.value;
        });
        return out;
    },

    async applyDrilldownCriteriaFilters() {
        const ctx = this.state.kpiDrilldownContext;
        if (!ctx) return;
        const criteria = this.collectKpiCriteriaFromDrilldownDom();
        if (ctx.mode === 'performance') {
            this.state.performanceFilters = this.state.performanceFilters || {};
            this.state.performanceFilters.criteria = criteria;
        } else {
            this.state.kpiFilters = this.state.kpiFilters || {};
            this.state.kpiFilters.criteria = criteria;
        }
        await this.reloadCurrentKpiDrilldown();
    },

    async reloadCurrentKpiDrilldown() {
        const ctx = this.state.kpiDrilldownContext;
        if (!ctx) return;
        if (ctx.kind === 'performance_chart') {
            return this.openPerformanceChartDrilldown(ctx.tableId, ctx.metricKey, ctx.rowContext || {});
        }
        if (ctx.kind === 'performance_table') {
            return this.openPerformanceDrilldown(ctx.tableId, ctx.metricKey, ctx.sectionKey, ctx.rowIndex);
        }
        if (ctx.kind === 'performance_ceo') {
            return this.openPerformanceCeoDrilldown(ctx.metricKey, ctx.row || {}, ctx.groupKey || '');
        }
        if (ctx.kind === 'kpi_advanced') {
            return this.openKpiAdvancedDrilldown(ctx.tableId, ctx.metricKey, ctx.rowIndex);
        }
    },

    async loadPerformanceDashboard(options = {}) {
        if (!this.canPerformanceFeatureClient('performance.view')) {
            Swal.fire('خطا', 'دسترسی به داشبورد عملکرد ندارید', 'error');
            return;
        }
        const content = document.getElementById('performance-dashboard-content');
        if (!content) return;

        const useState = options.useState === true;
        await this.loadKpiFilterData('performance'); // re-use existing loaders for users/products

        const nowRange = this.getCurrentMonthRangeGregorian();
        let dateFrom = this.state.performanceFilters?.date_from || nowRange.date_from;
        let dateTo = this.state.performanceFilters?.date_to || nowRange.date_to;
        let ownerIds = Array.isArray(this.state.performanceFilters?.owner_didar_ids) ? [...this.state.performanceFilters.owner_didar_ids] : [];
        let productIds = Array.isArray(this.state.performanceFilters?.product_ids) ? [...this.state.performanceFilters.product_ids] : [];
        let criteria = this.normalizeKpiCriteria(this.state.performanceFilters?.criteria || {});

        if (!useState) {
            const dateFromJ = (document.getElementById('performance-date-from')?.value || '').trim();
            const dateToJ = (document.getElementById('performance-date-to')?.value || '').trim();

            if (dateFromJ) {
                const g = this.jalaliToGregorianDateOnly(dateFromJ, false);
                if (g) dateFrom = g;
                else if (/^\d{4}-\d{2}-\d{2}$/.test(dateFromJ)) dateFrom = dateFromJ;
            }
            if (dateToJ) {
                const g = this.jalaliToGregorianDateOnly(dateToJ, true);
                if (g) dateTo = g;
                else if (/^\d{4}-\d{2}-\d{2}$/.test(dateToJ)) dateTo = dateToJ;
            }

            ownerIds = this.getCustomMultiSelectValues('performance-owner-filter');
            productIds = this.getCustomMultiSelectValues('performance-product-filter');
            criteria = this.collectKpiCriteriaFromDom(true);
        }

        if (!this.isAdminLikeForAnalytics() && !this.hasCrossUserScopeClient('performance')) {
            ownerIds = []; // server forces owner for agent-like users
        }

        const todayLimit = this.getTodayGregorianDateOnly();
        if (dateFrom > todayLimit) dateFrom = todayLimit;
        if (dateTo < dateFrom) dateTo = dateFrom;

        this.state.performanceFilters = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_ids: ownerIds,
            product_ids: productIds,
            criteria
        };

        content.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داشبورد عملکرد...</div>';
        const perfLoadRequestId = (Number(this.state.performanceLoadRequestId || 0) + 1);
        this.state.performanceLoadRequestId = perfLoadRequestId;

        const payload = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_ids: ownerIds.join(','),
            product_ids: productIds.join(',')
        };
        const commissionPayload = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_id: ownerIds.length === 1 ? ownerIds[0] : ''
        };
        const commissionRes = await this.req('get_commission_kpis', commissionPayload);
        this.state.commissionKpis = commissionRes.status === 'success' ? (commissionRes || {}) : null;
        this.appendCriteriaToKpiPayload(payload, 'performance');
        this.state.performanceSplitPayload = { ...payload };
        this.state.performanceTabsLoaded = false;
        this.state.performanceCeoReportLoaded = false;
        const sectionReq = async (section) => this.req('get_performance_dashboard_section', { ...payload, section });
        let usedSplit = false;
        try {
            const summaryRes = await sectionReq('summary');
            if (summaryRes.status !== 'success') throw new Error(summaryRes.message || 'summary failed');
            if (this.state.performanceLoadRequestId !== perfLoadRequestId) return;
            usedSplit = true;
            if (summaryRes.filters && summaryRes.filters.criteria) {
                this.state.performanceFilters = {
                    ...(this.state.performanceFilters || {}),
                    criteria: this.normalizeKpiCriteria(summaryRes.filters.criteria)
                };
            }
            this.state.performanceDashboard = this.mergePerformanceDashboardSection({}, summaryRes.data || {}, 'summary');
            this.renderPerformanceDashboard(this.state.performanceDashboard);

            if (this.canAccessCeoPerformanceReportClient()) {
                const ceoRes = await sectionReq('ceo_report');
                if (ceoRes.status === 'success') {
                    if (this.state.performanceLoadRequestId !== perfLoadRequestId) return;
                    this.state.performanceDashboard = this.mergePerformanceDashboardSection(this.state.performanceDashboard || {}, ceoRes.data || {}, 'ceo_report');
                    this.state.performanceCeoReportLoaded = true;
                    this.renderPerformanceDashboard(this.state.performanceDashboard);
                }
            }

            const chartsRes = await sectionReq('charts');
            if (chartsRes.status === 'success') {
                if (this.state.performanceLoadRequestId !== perfLoadRequestId) return;
                this.state.performanceDashboard = this.mergePerformanceDashboardSection(this.state.performanceDashboard || {}, chartsRes.data || {}, 'charts');
                this.renderPerformanceDashboard(this.state.performanceDashboard);
            }

            setTimeout(() => {
                this.loadPerformanceDashboardTabsSection({ requestId: perfLoadRequestId });
            }, 0);
        } catch (e) {
            console.warn('Performance split load failed, fallback to full endpoint', e);
        }

        if (!usedSplit) {
            const res = await this.req('get_performance_dashboard', payload);
            if (res.status !== 'success') {
                Swal.fire('خطا', res.message || 'خطا در بارگذاری داشبورد عملکرد', 'error');
                return;
            }
            if (res.filters && res.filters.criteria) {
                this.state.performanceFilters = {
                    ...(this.state.performanceFilters || {}),
                    criteria: this.normalizeKpiCriteria(res.filters.criteria)
                };
            }
            this.state.performanceDashboard = res.dashboard || null;
            this.state.performanceTabsLoaded = true;
            this.renderPerformanceDashboard(res.dashboard || {});
        }
    },

    async loadPerformanceDashboardTabsSection(options = {}) {
        const requestId = Number(options.requestId || this.state.performanceLoadRequestId || 0);
        if (!this.state.performanceSplitPayload) return;
        if (this.state.performanceTabsLoaded) return;
        if (this.state.performanceTabsLoading) return;
        this.state.performanceTabsLoading = true;
        try {
            const res = await this.req('get_performance_dashboard_section', { ...this.state.performanceSplitPayload, section: 'tabs' });
            if (res.status === 'success' && this.state.performanceLoadRequestId === requestId) {
                this.state.performanceDashboard = this.mergePerformanceDashboardSection(this.state.performanceDashboard || {}, res.data || {}, 'tabs');
                this.state.performanceTabsLoaded = true;
                this.renderPerformanceDashboard(this.state.performanceDashboard || {});
            }
        } catch (e) {
            console.warn('Performance tabs lazy load failed', e);
        } finally {
            this.state.performanceTabsLoading = false;
        }
    },

    mergePerformanceDashboardSection(current, sectionData, section) {
        const out = current ? JSON.parse(JSON.stringify(current)) : {};
        if (section === 'summary') {
            out.summary_cards = sectionData.summary_cards || {};
            out.comparison = sectionData.comparison || {};
            out.charts = out.charts || {};
            out.tabs = out.tabs || {};
            out.raw = out.raw || {};
        } else if (section === 'charts') {
            out.charts = { ...(out.charts || {}), ...(sectionData.charts || {}) };
        } else if (section === 'tabs') {
            out.tabs = sectionData.tabs || {};
            out.raw = { ...(out.raw || {}), ...(sectionData.raw || {}) };
        } else if (section === 'ceo_report') {
            out.ceo_report = sectionData.ceo_report || {};
        }
        return out;
    },

    renderPerformanceDashboard(data) {
        const content = document.getElementById('performance-dashboard-content');
        if (!content) return;
        if (this.isPerformanceDatePickerOpen()) {
            this.state.pendingPerformanceRenderData = data || {};
            clearTimeout(this.state.pendingPerformanceRenderTimer);
            this.state.pendingPerformanceRenderTimer = setTimeout(() => {
                if (this.isPerformanceDatePickerOpen()) return;
                const pending = this.state.pendingPerformanceRenderData || {};
                this.state.pendingPerformanceRenderData = null;
                this.renderPerformanceDashboard(pending);
            }, 160);
            return;
        }
        this.destroyPerformanceDatePickers();
        const filters = this.state.performanceFilters || {};
        const canChoosePerformanceOwners = this.hasCrossUserScopeClient('performance');
        const criteria = this.normalizeKpiCriteria(filters.criteria || {});
        const summary = data.summary_cards || {};
        const tabs = data.tabs || {};
        const ceoReport = data.ceo_report || {};
        const activityPerformance = summary.activity_performance || {};
        const lostReasonBreakdown = Array.isArray(summary.lost_reason_breakdown) ? summary.lost_reason_breakdown : [];

        const dateFromJ = filters.date_from ? (toPersianDate(filters.date_from) || filters.date_from) : '';
        const dateToJ = filters.date_to ? (toPersianDate(filters.date_to) || filters.date_to) : '';

        const formatToman = (amount) => `${this.formatNumber(Number(amount || 0))} تومان`;
        const commissionKpis = this.state.commissionKpis || {};
        const repKpis = commissionKpis.rep || {};
        const managerKpis = commissionKpis.manager || {};
        const canSeeTeamCommissionMetrics = this.isAdminOrCrmSpecialist();

        const tabButtons = [
            { key: 'activity', label: 'فعالیت' },
            { key: 'sales_deals', label: 'فروش و معاملات' },
            { key: 'payments', label: 'پرداخت' },
            { key: 'products', label: 'محصولات' },
            { key: 'failure_reasons', label: 'دلایل شکست' }
        ].filter(t => {
            const sectionMap = {
                activity: ['performance.table.activity_summary', 'performance.table.activity_type_details'],
                sales_deals: ['performance.table.deal_status', 'performance.table.lead_status', 'performance.table.conversion_status'],
                payments: ['performance.table.payment_mix', 'performance.table.payment_status'],
                products: ['performance.table.product_status', 'performance.table.product_sold_by_owner_day', 'performance.table.product_requests_by_owner_day', 'performance.table.product_failed_by_owner_day'],
                failure_reasons: ['performance.table.failure_reasons']
            };
            const keys = sectionMap[t.key] || [];
            if (!keys.length) return true;
            return keys.some(k => this.canPerformanceFeatureClient(k));
        }).map(t => {
            const isActive = this.state.performanceActiveTab === t.key;
            return `<button class="px-3 py-2 rounded text-xs border ${isActive ? 'bg-blue-600 border-blue-500 text-white' : 'bg-gray-800 border-gray-700 text-gray-300'}" onclick="app.switchPerformanceTab('${t.key}')">${t.label}</button>`;
        }).join('');
        const perfCanDailySales = this.canPerformanceFeatureClient('performance.chart.daily_sales');
        const perfCanDailyActivities = this.canPerformanceFeatureClient('performance.chart.daily_activities');
        const perfCanDealStatus = this.canPerformanceFeatureClient('performance.chart.deal_status');
        const perfCanProductShare = this.canPerformanceFeatureClient('performance.chart.product_share');
        const perfCanFunnel = this.canPerformanceFeatureClient('performance.chart.conversion_funnel');

        const lostReasonHtml = lostReasonBreakdown.length
            ? lostReasonBreakdown.slice(0, 3).map(item => {
                const reason = this.escapeHtml(item.reason || 'نامشخص');
                const count = this.formatNumber(Number(item.count || 0));
                return `<div class="text-[10px] text-gray-500">${reason}: ${count}</div>`;
            }).join('')
            : '';

        content.innerHTML = `
            <div class="glass perf-filter-bar">
                <div class="perf-filter-head">
                    <div class="perf-filter-title">
                        <span class="perf-filter-dot"></span>
                        <span>فیلترهای داشبورد عملکرد</span>
                    </div>
                    <span class="perf-filter-note">قواعد محاسباتی با پیش‌فرض سیستم اعمال می‌شوند.</span>
                    <div class="perf-quick-actions">
                        <button onclick="app.setPerformanceQuickRange('today')">امروز</button>
                        <button onclick="app.setPerformanceQuickRange('yesterday')">دیروز</button>
                        <button onclick="app.setPerformanceQuickRange('month')">ماه جاری</button>
                        <button onclick="app.setPerformanceQuickRange('prev_month')">ماه قبل</button>
                    </div>
                </div>
                <div class="perf-filter-grid">
                    <label class="perf-filter-field perf-date-field">
                        <span>از تاریخ</span>
                        <input type="text" id="performance-date-from" class="perf-filter-input" value="${dateFromJ}">
                    </label>
                    <label class="perf-filter-field perf-date-field">
                        <span>تا تاریخ</span>
                        <input type="text" id="performance-date-to" class="perf-filter-input" value="${dateToJ}">
                    </label>
                    ${canChoosePerformanceOwners ? `<div class="perf-select-field">${this.renderCustomMultiSelect('performance-owner-filter', 'مسئول', this.getPerformanceOwnerData(), filters.owner_didar_ids || [], true)}</div>` : ''}
                    <div class="perf-select-field perf-product-field">${this.renderCustomMultiSelect('performance-product-filter', 'محصول', this.getPerformanceProductData(), filters.product_ids || [], true)}</div>
                    <button onclick="app.loadPerformanceDashboard()" class="perf-apply-btn">
                        <i class="fa-solid fa-filter"></i>
                        <span>اعمال فیلتر</span>
                    </button>
                </div>
            </div>

            ${this.renderPerformanceCeoReportSection(ceoReport)}

            <div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-3 mb-6">
                ${this.renderPerformanceSummaryCard('فروش کل', formatToman(summary.total_sales_amount), `تعداد معامله: ${this.formatNumber(summary.with_sales_deals_count || 0)}`, 'مبلغ قرارداد: ' + formatToman(summary.total_contract_amount || 0), 'border-blue-500', 'performance_cards', 'sales_total')}
                ${this.renderPerformanceSummaryCard('واریزی ثبت‌شده در بازه', formatToman(summary.payment_registered_in_range_amount || 0), `معامله: ${this.formatNumber(summary.payment_registered_in_range_deal_count || 0)}`, `پرداخت: ${this.formatNumber(summary.payment_registered_in_range_receipt_count || 0)}`, 'border-sky-500', 'performance_cards', 'payment_registered_in_range_amount')}
                ${this.renderPerformanceSummaryCard('واریزی ثبت خارج بازه', formatToman(summary.payment_outside_registered_first_payment_amount || 0), `معامله: ${this.formatNumber(summary.payment_outside_registered_first_payment_deal_count || 0)}`, `پرداخت در بازه: ${this.formatNumber(summary.payment_outside_registered_first_payment_receipt_count || 0)}`, 'border-indigo-500', 'performance_cards', 'payment_outside_registered_first_payment_amount')}
                ${this.renderPerformanceSummaryCard('واریزی معاملات ناموفق', formatToman(summary.lost_deposit_amount || 0), `معامله: ${this.formatNumber(summary.lost_deposit_deal_count || 0)}`, `پرداخت: ${this.formatNumber(summary.lost_deposit_receipt_count || 0)}`, 'border-orange-500', 'performance_cards', 'lost_deposit_amount')}
                ${this.renderPerformanceSummaryCard('کل معاملات مرتبط', this.formatNumber(summary.deal_related_total_count || summary.deal_conversion_total_deals || 0), `مبلغ: ${formatToman(summary.deal_related_total_amount || 0)}`, 'ثبت‌شده در بازه + اولین واریزی در بازه', 'border-sky-500', 'performance_cards', 'deal_related_total_count')}
                ${this.renderPerformanceSummaryCard('ثبت‌شده در بازه', this.formatNumber(summary.deal_registered_in_range_count || 0), `مبلغ: ${formatToman(summary.deal_registered_in_range_amount || 0)}`, 'فقط تاریخ ثبت معامله داخل بازه', 'border-cyan-500', 'performance_cards', 'deal_registered_in_range_count')}
                ${this.renderPerformanceSummaryCard('اولین واریزی در بازه', this.formatNumber(summary.deal_outside_registered_first_payment_count || 0), `مبلغ: ${formatToman(summary.deal_outside_registered_first_payment_amount || 0)}`, 'ثبت خارج بازه', 'border-indigo-500', 'performance_cards', 'deal_outside_registered_first_payment_count')}
                ${this.renderPerformanceSummaryCard('درصد تحقق تارگت', `${this.formatNumber(summary.target_achievement_percent || 0)}%`, summary.target_has_target ? `هدف: ${formatToman(summary.target_amount || 0)}` : 'هدف تعریف نشده', `هدف روزانه: ${formatToman(summary.target_daily_amount || 0)}`, 'border-yellow-500')}
                ${this.renderPerformanceSummaryCard('مانده حساب', formatToman(summary.outstanding_amount), `تعداد بدهکار: ${this.formatNumber(summary.outstanding_count || 0)}`, `قرارداد: ${formatToman(summary.outstanding_contract_amount || 0)}`, 'border-red-500', 'performance_cards', 'outstanding_amount_sum')}
                ${this.renderPerformanceSummaryCard('تسویه‌شده کامل', this.formatNumber(summary.settled_count || 0), `مبلغ کل: ${formatToman(summary.settled_amount || 0)}`, `وصول شده: ${formatToman(summary.settled_collected_amount || 0)}`, 'border-green-500', 'performance_cards', 'settled_count')}
                ${this.renderPerformanceSummaryCard('فروش ناموفق', this.formatNumber(summary.lost_count || 0), `مبلغ کل: ${formatToman(summary.lost_amount || 0)}`, lostReasonHtml, 'border-orange-500', 'performance_cards', 'lost_count')}
                ${this.renderPerformanceSummaryCard('فروش موفق', this.formatNumber(summary.won_count || 0), `مبلغ کل: ${formatToman(summary.won_amount || 0)}`, '', 'border-emerald-500', 'performance_cards', 'won_count')}
                ${this.renderPerformanceSummaryCard('فروش جاری', this.formatNumber(summary.open_pipeline_count || 0), `پتانسیل: ${formatToman(summary.open_pipeline_amount || 0)}`, '', 'border-purple-500', 'performance_cards', 'open_pipeline_count')}
                ${this.renderPerformanceSummaryCard('نرخ تبدیل معاملات', `${this.formatNumber(summary.deal_conversion_rate || 0)}%`, `از ${this.formatNumber(summary.deal_conversion_total_deals || 0)} معامله`, `عدم تبدیل: ${this.formatNumber(summary.deal_non_conversion_rate || 0)}%`, 'border-pink-500', 'performance_cards', 'deal_conversion_rate')}
                ${this.renderPerformanceSummaryCard('نرخ تبدیل لید جدید', `${this.formatNumber(summary.lead_conversion_rate || 0)}%`, `از ${this.formatNumber(summary.lead_total_new || 0)} لید`, `عدم تبدیل: ${this.formatNumber(summary.lead_non_conversion_rate || 0)}%`, 'border-cyan-500', 'performance_cards', 'lead_conversion_rate')}
                ${this.renderPerformanceSummaryCard('معاملات بدون پرداخت', this.formatNumber(summary.without_payment_count || 0), `مجموع مبلغ: ${formatToman(summary.without_payment_amount || 0)}`, '', 'border-amber-500', 'performance_cards', 'without_payment_count')}
                ${this.renderPerformanceSummaryCard('جزئیات تبدیل لید جدید', this.formatNumber(summary.lead_converted || 0), `لید جدید: ${this.formatNumber(summary.lead_total_new || 0)}`, `عدم تبدیل: ${this.formatNumber(summary.lead_non_conversion_rate || 0)}%`, 'border-indigo-500', 'performance_cards', 'lead_converted')}
                ${this.renderPerformanceSummaryCard('میانگین زمان تبدیل', `${this.formatNumber(Number(summary.avg_conversion_days || 0).toFixed(2))} روز`, 'از ثبت معامله تا اولین پرداخت', '', 'border-emerald-500')}
                ${this.renderPerformanceComparisonCard(data.comparison || {})}
            </div>
            <div class="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-6 gap-3 mb-6">
                ${this.renderPerformanceSummaryCard('پورسانت', formatToman(repKpis.tce || 0), 'قطعی', '', 'border-emerald-500')}
            </div>

            ${this.renderPerformanceActivityPerformanceSection(activityPerformance)}

            <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4 relative" style="z-index: 1;">
                ${perfCanDailySales ? `<div class="glass p-4">
                    <div class="flex items-center justify-between mb-3">
                        <div class="text-sm text-gray-300">روند فروش روزانه (مبلغ + تعداد معامله)${this.renderSaleOsInfoTooltip(this.performanceInfoText('نمودار روزانه فروش بر اساس اولین واریزی فروش.', 'payment_records و deals.', 'برای هر روز: SUM مبلغ فروش اولیه و COUNT معاملات فروش‌شده.'), { title: 'روند فروش روزانه' })}</div>
                        ${this.renderPerformanceChartActions('dailySales', 'daily_sales')}
                    </div>
                    <div id="perf-chart-daily-sales" class="h-[360px]"></div>
                    ${this.renderPerformanceChartControls('sales', (data.charts?.daily_sales || []).length)}
                </div>` : ''}
                ${perfCanDailyActivities ? `<div class="glass p-4">
                    <div class="flex items-center justify-between mb-3">
                        <div class="text-sm text-gray-300">روند فعالیت روزانه (تجمیعی + تفکیک نوع)${this.renderSaleOsInfoTooltip(this.performanceInfoText('نمودار فعالیت‌های ثبت‌شده در هر روز.', 'activities.', 'COUNT فعالیت‌ها و گروه‌بندی بر اساس نوع فعالیت در هر تاریخ.'), { title: 'روند فعالیت روزانه' })}</div>
                        ${this.renderPerformanceChartActions('dailyActivities', 'daily_activities')}
                    </div>
                    <div id="perf-chart-daily-activities" class="h-[440px]"></div>
                    ${this.renderPerformanceChartControls('activity', (data.charts?.daily_activities || []).length)}
                </div>` : ''}
            </div>

            <div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-6 relative" style="z-index: 1;">
                ${perfCanDealStatus ? `<div class="glass p-4">
                    <div class="flex items-center justify-between mb-3">
                        <div class="text-sm text-gray-300">ترکیب وضعیت معاملات${this.renderSaleOsInfoTooltip(this.performanceInfoText('نمودار سهم وضعیت‌های اصلی معاملات.', 'deals و payment_records.', 'COUNT معاملات در وضعیت‌های جاری، دارای فروش، بدون فروش و ناموفق.'), { title: 'ترکیب وضعیت معاملات' })}</div>
                        ${this.renderPerformanceChartActions('dealStatus', 'deal_status_breakdown')}
                    </div>
                    <div id="perf-chart-deal-status" class="h-72"></div>
                </div>` : ''}
                ${perfCanProductShare ? `<div class="glass p-4">
                    <div class="flex items-center justify-between mb-3">
                        <div class="text-sm text-gray-300">سهم فروش محصولات${this.renderSaleOsInfoTooltip(this.performanceInfoText('سهم هر محصول از فروش بازه.', 'deal_products/products + payment_records.', 'SUM فروش اولیه تخصیص‌یافته به محصول / SUM کل فروش اولیه محصولات.'), { title: 'سهم فروش محصولات' })}</div>
                        ${this.renderPerformanceChartActions('productShare', 'product_sales_share')}
                    </div>
                    <div id="perf-chart-product-share" class="h-72"></div>
                </div>` : ''}
                ${perfCanFunnel ? `<div class="glass p-4">
                    <div class="flex items-center justify-between mb-3">
                        <div class="text-sm text-gray-300">قیف تبدیل${this.renderSaleOsInfoTooltip(this.performanceInfoText('نمای قیفی از مراحل تبدیل لید/معامله.', 'persons، deals و payment_records.', 'COUNT هر مرحله بر اساس تعریف stage در داده‌های KPI performance.'), { title: 'قیف تبدیل' })}</div>
                        ${this.renderPerformanceChartActions('conversionFunnel', 'conversion_funnel')}
                    </div>
                    <div id="perf-chart-conversion-funnel" class="h-72"></div>
                </div>` : ''}
            </div>

            <div class="glass p-4">
                <div class="flex flex-wrap gap-2 mb-4">${tabButtons}</div>
                <div id="performance-tab-content">${this.renderPerformanceTabContent(tabs, this.state.performanceActiveTab)}</div>
            </div>
        `;

        this.initPerformanceDatePickers();
        this.initCustomMultiSelectListeners();
        this.renderPerformanceCharts(data.charts || {});
        this.renderPerformanceCeoCharts(data.ceo_report || {});
    },

    perfMoney(value) {
        return `${this.formatNumber(Number(value || 0))} <span class="unit">تومان</span>`;
    },

    renderSaleOsInfoTooltip(content, options = {}) {
        const lines = Array.isArray(content)
            ? content.filter(Boolean).map(item => String(item))
            : String(content || '').split('\n').filter(Boolean);
        if (!lines.length) return '';
        const title = options.title ? String(options.title) : '';
        const placement = options.placement ? String(options.placement) : 'auto';
        const plain = [title, ...lines].filter(Boolean).join(' - ');
        return `
            <span class="so-info-tooltip so-info-tooltip--${this.escapeHtml(placement)}" tabindex="0" role="note"
                aria-label="${this.escapeHtml(plain)}" title="${this.escapeHtml(plain)}" onclick="event.stopPropagation()">
                <span class="so-info-tooltip-trigger">?</span>
                <span class="so-info-tooltip-panel">
                    ${title ? `<b>${this.escapeHtml(title)}</b>` : ''}
                    ${lines.map(line => `<span>${this.escapeHtml(line)}</span>`).join('')}
                </span>
            </span>
        `;
    },

    renderSaleOsLabelWithInfo(label, content, options = {}) {
        const tag = options.tag || 'span';
        const className = options.className ? ` class="${this.escapeHtml(options.className)}"` : '';
        const text = this.escapeHtml(label || '');
        return `<${tag}${className}>${text}${this.renderSaleOsInfoTooltip(content, options)}</${tag}>`;
    },

    performanceTooltipSemanticText(value) {
        let text = String(value || '').trim();
        if (!text) return '';
        const replacements = [
            [/payment_records/gi, 'پرداخت‌ها'],
            [/deals\.payments/gi, 'پرداخت‌های قدیمی معامله'],
            [/deals\/persons acquisition fields/gi, 'اطلاعات شیوه آشنایی معامله و لید'],
            [/deals acquisition fields/gi, 'اطلاعات شیوه آشنایی معامله'],
            [/deal_products\/products/gi, 'محصولات معامله'],
            [/deal_products/gi, 'محصولات معامله'],
            [/payment_records\/deals/gi, 'پرداخت‌ها و معاملات'],
            [/payment_records \+ deals/gi, 'پرداخت‌ها و معاملات'],
            [/payment_records \+ persons/gi, 'پرداخت‌ها و لیدها'],
            [/persons \+ deals \+ payment_records/gi, 'لیدها، معاملات و پرداخت‌ها'],
            [/persons، deals و payment_records/gi, 'لیدها، معاملات و پرداخت‌ها'],
            [/products/gi, 'محصولات'],
            [/\bdeals\b/gi, 'معاملات'],
            [/\bdeal\b/gi, 'معامله'],
            [/persons/gi, 'لیدها'],
            [/users/gi, 'کاربران'],
            [/activities/gi, 'فعالیت‌ها'],
            [/customer_access_requests/gi, 'درخواست‌های دسترسی مشتری'],
            [/commission_lines|commission read model/gi, 'محاسبات پورسانت'],
            [/KPI targets/gi, 'هدف‌های تعریف‌شده'],
            [/owner_didar_id/gi, 'مسئول'],
            [/register_time|created_at/gi, 'تاریخ ثبت'],
            [/first payment date|first_payment_date/gi, 'تاریخ اولین پرداخت'],
            [/payment date|payment_date/gi, 'تاریخ پرداخت'],
            [/deal_register_date|register_date/gi, 'تاریخ ثبت معامله'],
            [/first_payment/gi, 'اولین پرداخت'],
            [/payable_amount/gi, 'مبلغ قابل پرداخت معامله'],
            [/paid_amount|paid amount/gi, 'مبلغ پرداخت‌شده'],
            [/net amount/gi, 'مبلغ خالص'],
            [/cheque fields/gi, 'اطلاعات چک'],
            [/cheque_collection_status/gi, 'وضعیت وصول چک'],
            [/cheque_status/gi, 'وضعیت چک'],
            [/due\/collection dates/gi, 'تاریخ سررسید یا وصول'],
            [/deal_id/gi, 'معامله'],
            [/COUNT DISTINCT/gi, 'تعداد یکتای'],
            [/COUNT/gi, 'تعداد'],
            [/SUM/gi, 'جمع'],
            [/AVG/gi, 'میانگین'],
            [/MAX/gi, 'بیشینه'],
            [/MIN/gi, 'کمینه'],
            [/Won/gi, 'موفق'],
            [/Lost/gi, 'ناموفق'],
            [/Pending/gi, 'در جریان'],
            [/first_payment_only/gi, 'فقط اولین پرداخت'],
            [/all_payments/gi, 'همه پرداخت‌ها'],
            [/confirmed_only/gi, 'فقط پرداخت‌های تاییدشده'],
            [/ui_payment_date/gi, 'تاریخ واریزی نمایش‌داده‌شده در UI'],
            [/fallback/gi, 'پشتیبان محاسباتی'],
            [/raw snapshot/gi, 'جزئیات ثبت‌شده پرداخت'],
            [/deal creator/gi, 'ثبت‌کننده معامله'],
            [/lead source classification/gi, 'طبقه‌بندی منبع لید'],
            [/source\/detail\/topic/gi, 'شیوه آشنایی، جزئیات و موضوع محتوا'],
            [/CEO report/gi, 'گزارش مدیرعامل'],
            [/performance dashboard/gi, 'داشبورد عملکرد'],
            [/KPI\/Performance/gi, 'شاخص‌های عملکرد'],
            [/KPI performance/gi, 'شاخص‌های عملکرد'],
            [/first sales payment/gi, 'اولین پرداخت فروش'],
            [/cohort/gi, 'گروه مبنا'],
            [/bucket/gi, 'گروه'],
            [/stage/gi, 'مرحله'],
            [/pipeline/gi, 'مسیر پیگیری'],
            [/status/gi, 'وضعیت'],
            [/role classification/gi, 'طبقه‌بندی نقش ثبت‌کننده'],
            [/product lines/gi, 'ردیف‌های محصول معامله'],
            [/snapshot/gi, 'نسخه ثبت‌شده تاریخی'],
            [/method\/description/gi, 'روش یا توضیح پرداخت'],
            [/method/gi, 'روش پرداخت'],
            [/description/gi, 'توضیح'],
            [/delta/gi, 'اختلاف'],
        ];
        replacements.forEach(([pattern, replacement]) => {
            text = text.replace(pattern, replacement);
        });
        text = text.replace(/\b[a-z_]+\.[a-z_]+\b/gi, (match) => match.replace('.', ' '));
        text = text.replace(/\s+\+\s+/g, ' و ');
        text = text.replace(/([\u0600-\u06FF]+)\./g, '$1 ');
        text = text.replace(/\s+/g, ' ').trim();
        return text;
    },

    performanceInfoText(definition, source, formula, notes = '') {
        const lines = [];
        const def = this.performanceTooltipSemanticText(definition);
        const src = this.performanceTooltipSemanticText(source);
        const frm = this.performanceTooltipSemanticText(formula);
        const nts = this.performanceTooltipSemanticText(notes);
        if (def) lines.push(`تعریف: ${def}`);
        if (src) lines.push(`دامنه و مبنا: ${src}`);
        if (frm) lines.push(`فرمول/قاعده: ${frm}`);
        if (nts) lines.push(`نکته: ${nts}`);
        return lines;
    },

    getPerformanceSummaryCardInfo(metricKey, title) {
        const map = {
            sales_total: this.performanceInfoText(
                'مبلغ فروش بازه؛ یعنی پولی که به عنوان فروش اولیه معامله در بازه انتخاب‌شده شناسایی شده است.',
                'معاملات غیرناموفقی که اولین پرداخت فروش معتبرشان داخل بازه است؛ اگر وضعیت CRM هنوز Pending باشد ولی پرداخت معتبر دارد، برای گزارش مالی فروش محسوب می‌شود. پرداخت چک وصول‌نشده، سودران و معامله Lost در این عدد وارد نمی‌شود.',
                'جمع پرداخت‌های فروش در روز اولین پرداخت معامله. اگر چند پرداخت فروش در همان روز اول ثبت شده باشند، همه همان روز در فروش حساب می‌شوند.',
                'پرداخت‌های مانده حساب روزهای بعد در این کارت نمی‌آیند تا فروش جدید با وصول مانده حساب قاطی نشود؛ پرداخت معامله ناموفق در کارت واریزی معاملات ناموفق دیده می‌شود.'
            ),
            payment_registered_in_range_amount: this.performanceInfoText(
                'پرداخت‌های بازه برای معاملاتی که خود معامله نیز در همین بازه ثبت شده است.',
                'تمام پرداخت‌های معتبر داخل بازه، با شرط اینکه تاریخ ثبت معامله هم بین تاریخ شروع و پایان فیلتر باشد.',
                'جمع مبلغ پرداخت‌های معتبر این گروه؛ تعداد معامله = تعداد معاملات یکتا، تعداد پرداخت = تعداد ردیف پرداخت.',
                'این کارت برای جدا کردن عملکرد معاملات جدید همان بازه از وصول‌های مربوط به معاملات قدیمی است.'
            ),
            payment_outside_registered_first_payment_amount: this.performanceInfoText(
                'پرداخت‌های معاملاتی که قبل از بازه ثبت شده‌اند، اما اولین پرداخت فروششان در همین بازه اتفاق افتاده است.',
                'معامله از نظر ثبت، قدیمی است؛ از نظر فروش، در بازه فعلی فعال شده است.',
                'جمع پرداخت‌های معتبر داخل بازه برای این معاملات؛ شرط ورود معامله این است که اولین پرداخت فروش آن داخل بازه باشد.',
                'این عدد نباید با معاملات ثبت‌شده در بازه جمع شود مگر وقتی هدف، کل واریزی‌های بازه باشد.'
            ),
            lost_deposit_amount: this.performanceInfoText(
                'واریزی معاملات ناموفق؛ پول ثبت‌شده روی معامله‌ای که وضعیت CRM آن Lost است.',
                'پرداخت معتبر داخل بازه است، اما چون معامله ناموفق مانده، از فروش و مانده حساب جدا می‌شود.',
                'جمع مبلغ پرداخت‌های معتبر معامله‌های Lost در بازه؛ تعداد معامله = معاملات یکتای Lost دارای پرداخت، تعداد پرداخت = رسیدهای همین گروه.',
                'این کارت نباید پنهان شود چون بخشی از کل واریزی‌هاست و معمولا نشانه نیاز به اصلاح وضعیت یا بازبینی داده است.'
            ),
            deal_related_total_count: this.performanceInfoText(
                'کل معاملات مرتبط با بازه عملکرد.',
                'شامل دو گروه است: معاملات ثبت‌شده داخل بازه و معاملات قدیمی‌تری که اولین فروششان داخل بازه رخ داده است.',
                'تعداد معاملات داخل بازه ثبت + تعداد معاملات خارج بازه با اولین پرداخت فروش داخل بازه.',
                'این عدد با کارت «کل معاملات ثبت‌شده» CEO یکی نیست، چون CEO در آن کارت فقط ثبت‌شده‌های داخل بازه را نشان می‌دهد.'
            ),
            deal_registered_in_range_count: this.performanceInfoText(
                'تعداد معاملاتی که خودشان در بازه انتخاب‌شده ثبت شده‌اند.',
                'مبنای این کارت تاریخ ثبت معامله است؛ پرداخت داشتن یا نداشتن معامله شرط ورود به این عدد نیست.',
                'تعداد = معاملات با تاریخ ثبت بین تاریخ شروع و پایان، بعد از اعمال فیلتر مسئول و محصول.',
                'این عدد برای سنجش حجم ورودی فروش است و نباید با تعداد پرداخت یا تعداد فروش یکی فرض شود.'
            ),
            deal_outside_registered_first_payment_count: this.performanceInfoText(
                'تعداد معاملات قدیمی‌تر که فروششان در بازه فعلی شروع شده است.',
                'معامله قبل از بازه ثبت شده، اما اولین پرداخت فروش معتبر آن داخل بازه انتخاب‌شده است.',
                'تعداد = معاملات خارج بازه با اولین پرداخت فروش داخل بازه.',
                'این کارت برای جدا کردن فروش فعال‌شده از قبل از معاملات جدید ثبت‌شده همین بازه است.'
            ),
            outstanding_amount_sum: this.performanceInfoText(
                'مبلغی که از معاملات دارای پرداخت هنوز وصول نشده است.',
                'برای هر معامله، مبلغ قابل پرداخت با کل پرداخت‌های معتبر همان معامله مقایسه می‌شود. معاملات ناموفق وارد مانده حساب نمی‌شوند.',
                'برای هر معامله: مانده = مبلغ قابل پرداخت - کل پرداخت معتبر. جمع کارت = جمع مانده‌های مثبت.',
                'چک وصول‌نشده پرداخت قطعی محسوب نمی‌شود؛ پرداخت سودران هم در مانده حساب فروش معمولی مخلوط نمی‌شود.'
            ),
            settled_count: this.performanceInfoText(
                'تعداد معاملاتی که بدهی مالی‌شان کامل بسته شده است.',
                'معامله‌ای تسویه‌شده است که کل پرداخت‌های معتبرش به مبلغ قابل پرداخت معامله برسد یا از آن عبور کند.',
                'تعداد معاملات با کل پرداخت معتبر >= مبلغ قابل پرداخت معامله.',
                'این کارت وضعیت مالی معامله را می‌سنجد، نه صرفا برچسب وضعیت فروش.'
            ),
            lost_count: this.performanceInfoText(
                'تعداد معاملاتی که در دامنه عملکرد ناموفق شده‌اند.',
                'مبنای ورود، وضعیت نهایی ناموفق یا از دست‌رفته بعد از اعمال فیلترهای داشبورد است.',
                'تعداد = معاملات ناموفق دامنه عملکرد؛ مبلغ = مبلغ قابل پرداخت یا مبلغ مرتبط همان معاملات طبق خروجی کارت.',
                'این کارت فروش قطعی را کم نمی‌کند؛ فقط کیفیت و شکست‌های مسیر فروش را نشان می‌دهد.'
            ),
            won_count: this.performanceInfoText(
                'تعداد معاملات با وضعیت فروش موفق در دامنه عملکرد.',
                'در داشبورد عمومی، دامنه عملکرد شامل معاملات ثبت‌شده داخل بازه و معاملات قدیمی با اولین پرداخت فروش داخل بازه است.',
                'تعداد معاملات دارای وضعیت موفق در دامنه عملکرد؛ مبلغ کارت = جمع مبلغ قابل پرداخت همان معاملات.',
                'ممکن است با کارت CEO «معاملات موفق» فرق داشته باشد اگر CEO فقط معاملات ثبت‌شده داخل بازه را نمایش دهد.'
            ),
            open_pipeline_count: this.performanceInfoText(
                'تعداد معاملات باز که هنوز نتیجه نهایی نگرفته‌اند.',
                'معاملاتی که نه موفق نهایی شده‌اند و نه ناموفق، و همچنان در مسیر پیگیری قرار دارند.',
                'تعداد = معاملات غیرموفق و غیرناموفق در دامنه فیلتر؛ مبلغ = پتانسیل مالی یا مبلغ قابل پرداخت آن‌ها.',
                'این کارت فروش انجام‌شده نیست؛ ظرفیت پیگیری و تبدیل آینده را نشان می‌دهد.'
            ),
            deal_conversion_rate: this.performanceInfoText(
                'درصد تبدیل معاملات دامنه عملکرد به فروش.',
                'صورت کسر، معاملاتی است که اولین پرداخت فروش معتبر دارند؛ مخرج، کل معاملات مرتبط با بازه عملکرد است.',
                'نرخ تبدیل = تعداد معاملات فروش‌شده / کل معاملات مرتبط × ۱۰۰.',
                'اگر معاملات قدیمی با اولین پرداخت داخل بازه در دامنه عملکرد باشند، در مخرج و صورت طبق همان قاعده لحاظ می‌شوند.'
            ),
            lead_conversion_rate: this.performanceInfoText(
                'درصد تبدیل لیدهای جدید به فروش.',
                'لید جدید یعنی لیدی که هنگام ثبت اولین معامله‌اش قبلا در CRM وجود نداشته است.',
                'نرخ تبدیل لید = تعداد لیدهای جدیدی که به معامله دارای فروش رسیده‌اند / کل لیدهای جدید × ۱۰۰.',
                'این شاخص رفتار لید را می‌سنجد و ممکن است با نرخ تبدیل معامله متفاوت باشد.'
            ),
            without_payment_count: this.performanceInfoText(
                'تعداد معاملات دامنه عملکرد که هنوز هیچ پرداخت معتبر ندارند.',
                'معامله ممکن است فعالیت یا وضعیت پیگیری داشته باشد، اما تا وقتی پرداخت معتبر ندارد در این کارت می‌آید.',
                'تعداد = معاملات داخل دامنه فیلتر بدون هیچ پرداخت تاییدشده یا معتبر.',
                'این کارت برای تشخیص فاصله بین ثبت/پیگیری معامله و تبدیل مالی استفاده می‌شود.'
            ),
            lead_converted: this.performanceInfoText(
                'تعداد لیدهای جدیدی که حداقل یک فروش معتبر ساخته‌اند.',
                'برای هر لید جدید، بررسی می‌شود آیا معامله‌ای با پرداخت فروش معتبر دارد یا نه.',
                'تعداد = لیدهای جدید یکتا با حداقل یک معامله دارای اولین پرداخت فروش.',
                'یک لید حتی اگر چند معامله موفق داشته باشد در این عدد یک بار شمرده می‌شود.'
            )
        };
        if (metricKey && map[metricKey]) return map[metricKey];
        if (title === 'درصد تحقق تارگت') {
            return this.performanceInfoText('درصد تحقق هدف فروش بازه.', 'هدف فروش تعریف‌شده برای همین بازه و مبلغ فروش محاسبه‌شده با قاعده فروش اولیه.', 'درصد تحقق = فروش کل بازه / تارگت بازه × ۱۰۰.', 'اگر برای بازه هدف تعریف نشده باشد، درصد صفر یا بدون مقدار نمایش داده می‌شود.');
        }
        if (title === 'میانگین زمان تبدیل') {
            return this.performanceInfoText('میانگین زمان لازم از ثبت معامله تا اولین پرداخت فروش.', 'معاملاتی که اولین پرداخت فروش معتبر دارند و تاریخ ثبت معامله‌شان مشخص است.', 'برای هر معامله: تاریخ اولین پرداخت فروش - تاریخ ثبت معامله؛ سپس میانگین این فاصله‌ها نمایش داده می‌شود.', 'این عدد سرعت تبدیل را نشان می‌دهد؛ پرداخت‌های مانده حساب بعدی در آن اثری ندارند.');
        }
        if (title === 'پورسانت') {
            return this.performanceInfoText('پورسانت قطعی محاسبه‌شده برای بازه.', 'محاسبات پورسانت تاییدشده پس از اعمال قواعد فروش، پرداخت و بازه زمانی.', 'جمع پورسانت‌های قطعی واجد شرایط در بازه انتخاب‌شده.', 'این عدد از فروش خام جداست؛ فقط ردیف‌هایی که وارد چرخه پورسانت قطعی شده‌اند در آن اثر دارند.');
        }
        return this.performanceInfoText(
            title || 'کارت عملکرد',
            'شاخص‌های همین داشبورد بعد از اعمال فیلتر تاریخ، مسئول و محصول.',
            'عدد کارت بر اساس تعریف همان شاخص از شمارش، جمع مبلغ، میانگین یا درصد محاسبه می‌شود.',
            'برای کنترل صحت، به متن کارت و Drill-down همان کارت نگاه کنید؛ دامنه هر کارت ممکن است فروش، پرداخت، معامله یا لید باشد.'
        );
    },

    getPerformanceCeoMetricInfo(metricKey, label) {
        const map = {
            total_deposits_amount: this.performanceInfoText('کل واریزی‌ها؛ همه پول‌های معتبر ثبت‌شده در بازه انتخاب‌شده.', 'تمام پرداخت‌های معتبر داخل بازه، مستقل از اینکه معامله در همین بازه ثبت شده یا از قبل وجود داشته است.', 'کل واریزی‌ها = واریزی فروش + واریزی مانده حساب + چک‌های پاس‌شده + پرداخت‌های سودران + واریزی معاملات ناموفق.', 'اگر یک پرداخت معتبر در بازه باشد، باید در یکی از تفکیک‌های همین ردیف هم دیده شود تا عدد کل قابل کنترل باشد.'),
            sales_deposit_amount: this.performanceInfoText('واریزی فروش؛ بخش فروش جدید از کل واریزی‌ها.', 'پرداخت‌های فروش غیرچک، غیرسودران و غیرناموفق که به روز اولین پرداخت فروش معامله تعلق دارند. برای گزارش مالی، معامله دارای پرداخت معتبر موفق محسوب می‌شود حتی اگر وضعیت CRM هنوز Pending مانده باشد.', 'جمع پرداخت‌های روز اول فروش معامله؛ اگر پرداخت دوم یا سوم همان روز اول باشد، همچنان فروش حساب می‌شود.', 'پرداخت‌های روزهای بعد به مانده حساب می‌روند؛ پرداخت‌های معامله Lost جدا در واریزی معاملات ناموفق دیده می‌شوند.'),
            balance_deposit_amount: this.performanceInfoText('واریزی مانده حساب؛ وصول‌هایی که بعد از روز فروش اولیه انجام شده‌اند.', 'پرداخت‌های غیرچک، غیرسودران و غیرناموفق برای معاملاتی که قبلا اولین پرداخت فروش داشته‌اند. وضعیت CRM اگر هنوز Pending باشد مانع احتساب مالی نیست.', 'جمع پرداخت‌هایی که تاریخشان با تاریخ اولین پرداخت فروش همان معامله برابر نیست.', 'چک‌ها اینجا وارد نمی‌شوند تا مانده حساب نقدی از وصول چک جدا بماند.'),
            cheque_collected_amount: this.performanceInfoText('واریزی چک‌ها؛ فقط چک‌هایی که وصول یا پاس شده‌اند.', 'چک‌هایی که وضعیت وصولشان قطعی شده و تاریخ وصولشان در بازه انتخابی است.', 'جمع مبلغ چک‌های پاس‌شده داخل بازه؛ تعداد پرداخت = تعداد چک‌های پاس‌شده.', 'چک‌های سررسید نشده یا بلاتکلیف در وضعیت چک‌ها دیده می‌شوند، نه در واریزی نقدی.'),
            sudran_deposit_amount: this.performanceInfoText('پرداخت‌های سودران؛ پرداخت‌هایی که ماهیت سودران دارند.', 'پرداخت‌هایی که از مسیر یا توضیح پرداخت به عنوان سودران تشخیص داده می‌شوند.', 'جمع مبلغ پرداخت‌های سودران معتبر داخل بازه.', 'سودران جدا از فروش CRM/شخصی نمایش داده می‌شود تا منبع فروش اشتباه تفسیر نشود.'),
            lost_deposit_amount: this.performanceInfoText('واریزی معاملات ناموفق؛ پرداخت‌هایی که به معامله با وضعیت ناموفق متصل هستند.', 'این پرداخت‌ها معتبر و داخل بازه‌اند، اما چون معامله در CRM ناموفق است جدا نگه داشته می‌شوند تا با فروش یا مانده حساب موفق قاطی نشوند.', 'جمع مبلغ پرداخت‌های معتبر معامله‌های Lost در بازه؛ تعداد پرداخت = تعداد رسیدهای معتبر همین گروه.', 'این کارت برای کشف خطای عملیاتی یا نیاز به بازبینی وضعیت معامله است: پول ثبت شده، اما معامله ناموفق مانده است.'),
            outstanding_balance_total: this.performanceInfoText('کل مانده حساب‌های باز از اسفند به بعد.', 'معاملات غیرناموفق که پرداخت داشته‌اند اما کل پرداخت معتبرشان کمتر از مبلغ قابل پرداخت است.', 'برای هر معامله: مانده = مبلغ قابل پرداخت - کل پرداخت معتبر؛ جمع کارت = جمع مانده‌های مثبت.', 'ماه‌های قبل از اسفند در این شاخص محاسبه و نمایش داده نمی‌شوند.'),
            outstanding_balance_month: this.performanceInfoText('مانده حساب همان ماه شمسی.', 'معامله‌ها بر اساس ماه شمسی اولین پرداخت فروششان دسته‌بندی می‌شوند.', 'جمع مانده معاملات همان ماه = جمع مبلغ قابل پرداخت منهای پرداخت‌های معتبر هر معامله.', 'این ماه، ماه فروش اولیه معامله است؛ نه لزوما ماه آخرین پرداخت.'),
            all_cheques_count: this.performanceInfoText('نمای کل چک‌های ثبت‌شده در وضعیت چک‌ها.', 'تمام چک‌هایی که بعد از فیلترهای فعال در دامنه گزارش چک قرار می‌گیرند.', 'تعداد = تعداد چک‌ها؛ مبلغ = جمع مبلغ چک‌ها؛ معامله = تعداد معاملات یکتای دارای چک.', 'این کارت وضعیت چک را نشان می‌دهد، نه لزوما واریزی قطعی. فقط چک پاس‌شده وارد واریزی چک‌ها می‌شود.'),
            cheque_state: this.performanceInfoText('کارت تفکیکی وضعیت چک.', 'چک‌ها بر اساس برگشتی، سررسید رسیده و تعیین‌تکلیف نشده، بدون تاریخ سررسید، یا ماه‌های آینده دسته‌بندی می‌شوند.', 'تعداد و مبلغ هر کارت از چک‌های همان وضعیت محاسبه می‌شود.', 'ماه‌های بدون داده نمایش داده نمی‌شوند.'),
            deposit_registered_in_range_amount: this.performanceInfoText('پرداخت‌های معاملاتی که در همین بازه ثبت شده‌اند.', 'همه پرداخت‌های معتبر داخل بازه، فقط برای معامله‌هایی که تاریخ ثبتشان هم داخل بازه است.', 'جمع مبلغ پرداخت‌های معتبر این گروه.', 'این کارت توضیح می‌دهد چه مقدار از کل واریزی مربوط به معاملات تازه ثبت‌شده همین بازه بوده است.'),
            deposit_outside_registered_first_payment_amount: this.performanceInfoText('پرداخت معاملات قدیمی که فروششان در این بازه شروع شده است.', 'معامله قبل از بازه ثبت شده، اما اولین پرداخت فروش آن داخل بازه اتفاق افتاده است.', 'جمع پرداخت‌های معتبر داخل بازه برای این گروه.', 'این کارت از بالا جدا شده تا فروش جدیدِ معامله قدیمی با معاملات ثبت‌شده همین بازه قاطی نشود.')
        };
        return map[metricKey] || this.performanceInfoText(
            label || 'کارت مدیرعامل',
            'داده‌های گزارش مدیرعامل بعد از اعمال فیلترهای فعال داشبورد.',
            'عدد کارت از شمارش یا جمع همان گروه محاسبه می‌شود؛ درصدها نسبت به کل همان ردیف یا بخش هستند.',
            'برای کنترل صحت، کارت‌های تفکیکی باید یا با کارت کل همان ردیف جمع شوند یا در متن راهنما توضیح بدهند چرا هم‌پوشانی دارند.'
        );
    },

    getPerformanceTableColumnInfo(tableId, key, label, column = {}) {
        if (column?.description) return column.description;
        const common = {
            date: this.performanceInfoText('تاریخ ردیف.', 'تاریخی که برای همان جدول مبنای گزارش انتخاب شده است؛ مثل تاریخ پرداخت، تاریخ ثبت معامله یا تاریخ فعالیت.', 'ردیف‌ها بر اساس همان تاریخ گروه‌بندی می‌شوند.', 'برای بازه‌های شمسی، ابتدا مرز ماه شمسی به بازه میلادی معادل تبدیل می‌شود و سپس رکوردهای داخل آن بازه خوانده می‌شوند.'),
            owner_name: this.performanceInfoText('کارشناس یا مسئول رکورد.', 'مسئولی که طبق قاعده همان گزارش به معامله، پرداخت یا فعالیت نسبت داده شده است.', 'ردیف‌ها بر اساس مسئول گروه‌بندی می‌شوند و اعداد هر مسئول جدا جمع می‌شود.', 'اگر گزارش بر اساس پرداخت باشد، مسئول از معامله مرتبط با پرداخت تفسیر می‌شود.'),
            product_name: this.performanceInfoText('نام محصول مرتبط.', 'محصولی که روی معامله ثبت شده یا در نسخه تاریخی همان فروش ذخیره شده است.', 'ردیف‌ها بر اساس نام محصول گروه‌بندی می‌شوند.', 'اگر یک معامله چند محصول داشته باشد، هر محصول می‌تواند ردیف جدا بگیرد.'),
            amount: this.performanceInfoText(label || 'مبلغ', 'مبلغ‌های معتبر داخل دامنه همین ردیف.', 'مبلغ = جمع مبلغ‌های واجد شرایط برای گروه همین ردیف.', 'مبنای مبلغ در هر جدول متفاوت است؛ ممکن است فروش اولیه، کل پرداخت، مانده حساب یا مبلغ چک باشد.'),
            count: this.performanceInfoText(label || 'تعداد', 'رکوردها یا موجودیت‌های واجد شرایط همین جدول.', 'تعداد = شمارش رکوردها یا شمارش یکتای معاملات/لیدها طبق تعریف ستون.', 'اگر ستون درباره معامله یکتا باشد، چند پرداخت یک معامله فقط یک بار شمرده می‌شود.'),
            percent: this.performanceInfoText(label || 'درصد', 'سهم همین ردیف از کل همان ستون در جدول.', 'درصد = مقدار ردیف / جمع کل همان ستون × ۱۰۰.', 'درصدهای جدول باید در ردیف جمع کل به ۱۰۰٪ برسند، مگر ستون ذاتا هم‌پوشانی داشته باشد.')
        };
        if (common[key]) return common[key];
        if (String(key || '').includes('rate') || column.type === 'percent') {
            const numerator = column.numeratorKey || 'صورت مرتبط';
            const denominator = column.denominatorKey || 'مخرج مرتبط';
            return this.performanceInfoText(label, 'ردیف‌های تجمیع‌شده همین جدول.', `(${numerator} / ${denominator}) × ۱۰۰.`);
        }
        if (column.type === 'amount') return this.performanceInfoText(label, 'مبلغ‌های واجد شرایط بعد از فیلترهای فعال.', 'جمع مبلغ‌های گروه همین ردیف.', 'نوع مبلغ را از عنوان جدول تشخیص دهید: فروش، پرداخت، چک، مانده یا پورسانت.');
        if (column.type === 'count') return this.performanceInfoText(label, 'رکوردها یا موجودیت‌های واجد شرایط همین جدول.', 'شمارش ردیف‌ها، معاملات، لیدها یا فعالیت‌ها طبق تعریف ستون.', 'اگر ستون «معامله» باشد، شمارش معمولا یکتا است؛ اگر ستون «پرداخت» باشد، هر پرداخت جدا شمرده می‌شود.');
        if (column.type === 'day') return this.performanceInfoText(label, 'فاصله زمانی بین دو رخداد مرتبط.', 'میانگین تعداد روزهای بین تاریخ شروع و تاریخ پایان تعریف‌شده برای همان شاخص.', 'این ستون برای سرعت تبدیل یا زمان رسیدگی استفاده می‌شود، نه برای حجم فروش.');
        return this.performanceInfoText(label || key, 'مقدار تجمیع‌شده در همین جدول.', 'بر اساس گروه‌بندی و فیلترهای فعال داشبورد محاسبه می‌شود.', 'اگر این ستون درصد یا مبلغ دارد، ردیف جمع کل پایین جدول برای کنترل صحت آن استفاده می‌شود.');
    },

    getPerformanceCeoTableColumnInfo(tableId, key, label) {
        const map = {
            marketing_sources: {
                acquaintance_source: this.performanceInfoText('شیوه آشنایی؛ منبعی که نشان می‌دهد مشتری از چه کانالی وارد فروش شده است.', 'پرداخت‌های معتبر بازه بر اساس شیوه آشنایی معامله یا لید گروه‌بندی می‌شوند.', 'هر پرداخت به گروه شیوه آشنایی معامله خودش نسبت داده می‌شود.', 'اگر مقدار خام خالی باشد، در گروه نامشخص یا بدون جزئیات نمایش داده می‌شود.'),
                acquaintance_detail: this.performanceInfoText('جزئیات شیوه آشنایی؛ توضیح دقیق‌تر همان کانال جذب.', 'این ستون کنار شیوه آشنایی و موضوع محتوا، گروه بازاریابی دقیق‌تری می‌سازد.', 'ردیف جدول = ترکیب شیوه آشنایی + جزئیات + موضوع محتوا.', 'برای تحلیل کمپین‌ها، درصد مبلغ مهم‌تر از صرفا تعداد پرداخت است.'),
                content_topic: this.performanceInfoText('موضوع محتوا؛ محتوایی که به جذب یا تبدیل مشتری نسبت داده شده است.', 'پرداخت‌ها بر اساس موضوع محتوای ثبت‌شده روی معامله یا لید دسته‌بندی می‌شوند.', 'هر موضوع سهم خودش را از تعداد پرداخت، تعداد معامله و مبلغ واریزی می‌گیرد.', 'موضوع خالی به عنوان بدون موضوع نمایش داده می‌شود.'),
                payment_count: this.performanceInfoText('تعداد پرداخت‌های ثبت‌شده برای این گروه بازاریابی.', 'هر رسید/پرداخت معتبر داخل بازه یک بار شمرده می‌شود، حتی اگر چند پرداخت برای یک معامله باشد.', 'درصد = تعداد پرداخت‌های این ردیف / کل پرداخت‌های جدول بازاریابی × ۱۰۰.', 'جمع درصدهای ستون پرداخت باید ۱۰۰٪ شود؛ ردیف سایر موارد برای نگه‌داشتن همین جمع استفاده می‌شود.'),
                deal_count: this.performanceInfoText('تعداد معاملات یکتایی که در این گروه حداقل یک پرداخت دارند.', 'اگر یک معامله چند پرداخت داشته باشد، در تعداد معامله فقط یک بار حساب می‌شود.', 'درصد = تعداد معاملات یکتای این ردیف / کل معاملات یکتای پرداخت‌دار جدول × ۱۰۰.', 'به همین دلیل درصد تعداد معامله ممکن است با درصد تعداد پرداخت فرق داشته باشد.'),
                amount: this.performanceInfoText('مبلغ واریزی منسوب به این گروه بازاریابی.', 'همه پرداخت‌های معتبر بازه در گروه شیوه آشنایی خودشان جمع می‌شوند.', 'درصد = مبلغ این ردیف / کل مبلغ واریزی‌های جدول بازاریابی × ۱۰۰.', 'این ستون برای فهم کیفیت مالی هر کانال مهم‌تر از تعداد خام لید یا پرداخت است.')
            },
            product_sales: {
                product_name: this.performanceInfoText('نام محصول یا خدمت فروخته‌شده.', 'محصول از ردیف‌های محصول معامله یا نسخه تاریخی ثبت‌شده روی معامله خوانده می‌شود.', 'هر معامله فروش‌شده می‌تواند یک یا چند محصول داشته باشد.', 'اگر محصول در چند نام مشابه ثبت شده باشد، هر نام همان‌طور که در داده آمده جدا دیده می‌شود.'),
                sold_count: this.performanceInfoText('تعداد واحد محصول فروخته‌شده.', 'برای هر محصول، تعداد ثبت‌شده روی ردیف محصول معامله جمع می‌شود.', 'جمع تعداد = مجموع تعداد محصول در معاملات فروش‌شده یا سودران‌پرداخت داخل بازه.', 'این عدد لزوما برابر تعداد معامله نیست، چون یک معامله می‌تواند چند محصول یا چند واحد داشته باشد.'),
                deal_count: this.performanceInfoText('تعداد معاملات یکتایی که شامل این محصول هستند.', 'هر معامله برای هر محصول فقط یک بار شمرده می‌شود.', 'تعداد معامله محصول = تعداد معاملات یکتای دارای این محصول.', 'اگر یک معامله چند محصول داشته باشد، در چند ردیف محصول ظاهر می‌شود؛ پس جمع این ستون می‌تواند از تعداد کل معاملات بیشتر شود.'),
                crm_lead_count: this.performanceInfoText('تعداد معاملات این محصول که منبعشان CRM است.', 'منبع معامله بر اساس ثبت‌کننده و طبقه‌بندی لید مشخص می‌شود.', 'درصد داخل سلول = تعداد معاملات CRM این محصول / تعداد معاملات همین محصول × ۱۰۰.', 'این عدد تعداد معامله است، نه تعداد پرداخت.'),
                personal_lead_count: this.performanceInfoText('تعداد معاملات این محصول که لید شخصی محسوب شده‌اند.', 'لید شخصی یعنی فروش یا ثبت معامله به شخص/کارشناس نسبت داده شده، نه مسیر CRM عمومی.', 'درصد داخل سلول = تعداد معاملات شخصی این محصول / تعداد معاملات همین محصول × ۱۰۰.', 'CRM + شخصی + سودران باید توضیح‌دهنده ترکیب منبع همان محصول باشد.'),
                sudran_lead_count: this.performanceInfoText('تعداد معاملات این محصول که پرداخت سودران دارند.', 'معامله وقتی سودران حساب می‌شود که پرداخت سودران معتبر در بازه داشته باشد.', 'درصد داخل سلول = تعداد معاملات سودران این محصول / تعداد معاملات همین محصول × ۱۰۰.', 'سودران در این جدول جدا می‌شود تا با CRM یا لید شخصی قاطی نشود.'),
                crm_amounts: this.performanceInfoText('تفکیک واریزی‌های CRM مرتبط با این محصول.', 'پرداخت‌های معامله به محصول‌های همان معامله تخصیص داده می‌شوند.', 'فروش = سهم محصول از واریزی فروش؛ مانده = سهم محصول از واریزی مانده؛ چک = سهم محصول از چک پاس‌شده؛ ناموفق = پرداخت معامله Lost.', 'وقتی معامله چند محصول دارد، مبلغ پرداخت بین محصول‌ها بر اساس سهم مبلغ محصول از معامله پخش می‌شود.'),
                personal_amounts: this.performanceInfoText('تفکیک واریزی‌های لید شخصی مرتبط با این محصول.', 'همان قاعده CRM اعمال می‌شود، اما فقط برای معاملات منسوب به لید شخصی.', 'فروش + مانده + چک + ناموفق نشان می‌دهد این محصول چه نوع وصولی از لیدهای شخصی داشته است.', 'این ستون برای مقایسه کیفیت فروش شخصی با CRM استفاده می‌شود.'),
                sudran_amount: this.performanceInfoText('مبلغ سودران تخصیص‌یافته به این محصول.', 'فقط پرداخت‌های سودران مرتبط با معاملات این محصول در بازه جمع می‌شوند.', 'جمع سودران محصول = سهم محصول از پرداخت‌های سودران معامله.', 'سودران بخش فروش اولیه معمولی نیست و جدا گزارش می‌شود.'),
                payment_amount: this.performanceInfoText('کل پرداخت مرتبط با این محصول.', 'همه انواع پرداخت معتبر مربوط به معاملات این محصول در بازه لحاظ می‌شود.', 'کل پرداخت = فروش + مانده + چک پاس‌شده + سودران تخصیص‌یافته به محصول.', 'این ستون با فروش کل فرق دارد چون پرداخت‌های مانده و سودران را هم نشان می‌دهد.'),
                amount: this.performanceInfoText('فروش کل محصول.', 'فقط بخش فروش اولیه معاملات این محصول را نشان می‌دهد.', 'فروش کل محصول = جمع واریزی فروش اولیه تخصیص‌یافته به محصول.', 'پرداخت مانده حساب محصولی که فروش اولیه‌اش قبل از بازه بوده، در فروش محصول این بازه نباید وارد شود.')
            },
            agent_sales: {
                owner_name: this.performanceInfoText('کارشناس یا مسئول فروش.', 'پرداخت‌ها و معاملات بر اساس مسئول فعلی/منتسب معامله گروه‌بندی می‌شوند.', 'هر ردیف عملکرد مالی یک کارشناس را در بازه نشان می‌دهد.', 'اگر مالک پرداخت با مالک معامله متفاوت باشد، گزارش بر مبنای مسئول فروش تعریف‌شده در داشبورد عمل می‌کند.'),
                sales_deal_count: this.performanceInfoText('تعداد فروش کارشناس.', 'معامله وقتی فروش حساب می‌شود که اولین واریزی فروش آن داخل بازه باشد.', 'تعداد فروش = تعداد معاملات یکتای دارای اولین واریزی فروش داخل بازه.', 'این تعداد با تعداد پرداخت فرق دارد؛ یک معامله می‌تواند چند پرداخت داشته باشد.'),
                deal_count: this.performanceInfoText('تعداد معاملات دارای واریزی برای کارشناس.', 'هر معامله‌ای که حداقل یک پرداخت معتبر در بازه داشته باشد یک بار شمرده می‌شود.', 'تعداد معامله پرداخت‌دار = تعداد معاملات یکتای دارای پرداخت معتبر در بازه.', 'این ستون مبنای درصدهای CRM/شخصی/سودران همین ردیف است.'),
                crm_lead_count: this.performanceInfoText('تعداد معاملات پرداخت‌دار کارشناس که منبعشان CRM است.', 'منبع معامله بر اساس مسیر ثبت یا طبقه‌بندی لید تعیین می‌شود.', 'درصد = تعداد معاملات CRM کارشناس / کل معاملات پرداخت‌دار کارشناس × ۱۰۰.', 'این عدد برای فهم وابستگی فروش کارشناس به CRM استفاده می‌شود.'),
                personal_lead_count: this.performanceInfoText('تعداد معاملات پرداخت‌دار کارشناس که لید شخصی هستند.', 'لید شخصی یعنی فروش از مسیر شخص/کارشناس آمده، نه مسیر CRM عمومی.', 'درصد = تعداد معاملات شخصی کارشناس / کل معاملات پرداخت‌دار کارشناس × ۱۰۰.', 'این عدد تعداد معامله است، نه مبلغ و نه تعداد پرداخت.'),
                sudran_lead_count: this.performanceInfoText('تعداد معاملات پرداخت‌دار کارشناس که پرداخت سودران دارند.', 'پرداخت سودران جدا از CRM و لید شخصی نمایش داده می‌شود.', 'درصد = تعداد معاملات سودران کارشناس / کل معاملات پرداخت‌دار کارشناس × ۱۰۰.', 'اگر یک معامله هم پرداخت عادی و هم سودران داشته باشد، در مبلغ‌ها طبق نوع پرداخت تفکیک می‌شود.'),
                crm_amounts: this.performanceInfoText('واریزی‌های منسوب به CRM برای این کارشناس.', 'پرداخت‌های معاملات CRM کارشناس به فروش، مانده، چک و ناموفق تقسیم می‌شوند.', 'فروش = پرداخت‌های روز اول فروش؛ مانده = پرداخت‌های بعد از روز اول؛ چک = چک‌های پاس‌شده؛ ناموفق = پرداخت معامله Lost.', 'این تفکیک کمک می‌کند بفهمیم فروش جدید، وصول مطالبات و پرداخت‌های نیازمند بازبینی هر کارشناس چقدر بوده است.'),
                personal_amounts: this.performanceInfoText('واریزی‌های منسوب به لید شخصی برای این کارشناس.', 'مانند CRM، اما فقط برای معاملات لید شخصی.', 'فروش + مانده + چک + ناموفق = کل وصول لیدهای شخصی غیرسودران کارشناس.', 'برای مقایسه عملکرد شخصی کارشناس با فروش‌های CRM استفاده می‌شود.'),
                sudran_amount: this.performanceInfoText('مبلغ پرداخت سودران منتسب به کارشناس.', 'فقط پرداخت‌های سودران معتبر داخل بازه در این ستون جمع می‌شود.', 'جمع مبلغ سودران کارشناس = مجموع پرداخت‌های سودران معاملات منتسب به او.', 'این عدد جدا از فروش CRM و شخصی نمایش داده می‌شود.'),
                payment_amount: this.performanceInfoText('کل پرداخت منتسب به کارشناس.', 'همه پرداخت‌های معتبر بازه برای معاملات منتسب به کارشناس در این ستون جمع می‌شود.', 'کل پرداخت = فروش + مانده + چک پاس‌شده + سودران.', 'این ستون باید با مجموع تفکیک‌های پرداخت همان ردیف قابل کنترل باشد.'),
                sales_amount: this.performanceInfoText('فروش کل کارشناس.', 'فقط واریزی فروش اولیه معاملات کارشناس را نشان می‌دهد.', 'فروش کل = جمع پرداخت‌های روز اول فروش معاملات کارشناس.', 'پرداخت مانده حساب روزهای بعد در فروش کل نمی‌آید، اما در کل پرداخت دیده می‌شود.')
            }
        };
        return (map[tableId] && map[tableId][key]) || this.performanceInfoText(
            label || key,
            'ردیف‌های همین جدول بعد از اعمال فیلترهای فعال داشبورد.',
            'عدد یا درصد بر اساس گروه‌بندی همین جدول محاسبه می‌شود.',
            'درصدهای جدول باید با ردیف جمع کل قابل کنترل باشند؛ اگر یک معامله چند محصول یا چند پرداخت دارد، ممکن است تعداد معامله و تعداد پرداخت برابر نباشند.'
        );
    },

    getPerformanceCeoGroupInfo(title) {
        const map = {
            'واریزی‌ها': this.performanceInfoText(
                'نمای کل پول ورودی در بازه انتخاب‌شده.',
                'همه پرداخت‌های معتبر داخل بازه، بدون وابسته کردن کل واریزی به تاریخ ثبت معامله.',
                'کل واریزی‌ها = واریزی فروش + واریزی مانده حساب + واریزی چک‌های پاس‌شده + پرداخت‌های سودران + واریزی معاملات ناموفق.',
                'هیچ پرداخت معتبری نباید خارج از یکی از تفکیک‌های این ردیف بماند؛ معامله پرداخت‌دار غیرناموفق برای گزارش مالی موفق محسوب می‌شود، اما پرداخت معامله Lost جدا می‌آید.'
            ),
            'وضعیت مانده حساب‌ها': this.performanceInfoText(
                'نمای بدهی باقی‌مانده معاملات دارای پرداخت.',
                'فقط معاملات غیرناموفق از اسفند به بعد که مبلغ قابل پرداختشان هنوز کامل وصول نشده است.',
                'مانده هر معامله = مبلغ قابل پرداخت - کل پرداخت معتبر؛ کارت کل = جمع مانده‌های مثبت.',
                'کارت‌های ماهانه بر اساس ماه شمسی اولین پرداخت فروش معامله ساخته می‌شوند.'
            ),
            'وضعیت چک‌ها': this.performanceInfoText(
                'نمای مدیریتی چک‌های ثبت‌شده، مستقل از واریزی نقدی.',
                'چک‌ها بر اساس وضعیت وصول، برگشت، سررسید و ماه سررسید گروه‌بندی می‌شوند.',
                'کارت کل = تعداد و مبلغ همه چک‌های مشمول؛ کارت‌های بعدی تفکیک همان فضای چک هستند.',
                'فقط چک پاس‌شده وارد واریزی چک‌ها می‌شود؛ چک بلاتکلیف هنوز واریزی قطعی نیست.'
            ),
            'تفکیک واریزی بر اساس بازه معامله': this.performanceInfoText(
                'تفکیک کل پرداخت‌های بازه بر اساس زمان ثبت معامله.',
                'پرداخت‌ها به دو گروه تقسیم می‌شوند: معامله ثبت‌شده داخل بازه و معامله قدیمی‌تر.',
                'جمع این دو کارت باید با کل واریزی‌های معتبر بازه برابر باشد.',
                'این ردیف کمک می‌کند بفهمیم پول ورودی از فروش‌های جدید همان بازه آمده یا از معاملات قدیمی‌تر.'
            )
        };
        return map[title] || this.performanceInfoText(
            title || 'بخش گزارش مدیرعامل',
            'شاخص‌های مدیریتی همین بخش، بعد از اعمال فیلترهای بازه، مسئول و محصول.',
            'کارت اول معمولا کل است و کارت‌های بعدی تفکیک همان کل یا همان گروه مبنا هستند.',
            'برای کنترل صحت، جمع کارت‌های تفکیکی باید با کارت کل همان ردیف قابل توضیح باشد.'
        );
    },

    getPerformanceCeoDealStatusInfo(row = {}) {
        const key = String(row.key || '');
        const map = {
            total_registered_deals: this.performanceInfoText(
                'کل معاملاتی که داخل بازه انتخاب‌شده ثبت شده‌اند.',
                'این کارت فقط به تاریخ ثبت معامله نگاه می‌کند، نه به اینکه پرداخت داشته یا نداشته باشد.',
                'تعداد = همه معاملات ثبت‌شده داخل بازه؛ مبلغ = مجموع پرداخت‌های معتبر داخل بازه برای همین معاملات.',
                'معاملات قدیمی که اولین پرداختشان داخل بازه بوده، در بخش جداگانه پایین نمایش داده می‌شوند.'
            ),
            no_activity_deals: this.performanceInfoText(
                'معاملات ثبت‌شده داخل بازه که هیچ فعالیتی برایشان ثبت نشده است.',
                'فعالیت یعنی تماس، پیگیری، جلسه، پیام یا هر اقدام ثبت‌شده روی معامله.',
                'تعداد = معاملات داخل بازه با صفر فعالیت؛ مبلغ = پرداخت‌های معتبر داخل بازه همان معاملات.',
                'این کارت برای کشف لید/معامله‌هایی است که بعد از ثبت رها شده‌اند.'
            ),
            no_answer_only_deals: this.performanceInfoText(
                'معاملاتی که فقط فعالیت عدم پاسخ دارند و فعالیت موفق یا پیگیری موثر ندارند.',
                'اگر معامله هم عدم پاسخ داشته باشد و هم فعالیت موفق/پیگیری، اینجا کامل حساب نمی‌شود.',
                'تعداد = معاملات داخل بازه با فعالیت‌های صرفا بی‌پاسخ؛ درصد = تعداد این کارت / کل معاملات ثبت‌شده داخل بازه.',
                'این شاخص با «بدون فعالیت» فرق دارد؛ اینجا فعالیت هست، اما نتیجه مؤثر ندارد.'
            ),
            follow_up_deals: this.performanceInfoText(
                'معاملات در حال پیگیری.',
                'معاملاتی که پرداخت ندارند، ناموفق نشده‌اند، و حداقل یک فعالیت موفق یا پیگیری معتبر دارند.',
                'تعداد = معاملات داخل بازه با فعالیت مؤثر و بدون پرداخت؛ مبلغ = پرداخت‌های معتبر داخل بازه اگر وجود داشته باشد.',
                'این کارت ظرفیت فروش آینده را نشان می‌دهد، نه فروش قطعی.'
            ),
            lost_deals: this.performanceInfoText(
                'معاملات ناموفق ثبت‌شده داخل بازه.',
                'معامله‌ای که وضعیت آن ناموفق/از دست رفته است.',
                'تعداد = معاملات ناموفق داخل بازه؛ درصد = تعداد ناموفق / کل معاملات ثبت‌شده داخل بازه.',
                'این عدد ممکن است با فروش ناموفق عمومی فرق کند اگر دامنه عمومی معاملات قدیمی با پرداخت داخل بازه را هم لحاظ کند.'
            ),
            won_deals: this.performanceInfoText(
                'معاملات موفق مالی داخل بازه که فروششان در همین بازه اتفاق افتاده و کامل تسویه شده‌اند.',
                'این کارت از همان مجموعه واریزی فروش می‌آید: معامله باید ناموفق نباشد، داخل بازه ثبت شده باشد، و اولین پرداخت فروش معتبرش هم داخل همین بازه باشد. اگر CRM هنوز Pending مانده باشد ولی پرداخت معتبر دارد، در گزارش مالی موفق محسوب می‌شود.',
                'تعداد = معاملات غیرناموفق این گروه که کل پرداخت معتبرشان به مبلغ قابل پرداخت رسیده یا از آن عبور کرده است.',
                'این کارت با موفق دارای مانده حساب هم‌پوشانی ندارد؛ یکی کامل‌پرداخت‌شده است و دیگری مانده‌دار.'
            ),
            won_outstanding_deals: this.performanceInfoText(
                'معاملات موفق مالی داخل بازه که فروششان در همین بازه اتفاق افتاده اما هنوز مانده حساب دارند.',
                'این کارت هم از همان مجموعه واریزی فروش می‌آید: معامله باید ناموفق نباشد، داخل بازه ثبت شده باشد، و اولین پرداخت فروش معتبرش داخل همین بازه باشد.',
                'تعداد = معاملات غیرناموفق این گروه که پرداخت معتبر دارند اما کل پرداخت معتبرشان هنوز کمتر از مبلغ قابل پرداخت است.',
                'این کارت با معاملات موفق کامل‌شده هم‌پوشانی ندارد؛ جمع این دو کارت، معاملات موفق مالی ثبت‌شده داخل بازه با اولین پرداخت داخل بازه را می‌دهد.'
            ),
            first_payment_outside_registered_deals: this.performanceInfoText(
                'معاملات موفق مالی ثبت‌شده قبل از بازه که اولین پرداخت فروششان داخل بازه اتفاق افتاده است.',
                'این بخش برای جدا کردن فروش فعال‌شده در این ماه از معاملات ثبت‌شده همین ماه است.',
                'تعداد = معاملات غیرناموفق خارج بازه با اولین پرداخت فروش داخل بازه؛ مبلغ = پرداخت‌های معتبر داخل بازه برای آنها.',
                'این کارت با دو کارت معاملات موفق و موفق دارای مانده حساب هم‌پوشانی ندارد؛ جمع این سه کارت باید با تعداد معامله واریزی فروش برابر شود.'
            ),
            previous_range_completed_deals: this.performanceInfoText(
                'زیرگروه معاملات موفق مالی قدیمی که در این بازه کامل تسویه شده‌اند.',
                'معامله قبل از بازه ثبت شده، ناموفق نیست، اولین پرداخت فروش داخل بازه بوده، و کل مبلغ قابل پرداخت وصول شده است.',
                'تعداد = معاملات قدیمی با پرداخت معتبر >= مبلغ قابل پرداخت؛ درصد = تعداد این کارت / کل بخش معاملات قدیمی فعال‌شده.',
                'این کارت وضعیت مالی کامل‌شدن را نشان می‌دهد.'
            ),
            previous_range_outstanding_deals: this.performanceInfoText(
                'زیرگروه معاملات موفق مالی قدیمی که در این بازه پرداخت گرفته‌اند اما هنوز مانده دارند.',
                'معامله قبل از بازه ثبت شده، ناموفق نیست، اولین پرداخت فروش داخل بازه بوده، و هنوز کل مبلغ قابل پرداخت وصول نشده است.',
                'تعداد = معاملات قدیمی با مانده مثبت؛ درصد = تعداد این کارت / کل بخش معاملات قدیمی فعال‌شده.',
                'مبلغ کارت، پرداخت داخل بازه است؛ مانده واقعی در وضعیت مانده حساب‌ها کنترل می‌شود.'
            )
        };
        return map[key] || this.performanceInfoText(
            row.label || 'وضعیت معامله',
            'معاملات همان گروه مبنا پس از اعمال فیلترهای فعال.',
            'درصد = تعداد این کارت / کل گروه مبنا × ۱۰۰؛ مبلغ = پرداخت‌های معتبر داخل بازه برای همین معاملات.',
            'پیل‌های شخص/CRM/سودران نشان می‌دهند معامله از چه مسیر ثبت یا پرداختی آمده است.'
        );
    },

    ceoStatusBadgeMeta(kind, status) {
        const s = String(status || '').trim();
        if (kind === 'access') {
            const m = { done: ['انجام‌شده', 'is-green'], pending: ['در انتظار', 'is-gray'], not_done: ['انجام‌نشده', 'is-red'] };
            return m[s] || [s || '-', 'is-gray'];
        }
        if (kind === 'collection') {
            const m = { collected: ['پاس‌شده', 'is-green'], returned: ['برگشتی', 'is-red'], blataklif: ['بلاتکلیف', 'is-amber'], pending: ['در انتظار', 'is-gray'], not_cheque: ['غیرچک', 'is-gray'] };
            return m[s] || [s || '-', 'is-gray'];
        }
        const m = { approved: ['تاییدشده', 'is-green'], rejected: ['ردشده', 'is-red'], blataklif: ['بلاتکلیف', 'is-amber'], pending: ['در انتظار', 'is-gray'], not_cheque: ['غیرچک', 'is-gray'] };
        return m[s] || [s || '-', 'is-gray'];
    },

    renderPerformanceCeoReportSection(report = {}) {
        if (!this.canPerformanceFeatureClient('performance.ceo_report')) {
            return '';
        }
        const hasData = report && Object.keys(report || {}).length > 0;
        if (!hasData) {
            const skel = (h) => `<div class="perf-skel" style="height:${h}px"></div>`;
            return `
                <section class="glass p-4 md:p-5 mb-6 border border-gray-700/70">
                    <div class="perf-section-head">
                        <div>
                            <div class="perf-section-title">گزارش مدیرعامل</div>
                            <div class="perf-section-sub">در حال آماده‌سازی گزارش واریزی، مانده حساب، چک، محصول و معاملات...</div>
                        </div>
                    </div>
                    <div class="grid grid-cols-1 md:grid-cols-4 gap-3 mt-4">
                        ${Array.from({ length: 4 }).map(() => skel(96)).join('')}
                    </div>
                    <div class="grid grid-cols-1 xl:grid-cols-2 gap-4 mt-4">${skel(220)}${skel(220)}</div>
                </section>
            `;
        }

        const summary = report.summary || {};
        const cashRows = Array.isArray(report.cash_composition) ? report.cash_composition : [];
        const depositCohortRows = Array.isArray(report.deposit_cohort) ? report.deposit_cohort : [];
        const productRows = Array.isArray(report.product_sales) ? report.product_sales : [];
        const marketingRows = Array.isArray(report.marketing_sources) ? report.marketing_sources : (Array.isArray(report.registered_deals_sources) ? report.registered_deals_sources : []);
        const agentRows = Array.isArray(report.agent_sales) ? report.agent_sales : [];
        const accessRows = Array.isArray(report.access_grants) ? report.access_grants : [];
        const outstanding = report.outstanding_balances || {};
        const outstandingMonths = Array.isArray(outstanding.months) ? outstanding.months : [];
        const chequeCards = Array.isArray(report.cheques?.cards) ? report.cheques.cards : [];
        const dealStatus = report.deal_status || {};
        const dealStatusCards = Array.isArray(dealStatus.cards) ? dealStatus.cards : [];
        const dealLeadMix = Array.isArray(dealStatus.lead_mix) ? dealStatus.lead_mix : [];
        const dealPreviousSuccess = dealStatus.previous_success || {};
        const depositTotal = Object.assign({
            label: 'کل واریزی‌ها',
            amount: summary.total_deposits_amount || 0,
            deal_count: summary.total_deposits_deal_count || 0,
            payment_count: summary.total_deposits_receipt_count || 0,
            metric_key: 'total_deposits_amount'
        }, report.deposit_total || {});
        depositTotal.metric_key = 'total_deposits_amount';
        depositTotal.key = '';

        const outstandingTotal = Object.assign({
            key: '',
            label: 'کل مانده حساب‌ها',
            amount: summary.outstanding_balance_amount || 0,
            deal_count: summary.outstanding_balance_deal_count || 0,
            metric_key: 'outstanding_balance_total'
        }, outstanding.total || {});
        outstandingTotal.key = '';
        outstandingTotal.label = 'کل مانده حساب‌ها';
        outstandingTotal.metric_key = 'outstanding_balance_total';

        const chequeTotal = Object.assign({
            key: '',
            label: 'کل چک‌ها',
            count: summary.all_cheques_count || 0,
            amount: summary.all_cheques_amount || 0,
            metric_key: 'all_cheques_count'
        }, report.cheques?.total || {});
        chequeTotal.key = '';
        chequeTotal.label = 'کل چک‌ها';
        chequeTotal.metric_key = 'all_cheques_count';

        this.state.performanceCeoRows = {
            deposit_total: [depositTotal],
            deposit_cohort: depositCohortRows,
            cash: cashRows,
            outstanding_total: [outstandingTotal],
            outstanding_months: outstandingMonths,
            cheque_total: [chequeTotal],
            cheque_cards: chequeCards,
            product_sales: productRows,
            marketing_sources: marketingRows,
            agent_sales: agentRows,
            access_grants: accessRows,
            deal_status_cards: dealStatusCards,
            deal_lead_mix: dealLeadMix,
            deal_previous_success: [dealPreviousSuccess.total, dealPreviousSuccess.completed, dealPreviousSuccess.outstanding].filter(Boolean)
        };

        return `
            <section class="glass p-4 md:p-5 mb-6 border border-gray-700/70">
                <div class="perf-section-head">
                    <div>
                        <div class="perf-section-title">گزارش مدیرعامل</div>
                        <div class="perf-section-sub">نمای مالی و عملیاتی بازه انتخاب‌شده. هر کارت و جدول قابل Drill-down است.</div>
                    </div>
                    <span class="text-[11px] text-gray-500">کل واریزی‌ها همه پرداخت‌های ثبت‌شده در بازه انتخاب‌شده است؛ تاریخ ثبت معامله فقط در تفکیک بازاریابی استفاده می‌شود.</span>
                </div>

                ${this.renderPerformanceCeoMetricRow('واریزی‌ها', depositTotal, cashRows, 'cash', { totalGroup: 'deposit_total', totalMetric: 'total_deposits_amount', metricMap: { sales_deposit: 'sales_deposit_amount', balance_deposit: 'balance_deposit_amount', cheque_collected: 'cheque_collected_amount', sudran_deposit: 'sudran_deposit_amount', lost_deposit: 'lost_deposit_amount' }, rail: 'perf-rail-money', totalRail: 'perf-rail-money', cap: 'performance.table.ceo_cash_composition' })}
                ${this.renderPerformanceCeoMetricRow('وضعیت مانده حساب‌ها', outstandingTotal, outstandingMonths, 'outstanding_months', { totalGroup: 'outstanding_total', totalMetric: 'outstanding_balance_total', rowMetric: 'outstanding_balance_month', rail: 'perf-rail-bal', totalRail: 'perf-rail-bal', cap: 'performance.card.outstanding_balance_amount' })}
                ${this.renderPerformanceCeoMetricRow('وضعیت چک‌ها', chequeTotal, chequeCards, 'cheque_cards', { totalGroup: 'cheque_total', totalMetric: 'all_cheques_count', rowMetric: 'cheque_state', rail: 'perf-rail-cheque', totalRail: 'perf-rail-cheque', cap: 'performance.table.ceo_cheque_status', primary: 'count' })}

                <div class="grid grid-cols-1 xl:grid-cols-2 gap-4 mt-5">
                    <div class="xl:col-span-2">${this.renderPerformanceCeoMetricRow('تفکیک واریزی بر اساس بازه معامله', {}, depositCohortRows, 'deposit_cohort', { hideTotal: true, metricMap: { registered_range_deposit: 'deposit_registered_in_range_amount', outside_range_deposit: 'deposit_outside_registered_first_payment_amount' }, rail: 'perf-rail-money', cap: 'performance.card.total_deposits_amount' })}</div>
                    ${this.renderPerformanceCeoMarketingPanel(marketingRows)}
                    ${this.renderPerformanceCeoProductPanel(productRows)}
                    ${this.renderPerformanceCeoAgentPanel(agentRows)}
                </div>
                ${this.renderPerformanceCeoDealStatusPanel(dealStatusCards, dealLeadMix, dealPreviousSuccess)}
                ${this.renderPerformanceCeoAccessPanel(accessRows)}
            </section>
        `;
    },

    renderPerformanceCeoMetricRow(title, totalRow = {}, rows = [], groupKey = '', options = {}) {
        if (options.cap && !this.canPerformanceFeatureClient(options.cap)) return '';
        const totalGroup = options.totalGroup || `${groupKey}_total`;
        const primary = options.primary || 'amount';
        const metricMap = options.metricMap || {};
        const totalMetric = options.totalMetric || totalRow.metric_key || '';
        const rowMetric = options.rowMetric || '';
        const valueHtml = (row, isTotal = false) => {
            if (primary === 'count') {
                const amount = Number(row.amount || 0);
                return `<span>${this.formatNumber(Number(row.count || 0))}</span>${amount > 0 ? `<span class="unit"> | ${this.formatNumber(amount)} تومان</span>` : ''}`;
            }
            return this.perfMoney(row.amount || 0);
        };
        const metaItems = (row, isTotal = false) => {
            const parts = [];
            if (row.deal_count != null) parts.push({ label: 'معامله', value: this.formatNumber(Number(row.deal_count || 0)) });
            if (row.payment_count != null) parts.push({ label: 'پرداخت', value: this.formatNumber(Number(row.payment_count || 0)) });
            if (row.count != null && primary !== 'count') parts.push({ label: 'تعداد', value: this.formatNumber(Number(row.count || 0)) });
            if (row.percent != null && !isTotal) parts.push({ label: 'سهم', value: `${this.formatNumber(Number(row.percent || 0))}٪` });
            return parts;
        };
        const metaHtml = (row, isTotal = false) => {
            const items = metaItems(row, isTotal);
            if (!items.length) return '';
            return `<div class="perf-card-meta">${items.map(item => `
                <span class="perf-card-chip"><span>${this.escapeHtml(item.label)}</span><b>${this.escapeHtml(item.value)}</b></span>
            `).join('')}</div>`;
        };
        const metaLabel = (row, isTotal = false) => {
            return metaItems(row, isTotal).map(item => `${item.label}: ${item.value}`).join('، ');
        };
        const card = (row, idx, isTotal = false) => {
            const label = row.label || row.key || '-';
            const metric = isTotal ? totalMetric : (row.metric_key || metricMap[row.key] || rowMetric);
            const targetGroup = isTotal ? totalGroup : groupKey;
            const targetIndex = isTotal ? 0 : idx;
            const cls = isTotal ? `perf-hero ${options.totalRail || ''}` : `perf-card ${options.rail || ''} is-click`;
            const click = metric ? `onclick="app.openPerformanceCeoRowDrilldown('${metric}','${targetGroup}',${targetIndex})" role="button" tabindex="0"` : '';
            const aria = [label, metaLabel(row, isTotal)].filter(Boolean).join('، ');
            const info = this.getPerformanceCeoMetricInfo(metric, label);
            return `
                <div class="${cls}" ${click} aria-label="${this.escapeHtml(aria)}">
                    <div class="perf-card-top">
                        <div class="perf-card-lbl"><span class="perf-card-lbl-text">${this.escapeHtml(label)}</span>${this.renderSaleOsInfoTooltip(info, { title: label })}</div>
                        ${metric ? '<div class="perf-card-hint" aria-hidden="true"><i class="fa-solid fa-arrow-pointer"></i><span>جزئیات</span></div>' : ''}
                    </div>
                    <div class="perf-card-val">${valueHtml(row, isTotal)}</div>
                    ${metaHtml(row, isTotal)}
                </div>
            `;
        };
        const bodyRows = Array.isArray(rows) ? rows : [];
        return `
            <div class="perf-group"><span class="perf-dot" style="background:${options.dot || '#38bdf8'}"></span> ${this.escapeHtml(title)}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoGroupInfo(title), { title })}</div>
            <div class="perf-row-grid">
                ${options.hideTotal ? '' : card(totalRow, 0, true)}
                ${bodyRows.length ? bodyRows.map((row, idx) => card(row, idx, false)).join('') : '<div class="perf-empty">داده‌ای برای تفکیک این ردیف وجود ندارد.</div>'}
            </div>
        `;
    },

    renderPerformanceCeoCashPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_cash_composition')) return '';
        const palette = ['#10b981', '#22d3ee', '#3b82f6', '#f59e0b', '#a78bfa'];
        const total = rows.reduce((s, r) => s + Number(r.amount || 0), 0);
        const legend = rows.map((row, idx) => {
            const pct = total > 0 ? Math.round((Number(row.amount || 0) / total) * 100) : 0;
            return `<span><span class="sw" style="background:${palette[idx % palette.length]}"></span>${this.escapeHtml(row.label || row.key || '-')} ${this.formatNumber(pct)}٪</span>`;
        }).join('');
        const body = rows.length
            ? `<div class="perf-legend">${legend}</div><div id="perf-ceo-cash-chart" style="min-height:210px"></div>`
            : '<div class="perf-empty">واریزی‌ای در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel">
                <div class="perf-panel-title" style="color:#6ee7b7"><i class="fa-solid fa-chart-pie"></i> ترکیب واریزی‌ها</div>
                ${body}
            </div>
        `;
    },

    renderPerformanceCeoProductPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_product_sales')) return '';
        const th = (key, label) => `<th style="text-align:center">${this.escapeHtml(label)}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('product_sales', key, label), { title: label })}</th>`;
        const splitAmountFields = [
            'crm_lead_count', 'personal_lead_count', 'sudran_lead_count',
            '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', 'payment_amount'
        ];
        const displayRows = this.withCeoOtherSummaryRow(rows, 15, {
            labelFields: { product_name: 'سایر محصولات' },
            sumFields: ['sold_count', 'deal_count', 'amount', ...splitAmountFields],
            percentFields: ['sold_share_percent', 'amount_share_percent', 'payment_amount_share_percent']
        });
        const totals = this.sumCeoRows(displayRows, ['sold_count', 'deal_count', 'amount', ...splitAmountFields]);
        const sourceCell = (count, total) => {
            const pct = Number(total || 0) > 0 ? (Number(count || 0) / Number(total || 0)) * 100 : 0;
            return `<span class="perf-agent-source-count"><b>${this.formatNumber(Number(count || 0))}</b><em>${this.formatNumber(pct)}٪</em></span>`;
        };
        const body = rows.length
            ? `
                <div style="overflow-x:auto">
                    <table class="perf-tbl">
                        <thead><tr>
                            <th>${this.escapeHtml('محصول')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('product_sales', 'product_name', 'محصول'), { title: 'محصول' })}</th>
                            ${th('sold_count', 'تعداد')}
                            ${th('deal_count', 'معامله')}
                            ${th('crm_lead_count', 'لید بدون مسئول')}
                            ${th('personal_lead_count', 'لید شخص')}
                            ${th('sudran_lead_count', 'لید سودران')}
                            ${th('crm_amounts', 'واریزی لیدهای CRM')}
                            ${th('personal_amounts', 'واریزی لیدهای شخصی')}
                            ${th('sudran_amount', 'واریزی سودران')}
                            ${th('payment_amount', 'کل پرداخت مرتبط')}
                            ${th('amount', 'فروش کل')}
                        </tr></thead>
                        <tbody>
                            ${displayRows.map((row, idx) => `
                                <tr>
                                    <td>${row.is_other_summary ? this.escapeHtml(row.product_name || 'سایر محصولات') : `<button type="button" class="perf-linkbtn" onclick="app.openPerformanceCeoRowDrilldown('product_sales','product_sales',${idx})">${this.escapeHtml(row.product_name || 'نامشخص')}</button>`}</td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.sold_count || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.sold_share_percent || 0))}٪)</span></td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.deal_count || 0))}</td>
                                    <td style="text-align:center">${sourceCell(row.crm_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${sourceCell(row.personal_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${sourceCell(row.sudran_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(row, 'crm')}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(row, 'personal')}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoSudranCell(row)}</td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.payment_amount || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.payment_amount_share_percent || 0))}٪)</span></td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.amount || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.amount_share_percent || 0))}٪)</span></td>
                                </tr>
                            `).join('')}
                        </tbody>
                        <tfoot>
                            <tr>
                                <td>جمع کل</td>
                                <td style="text-align:center">${this.formatNumber(totals.sold_count)} <span class="text-gray-500">(100٪)</span></td>
                                <td style="text-align:center">${this.formatNumber(totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.crm_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.personal_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.sudran_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(totals, 'crm')}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(totals, 'personal')}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoSudranCell(totals)}</td>
                                <td style="text-align:center">${this.formatNumber(totals.payment_amount)} <span class="text-gray-500">(100٪)</span></td>
                                <td style="text-align:center">${this.formatNumber(totals.amount)} <span class="text-gray-500">(100٪)</span></td>
                            </tr>
                        </tfoot>
                    </table>
                </div>`
            : '<div class="perf-empty">فروش محصولی در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel">
                <div class="perf-panel-title" style="color:#c4b5fd"><i class="fa-solid fa-boxes-stacked"></i> محصولات فروخته‌شده${this.renderSaleOsInfoTooltip(this.performanceInfoText('محصولات معاملات فروش‌شده یا سودران‌پرداخت در بازه، همراه با تفکیک لید و واریزی.', 'deal_products/products + payment_records.', 'محصولات از معاملات با اولین پرداخت فروش یا پرداخت سودران داخل بازه می‌آیند؛ فروش کل فقط اولین واریزی فروش غیرسودران است.'), { title: 'محصولات فروخته‌شده' })}</div>
                ${body}
            </div>
        `;
    },

    renderPerformanceCeoMarketingPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_marketing_sources') && !this.canPerformanceFeatureClient('performance.table.ceo_registered_deals_sources')) return '';
        const th = (key, label) => `<th style="text-align:center">${this.escapeHtml(label)}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('marketing_sources', key, label), { title: label })}</th>`;
        const displayRows = this.withCeoOtherSummaryRow(rows, 15, {
            labelFields: { acquaintance_source: 'سایر موارد', acquaintance_detail: 'ردیف‌های دیگر', content_topic: 'همه موضوعات' },
            sumFields: ['payment_count', 'deal_count', 'amount'],
            percentFields: ['payment_share_percent', 'deal_share_percent', 'amount_share_percent']
        });
        const totals = this.sumCeoRows(displayRows, ['payment_count', 'deal_count', 'amount']);
        const body = rows.length
            ? `
                <div style="overflow-x:auto">
                    <table class="perf-tbl">
                        <thead><tr>
                            <th>${this.escapeHtml('شیوه آشنایی')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('marketing_sources', 'acquaintance_source', 'شیوه آشنایی'), { title: 'شیوه آشنایی' })}</th>
                            <th>${this.escapeHtml('جزئیات')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('marketing_sources', 'acquaintance_detail', 'جزئیات'), { title: 'جزئیات' })}</th>
                            <th>${this.escapeHtml('موضوع محتوا')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('marketing_sources', 'content_topic', 'موضوع محتوا'), { title: 'موضوع محتوا' })}</th>
                            ${th('payment_count', 'پرداخت')}${th('deal_count', 'معامله')}${th('amount', 'مبلغ')}
                        </tr></thead>
                        <tbody>
                            ${displayRows.map((row, idx) => `
                                <tr>
                                    <td>${row.is_other_summary ? this.escapeHtml(row.acquaintance_source || 'سایر موارد') : `<button type="button" class="perf-linkbtn" onclick="app.openPerformanceCeoRowDrilldown('marketing_source','marketing_sources',${idx})">${this.escapeHtml(this.getLookupLabel('lead_sources', row.acquaintance_source || ''))}</button>`}</td>
                                    <td>${this.escapeHtml(this.getLookupLabel('lead_source_details', row.acquaintance_detail || ''))}</td>
                                    <td>${this.escapeHtml(this.getLookupLabel('content_topics', row.content_topic || ''))}</td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.payment_count || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.payment_share_percent || 0))}٪)</span></td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.deal_count || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.deal_share_percent || 0))}٪)</span></td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.amount || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.amount_share_percent || 0))}٪)</span></td>
                                </tr>
                            `).join('')}
                        </tbody>
                        <tfoot>
                            <tr>
                                <td>جمع کل</td>
                                <td>-</td>
                                <td>-</td>
                                <td style="text-align:center">${this.formatNumber(totals.payment_count)} <span class="text-gray-500">(100٪)</span></td>
                                <td style="text-align:center">${this.formatNumber(totals.deal_count)} <span class="text-gray-500">(100٪)</span></td>
                                <td style="text-align:center">${this.formatNumber(totals.amount)} <span class="text-gray-500">(100٪)</span></td>
                            </tr>
                        </tfoot>
                    </table>
                </div>`
            : '<div class="perf-empty">پرداختی با اطلاعات مارکتینگ در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel">
                <div class="perf-panel-title" style="color:#a5b4fc"><i class="fa-solid fa-bullhorn"></i> گزارشات مارکتینگ${this.renderSaleOsInfoTooltip(this.performanceInfoText('پرداخت‌ها و معاملات پرداخت‌دار به تفکیک شیوه آشنایی، جزئیات و موضوع محتوا.', 'payment_records + deals/persons acquisition fields.', 'هر ردیف با source/detail/topic گروه‌بندی می‌شود؛ درصدها از کل پرداخت، کل معامله یا کل مبلغ همان جدول محاسبه می‌شوند.'), { title: 'گزارشات مارکتینگ' })}</div>
                ${body}
            </div>
        `;
    },

    withCeoOtherSummaryRow(rows = [], limit = 15, config = {}) {
        if (!Array.isArray(rows) || rows.length <= limit) return Array.isArray(rows) ? rows : [];
        const visible = rows.slice(0, limit);
        const hidden = rows.slice(limit);
        const other = Object.assign({ is_other_summary: true }, config.labelFields || {});
        (config.sumFields || []).forEach(field => {
            other[field] = hidden.reduce((sum, row) => sum + Number(row[field] || 0), 0);
        });
        (config.percentFields || []).forEach(field => {
            const visibleShare = visible.reduce((sum, row) => sum + Number(row[field] || 0), 0);
            other[field] = Math.max(0, Number((100 - visibleShare).toFixed(2)));
        });
        return [...visible, other];
    },

    sumCeoRows(rows = [], fields = []) {
        const totals = {};
        (fields || []).forEach(field => {
            totals[field] = (Array.isArray(rows) ? rows : []).reduce((sum, row) => sum + Number(row?.[field] || 0), 0);
        });
        return totals;
    },

    renderPerformanceCeoSplitAmount(amount, denominator) {
        const value = Number(amount || 0);
        const total = Number(denominator || 0);
        const pct = total > 0 ? (value / total) * 100 : 0;
        return `<b>${this.formatNumber(value)}</b><small>${this.formatNumber(pct)}٪</small>`;
    },

    renderPerformanceCeoAmountSplitCell(row = {}, prefix = 'crm', denominatorField = 'payment_amount') {
        const denominator = Number(row?.[denominatorField] || 0);
        return `
            <div class="perf-agent-amount-split">
                <span><em>فروش</em>${this.renderPerformanceCeoSplitAmount(row?.[`${prefix}_sales_amount`] || 0, denominator)}</span>
                <span><em>مانده</em>${this.renderPerformanceCeoSplitAmount(row?.[`${prefix}_balance_amount`] || 0, denominator)}</span>
                <span><em>چک</em>${this.renderPerformanceCeoSplitAmount(row?.[`${prefix}_cheque_amount`] || 0, denominator)}</span>
                <span><em>ناموفق</em>${this.renderPerformanceCeoSplitAmount(row?.[`${prefix}_lost_amount`] || 0, denominator)}</span>
            </div>
        `;
    },

    renderPerformanceCeoSudranCell(row = {}, denominatorField = 'payment_amount') {
        const amount = Number(row?.sudran_amount || 0);
        const denominator = Number(row?.[denominatorField] || 0);
        return `<span class="perf-agent-sudran-amount">${this.renderPerformanceCeoSplitAmount(amount, denominator)}</span>`;
    },

    renderPerformanceCeoAgentPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_agent_sales')) return '';
        const th = (key, label) => `<th style="text-align:center">${this.escapeHtml(label)}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('agent_sales', key, label), { title: label })}</th>`;
        const splitAmountFields = [
            'crm_lead_count', 'personal_lead_count', 'sudran_lead_count',
            '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'
        ];
        const displayRows = this.withCeoOtherSummaryRow(rows, 20, {
            labelFields: { owner_name: 'سایر کارشناسان' },
            sumFields: ['sales_deal_count', 'deal_count', 'payment_count', 'sales_amount', 'payment_amount', ...splitAmountFields],
            percentFields: ['sales_count_share_percent', 'sales_amount_share_percent', 'payment_count_share_percent', 'payment_amount_share_percent']
        });
        const totals = this.sumCeoRows(displayRows, ['sales_deal_count', 'sales_amount', 'payment_amount', 'payment_count', 'deal_count', ...splitAmountFields]);
        const sourceCell = (count, total) => {
            const pct = Number(total || 0) > 0 ? (Number(count || 0) / Number(total || 0)) * 100 : 0;
            return `<span class="perf-agent-source-count"><b>${this.formatNumber(Number(count || 0))}</b><em>${this.formatNumber(pct)}٪</em></span>`;
        };
        const body = rows.length
            ? `
                <div style="overflow-x:auto">
                    <table class="perf-tbl">
                        <thead><tr>
                            <th>${this.escapeHtml('کارشناس')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoTableColumnInfo('agent_sales', 'owner_name', 'کارشناس'), { title: 'کارشناس' })}</th>
                            ${th('sales_deal_count', 'تعداد فروش')}
                            ${th('deal_count', 'معامله پرداخت‌دار')}
                            ${th('crm_lead_count', 'لید بدون مسئول')}
                            ${th('personal_lead_count', 'لید شخص')}
                            ${th('sudran_lead_count', 'لید سودران')}
                            ${th('crm_amounts', 'واریزی لیدهای CRM')}
                            ${th('personal_amounts', 'واریزی لیدهای شخصی')}
                            ${th('sudran_amount', 'واریزی سودران')}
                            ${th('payment_amount', 'کل پرداخت')}
                            ${th('sales_amount', 'فروش کل')}
                        </tr></thead>
                        <tbody>
                            ${displayRows.map((row, idx) => `
                                <tr>
                                    <td>${row.is_other_summary ? this.escapeHtml(row.owner_name || 'سایر کارشناسان') : `<button type="button" class="perf-linkbtn" onclick="app.openPerformanceCeoRowDrilldown('agent_sales','agent_sales',${idx})">${this.escapeHtml(row.owner_name || 'بدون مسئول')}</button>`}</td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.sales_deal_count || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.sales_count_share_percent || 0))}٪)</span></td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.deal_count || 0))}</td>
                                    <td style="text-align:center">${sourceCell(row.crm_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${sourceCell(row.personal_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${sourceCell(row.sudran_lead_count, row.deal_count)}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(row, 'crm')}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(row, 'personal')}</td>
                                    <td style="text-align:center">${this.renderPerformanceCeoSudranCell(row)}</td>
                                    <td style="text-align:center">
                                        ${this.formatNumber(Number(row.payment_amount || 0))}
                                        <span class="text-gray-500">(${this.formatNumber(Number(row.payment_amount_share_percent || 0))}٪)</span>
                                        <div class="text-[10px] text-gray-500 mt-1">${this.formatNumber(Number(row.payment_count || 0))} پرداخت</div>
                                    </td>
                                    <td style="text-align:center">${this.formatNumber(Number(row.sales_amount || 0))} <span class="text-gray-500">(${this.formatNumber(Number(row.sales_amount_share_percent || 0))}٪)</span></td>
                                </tr>
                            `).join('')}
                        </tbody>
                        <tfoot>
                            <tr>
                                <td>جمع کل</td>
                                <td style="text-align:center">${this.formatNumber(totals.sales_deal_count)} <span class="text-gray-500">(100٪)</span></td>
                                <td style="text-align:center">${this.formatNumber(totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.crm_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.personal_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${sourceCell(totals.sudran_lead_count, totals.deal_count)}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(totals, 'crm')}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoAmountSplitCell(totals, 'personal')}</td>
                                <td style="text-align:center">${this.renderPerformanceCeoSudranCell(totals)}</td>
                                <td style="text-align:center">
                                    ${this.formatNumber(totals.payment_amount)}
                                    <span class="text-gray-500">(100٪)</span>
                                    <div class="text-[10px] text-gray-500 mt-1">${this.formatNumber(totals.payment_count)} پرداخت</div>
                                </td>
                                <td style="text-align:center">${this.formatNumber(totals.sales_amount)} <span class="text-gray-500">(100٪)</span></td>
                            </tr>
                        </tfoot>
                    </table>
                </div>`
            : '<div class="perf-empty">پرداخت منتسب به کارشناسی در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel xl:col-span-2">
                <div class="perf-panel-title" style="color:#67e8f9"><i class="fa-solid fa-user-tie"></i> فروش کارشناسان${this.renderSaleOsInfoTooltip(this.performanceInfoText('عملکرد مالی هر کارشناس با تفکیک فروش، کل پرداخت، لید بدون مسئول/شخصی/سودران.', 'payment_records، deals و users.', 'فروش کل فقط اولین واریزی فروش است؛ کل پرداخت همه پرداخت‌های معتبر منتسب به کارشناس در بازه است.'), { title: 'فروش کارشناسان' })}</div>
                ${body}
            </div>
        `;
    },

    renderPerformanceCeoDealSourcesPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_registered_deals_sources')) return '';
        const sourceInfo = this.performanceInfoText('معاملات ثبت‌شده به تفکیک شیوه آشنایی.', 'deals acquisition fields.', 'COUNT معاملات ثبت‌شده در بازه برای هر source/detail/topic.');
        const body = rows.length
            ? `
                <div style="overflow-x:auto">
                    <table class="perf-tbl">
                        <thead><tr>
                            <th>شیوه آشنایی${this.renderSaleOsInfoTooltip(sourceInfo, { title: 'شیوه آشنایی' })}</th>
                            <th>جزئیات${this.renderSaleOsInfoTooltip(sourceInfo, { title: 'جزئیات' })}</th>
                            <th>موضوع محتوا${this.renderSaleOsInfoTooltip(sourceInfo, { title: 'موضوع محتوا' })}</th>
                            <th style="text-align:center">معامله${this.renderSaleOsInfoTooltip(this.performanceInfoText('تعداد معاملات ثبت‌شده در بازه برای این گروه.', 'deals.', 'COUNT deals در گروه.'), { title: 'معامله' })}</th>
                            <th style="text-align:center">اولین واریزی${this.renderSaleOsInfoTooltip(this.performanceInfoText('تعداد معاملات این گروه که اولین واریزی فروششان در بازه است.', 'payment_records + deals.', 'COUNT dealهای دارای first sales payment در بازه.'), { title: 'اولین واریزی' })}</th>
                        </tr></thead>
                        <tbody>
                            ${rows.slice(0, 12).map((row, idx) => `
                                <tr>
                                    <td><span class="perf-dealcell"><i class="fa-solid fa-route" style="font-size:11px"></i>${this.escapeHtml(this.getLookupLabel('lead_sources', row.acquaintance_source || ''))}</span></td>
                                    <td>${this.escapeHtml(this.getLookupLabel('lead_source_details', row.acquaintance_detail || ''))}</td>
                                    <td>${this.escapeHtml(this.getLookupLabel('content_topics', row.content_topic || ''))}</td>
                                    <td style="text-align:center"><button type="button" class="perf-pill" style="cursor:pointer;border:0" onclick="app.openPerformanceCeoRowDrilldown('registered_deals_source','registered_deals_sources',${idx})">${this.formatNumber(row.deal_count || 0)}</button></td>
                                    <td style="text-align:center">${this.formatNumber(row.first_payment_deal_count || 0)}</td>
                                </tr>
                            `).join('')}
                        </tbody>
                    </table>
                </div>
            `
            : '<div class="perf-empty">معامله‌ای در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel mt-4">
                <div class="perf-panel-title" style="color:#a5b4fc"><i class="fa-solid fa-diagram-project"></i> معاملات بر اساس شیوه آشنایی</div>
                ${body}
            </div>
        `;
    },

    renderPerformanceCeoDealStatusPanel(cards = [], leadMix = [], previousSuccess = {}) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_deal_status') && !this.canPerformanceFeatureClient('performance.ceo_report')) return '';
        const pillRow = (row) => `
            <div class="perf-deal-source-pills">
                <span><b>${this.formatNumber(Number(row.agent_deal_count || 0))}</b> معامله شخص</span>
                <span><b>${this.formatNumber(Number(row.crm_deal_count || 0))}</b> معامله CRM</span>
                <span><b>${this.formatNumber(Number(row.sudran_deal_count || 0))}</b> معامله سودران</span>
            </div>
        `;
        const renderCard = (row, idx, group, metric, extraClass = '') => `
            <div class="perf-tile ${extraClass}" role="button" tabindex="0" onclick="app.openPerformanceCeoRowDrilldown('${metric}','${group}',${idx})">
                <div class="perf-tile-lbl">${this.escapeHtml(row.label || row.key || '-')}${this.renderSaleOsInfoTooltip(this.getPerformanceCeoDealStatusInfo(row), { title: row.label || row.key || '-' })}</div>
                <div class="perf-tile-val">${this.formatNumber(Number(row.count || 0))}</div>
                <div class="perf-card-sub">${this.formatNumber(Number(row.percent || 0))}٪ | ${this.formatNumber(Number(row.amount || 0))} تومان</div>
                ${pillRow(row)}
            </div>
        `;
        const statusBody = cards.length
            ? `<div class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-7 gap-2">${cards.map((row, idx) => renderCard(row, idx, 'deal_status_cards', 'deal_status')).join('')}</div>`
            : '<div class="perf-empty">معامله‌ای در این بازه پیدا نشد.</div>';
        const previousRows = [previousSuccess.total, previousSuccess.completed, previousSuccess.outstanding].filter(Boolean);
        const previousBody = previousRows.length && Number(previousRows[0]?.count || 0) > 0
            ? `<div class="perf-prev-success mt-4">
                    <div class="perf-prev-success-head">
                        <div>
                            <div class="perf-prev-success-title">معاملات موفق</div>
                            <div class="perf-prev-success-sub">ثبت شده از بازه قبلی، در این بازه موفق شده است</div>
                        </div>
                    </div>
                    <div class="grid grid-cols-1 md:grid-cols-3 gap-2 mt-3">
                        ${previousRows.map((row, idx) => renderCard(row, idx, 'deal_previous_success', 'deal_status', 'is-previous')).join('')}
                    </div>
                </div>`
            : '';
        const mixBody = leadMix.length
            ? `<div class="grid grid-cols-1 md:grid-cols-3 gap-2 mt-3">${leadMix.map((row, idx) => `
                <button type="button" class="perf-badge is-teal" onclick="app.openPerformanceCeoRowDrilldown('deal_status','deal_lead_mix',${idx})">
                    <span>${this.escapeHtml(row.label || row.key || '-')}${this.renderSaleOsInfoTooltip(this.performanceInfoText('تفکیک لیدهای ثبت‌شده در بازه بر اساس جدید/قدیمی و سابقه خرید.', 'persons و سابقه پرداخت/معامله.', 'COUNT لیدهای هر گروه؛ درصد = count / کل لیدهای این بخش × ۱۰۰.'), { title: row.label || row.key || '-' })}</span>
                    <b>${this.formatNumber(Number(row.count || 0))} | ${this.formatNumber(Number(row.percent || 0))}٪</b>
                </button>
            `).join('')}</div>`
            : '';
        return `
            <div class="perf-panel mt-5">
                <div class="perf-panel-title" style="color:#67e8f9"><i class="fa-solid fa-briefcase"></i> وضعیت معاملات${this.renderSaleOsInfoTooltip(this.performanceInfoText('این بخش وضعیت عملیاتی معاملات را از وضعیت مالی جدا می‌کند.', 'معاملات ثبت‌شده داخل بازه، فعالیت‌های ثبت‌شده روی آن‌ها و پرداخت‌های معتبر همان بازه.', 'کارت‌های اصلی فقط معاملات ثبت‌شده داخل بازه را می‌سنجند؛ بخش جدا، معاملات قدیمی را نشان می‌دهد که فروششان در این بازه اتفاق افتاده است.', 'جمع کارت‌های وضعیت ممکن است همیشه برابر کل نباشد، چون بعضی وضعیت‌ها تحلیلی‌اند و می‌توانند با یکدیگر هم‌پوشانی داشته باشند؛ اما هر کارت باید دامنه خودش را شفاف توضیح دهد.'), { title: 'وضعیت معاملات' })}</div>
                ${statusBody}
                ${previousBody}
                ${mixBody}
            </div>
        `;
    },

    renderPerformanceCeoAccessPanel(rows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_access_grants')) return '';
        const body = rows.length
            ? `<div class="grid grid-cols-3 gap-2">
                    ${rows.map((row, idx) => {
                        const [label, cls] = this.ceoStatusBadgeMeta('access', row.status);
                        const color = cls === 'is-green' ? 'rgba(16,185,129,.3)' : (cls === 'is-red' ? 'rgba(248,113,113,.3)' : 'rgba(100,116,139,.3)');
                        const txt = cls === 'is-green' ? '#6ee7b7' : (cls === 'is-red' ? '#fca5a5' : '#cbd5e1');
                        return `
                            <div class="perf-tile" style="border-color:${color}" role="button" tabindex="0" aria-label="${this.escapeHtml(label)}"
                                onclick="app.openPerformanceCeoRowDrilldown('access_status','access_grants',${idx})">
                                <div class="perf-tile-lbl" style="color:${txt}">${this.escapeHtml(label)}${this.renderSaleOsInfoTooltip(this.performanceInfoText('تعداد درخواست‌های دسترسی مشتری در این وضعیت.', 'customer_access_requests.', 'COUNT درخواست‌ها بر اساس status در بازه انتخابی.'), { title: label })}</div>
                                <div class="perf-tile-val">${this.formatNumber(row.count || 0)}</div>
                            </div>`;
                    }).join('')}
                </div>`
            : '<div class="perf-empty">درخواستی در این بازه پیدا نشد.</div>';
        return `
            <div class="perf-panel">
                <div class="perf-panel-title" style="color:#5eead4"><i class="fa-solid fa-key"></i> دسترسی‌های مشتری${this.renderSaleOsInfoTooltip(this.performanceInfoText('وضعیت درخواست‌های دسترسی مشتری در بازه.', 'customer_access_requests.', 'COUNT درخواست‌ها بر اساس وضعیت انجام‌شده، در انتظار یا انجام‌نشده.'), { title: 'دسترسی‌های مشتری' })}</div>
                ${body}
            </div>
        `;
    },

    renderPerformanceCeoChequePanel(statusRows = [], collectionRows = []) {
        if (!this.canPerformanceFeatureClient('performance.table.ceo_cheque_status')) return '';
        const renderRows = (rows, kind, group, metricKey) => rows.map((row, idx) => {
            const [label, cls] = this.ceoStatusBadgeMeta(kind, row.status);
            return `
                <button type="button" class="perf-badge ${cls}" aria-label="${this.escapeHtml(label)}"
                    onclick="app.openPerformanceCeoRowDrilldown('${metricKey}','${group}',${idx})">
                    <span>${this.escapeHtml(label)}${this.renderSaleOsInfoTooltip(this.performanceInfoText('تعداد چک‌ها در این وضعیت.', 'payment_records cheque fields.', 'COUNT چک‌ها بر اساس وضعیت تایید یا وصول.'), { title: label })}</span><b>${this.formatNumber(row.count || 0)}</b>
                </button>`;
        }).join('');
        return `
            <div class="perf-panel">
                <div class="perf-panel-title" style="color:#fcd34d"><i class="fa-solid fa-money-check-dollar"></i> وضعیت کل چک‌ها${this.renderSaleOsInfoTooltip(this.performanceInfoText('نمای وضعیت کل چک‌ها بر اساس تایید، برگشت، وصول و سررسید.', 'payment_records cheque fields.', 'COUNT و SUM چک‌ها با گروه‌بندی وضعیت و ماه سررسید.'), { title: 'وضعیت کل چک‌ها' })}</div>
                <div class="grid grid-cols-2 gap-4">
                    <div>
                        <div class="text-[10.5px] text-gray-500 mb-2">وضعیت تایید</div>
                        <div class="flex flex-col gap-2">${renderRows(statusRows, 'status', 'cheque_status', 'cheque_status') || '<div class="perf-empty">داده‌ای نیست.</div>'}</div>
                    </div>
                    <div>
                        <div class="text-[10.5px] text-gray-500 mb-2">مرحله وصول</div>
                        <div class="flex flex-col gap-2">${renderRows(collectionRows, 'collection', 'cheque_collection_status', 'cheque_collection_status') || '<div class="perf-empty">داده‌ای نیست.</div>'}</div>
                    </div>
                </div>
            </div>
        `;
    },

    async renderPerformanceCeoCharts(report = {}) {
        if (!this.canPerformanceFeatureClient('performance.ceo_report')) return;
        this.destroyPerformanceChart('ceoCash');
        this.destroyPerformanceChart('ceoProduct');
        const cashRows = Array.isArray(report.cash_composition) ? report.cash_composition : [];
        const productRows = (Array.isArray(report.product_sales) ? report.product_sales : []).slice(0, 12);
        const cashEl = document.querySelector('#perf-ceo-cash-chart');
        const prodEl = document.querySelector('#perf-ceo-product-chart');
        if (!cashEl && !prodEl) return;
        try { await this.ensureVendor('apexcharts'); } catch (e) {}
        if (typeof ApexCharts === 'undefined') {
            this.renderCeoChartFallback(cashEl, cashRows, 'cash');
            this.renderCeoChartFallback(prodEl, productRows, 'product');
            return;
        }

        if (cashEl && cashRows.length) {
            const palette = ['#10b981', '#22d3ee', '#3b82f6', '#f59e0b', '#a78bfa'];
            this.state.performanceCharts.ceoCash = new ApexCharts(cashEl, {
                chart: {
                    type: 'donut', height: 210, fontFamily: 'inherit', background: 'transparent',
                    events: {
                        dataPointSelection: (e, ctx, cfg) => this.openPerformanceCeoRowDrilldown('cash_category', 'cash', cfg.dataPointIndex)
                    }
                },
                theme: { mode: 'dark' },
                labels: cashRows.map(r => r.label || r.key || '-'),
                series: cashRows.map(r => Number(r.amount || 0)),
                colors: palette,
                stroke: { colors: ['#0f172a'], width: 2 },
                legend: { show: false },
                dataLabels: { enabled: false },
                plotOptions: { pie: { donut: { size: '62%' } } },
                tooltip: { theme: 'dark', y: { formatter: (v) => `${this.formatNumber(Math.round(Number(v || 0)))} تومان` } }
            });
            this.state.performanceCharts.ceoCash.render();
        }

        if (prodEl && productRows.length) {
            this.state.performanceCharts.ceoProduct = new ApexCharts(prodEl, {
                chart: {
                    type: 'bar', height: Math.max(200, productRows.length * 30 + 40), fontFamily: 'inherit', background: 'transparent',
                    events: {
                        dataPointSelection: (e, ctx, cfg) => this.openPerformanceCeoRowDrilldown('product_sales', 'product_sales', cfg.dataPointIndex)
                    }
                },
                theme: { mode: 'dark' },
                series: [{ name: 'فروش', data: productRows.map(r => Number(r.sold_count || 0)) }],
                xaxis: { categories: productRows.map(r => r.product_name || 'نامشخص'), labels: { style: { colors: '#9ca3af' } } },
                yaxis: { labels: { style: { colors: '#cbd5e1', fontSize: '11px' }, maxWidth: 160 } },
                colors: ['#a78bfa'],
                grid: { borderColor: 'rgba(255,255,255,.06)' },
                plotOptions: { bar: { horizontal: true, borderRadius: 5, barHeight: '62%', distributed: false } },
                dataLabels: { enabled: true, style: { colors: ['#e2e8f0'], fontSize: '11px' } },
                legend: { show: false },
                tooltip: {
                    theme: 'dark',
                    y: { formatter: (v, opts) => { const r = productRows[opts.dataPointIndex] || {}; return `${this.formatNumber(Math.round(Number(v || 0)))} فروش | ${this.formatNumber(Number(r.amount || 0))} تومان`; } }
                }
            });
            this.state.performanceCharts.ceoProduct.render();
        }
    },

    renderCeoChartFallback(el, rows, kind) {
        if (!el || !Array.isArray(rows) || !rows.length) return;
        const valKey = kind === 'cash' ? 'amount' : 'sold_count';
        const labelKey = kind === 'cash' ? 'label' : 'product_name';
        const color = kind === 'cash' ? '#22d3ee' : '#a78bfa';
        const max = Math.max(1, ...rows.map(r => Number(r[valKey] || 0)));
        el.innerHTML = rows.slice(0, 12).map((r) => {
            const v = Number(r[valKey] || 0);
            const w = Math.max(4, Math.min(100, (v / max) * 100));
            return `<div class="perf-barrow" style="display:flex;align-items:center;gap:8px;margin-bottom:7px;font-size:11px">
                <span style="width:120px;color:#cbd5e1" class="truncate">${this.escapeHtml(r[labelKey] || r.key || '-')}</span>
                <span style="flex:1;height:7px;background:rgba(255,255,255,.06);border-radius:4px;overflow:hidden"><span style="display:block;height:100%;width:${w}%;background:${color}"></span></span>
                <span style="color:#fff;font-weight:700;min-width:36px;text-align:left">${this.formatNumber(v)}</span>
            </div>`;
        }).join('');
    },

    async openPerformanceCeoRowDrilldown(metricKey, groupKey, rowIndex) {
        const row = this.state.performanceCeoRows?.[groupKey]?.[rowIndex] || {};
        return this.openPerformanceCeoDrilldown(metricKey, row, groupKey);
    },

    async openPerformanceCeoDrilldown(metricKey, row = {}, groupKey = '') {
        if (!this.canPerformanceFeatureClient('performance.drilldown')) {
            Swal.fire('خطا', 'دسترسی Drilldown داشبورد عملکرد ندارید', 'error');
            return;
        }
        this.state.kpiDrilldownContext = {
            mode: 'performance',
            kind: 'performance_ceo',
            metricKey,
            row,
            groupKey
        };

        const subtitle = document.getElementById('kpi-drilldown-subtitle');
        if (subtitle) {
            let rowLabel = row.label || row.product_name || row.owner_name || row.status || row.acquaintance_source || '';
            if (groupKey === 'marketing_sources' || groupKey === 'registered_deals_sources') {
                rowLabel = [
                    this.getLookupLabel('lead_sources', row.acquaintance_source || ''),
                    this.getLookupLabel('lead_source_details', row.acquaintance_detail || ''),
                    this.getLookupLabel('content_topics', row.content_topic || '')
                ].filter(label => label && label !== '-').join(' / ');
            }
            subtitle.textContent = `گزارش مدیرعامل | ${metricKey}${rowLabel ? ' | ' + rowLabel : ''}`;
        }
        const container = document.getElementById('kpi-drilldown-content');
        if (container) {
            container.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داده‌های مرتبط...</div>';
        }
        this.openModal('modalKpiDrilldown');

        const filters = this.state.performanceFilters || {};
        const payload = {
            table_id: 'performance_ceo_report',
            metric_key: metricKey,
            date_from: filters.date_from || '',
            date_to: filters.date_to || '',
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.join(',') : '',
            product_ids: Array.isArray(filters.product_ids) ? filters.product_ids.join(',') : '',
            deal_statuses: '',
            deal_lead_types: '',
            row_product_name: row.product_name || '',
            row_owner_didar_id: row.owner_didar_id || '',
            row_owner_name: row.owner_name || '',
            row_category: row.key || row.category || '',
            row_status: row.status || '',
            row_acquaintance_source: row.acquaintance_source || '',
            row_acquaintance_detail: row.acquaintance_detail || '',
            row_content_topic: row.content_topic || ''
        };

        this.appendCriteriaToKpiPayload(payload, 'performance');
        const res = await this.req('get_kpi_drilldown', payload);
        if (res.status !== 'success') {
            if (container) container.innerHTML = `<div class="text-sm text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت جزئیات')}</div>`;
            return;
        }
        this.state.kpiDrilldown = {
            tableId: 'performance_ceo_report',
            metricKey,
            row: row || null,
            data: res,
            loading: false
        };
        if (container) container.innerHTML = this.renderKpiDrilldownContent(res);
    },

    switchPerformanceTab(tabKey) {
        this.state.performanceActiveTab = tabKey;
        const tabs = this.state.performanceDashboard?.tabs || {};
        const container = document.getElementById('performance-tab-content');
        if (container) {
            container.innerHTML = this.renderPerformanceTabContent(tabs, tabKey);
        }
        if (!this.state.performanceTabsLoaded) {
            this.loadPerformanceDashboardTabsSection();
        }
    },

    renderPerformanceSummaryCard(title, value, sub1, sub2, borderClass = 'border-gray-500', drillTableId = '', drillMetricKey = '') {
        if (drillMetricKey && !this.canPerformanceFeatureClient(`performance.card.${drillMetricKey}`)) {
            return '';
        }
        const isClickable = drillTableId !== '' && drillMetricKey !== '';
        const info = this.getPerformanceSummaryCardInfo(drillMetricKey, title);
        const clickHandler = isClickable
            ? `onclick="app.openPerformanceChartDrilldown('${drillTableId}','${drillMetricKey}', {})"`
            : '';
        return `
            <div class="glass p-3 border-r-4 ${borderClass} ${isClickable ? 'cursor-pointer hover:border-cyan-400 transition-colors' : ''}" ${clickHandler} title="${isClickable ? 'نمایش جزئیات' : ''}">
                <div class="text-[11px] text-gray-400 mb-1 flex items-center gap-1">${this.escapeHtml(title)}${this.renderSaleOsInfoTooltip(info, { title })}</div>
                <div class="text-lg md:text-xl font-bold text-white leading-7 mb-1">${value}</div>
                <div class="text-[11px] text-gray-400">${sub1 || ''}</div>
                <div class="text-[11px] text-gray-500">${sub2 || ''}</div>
                ${isClickable ? '<div class="text-[10px] text-cyan-300 mt-2">برای مشاهده جزئیات کلیک کنید</div>' : ''}
            </div>
        `;
    },

    renderPerformanceComparisonCard(comparison) {
        const label = this.escapeHtml(comparison?.label || 'مقایسه با بازه قبل');
        const salesCmp = comparison?.summary?.total_sales_amount || {};
        const sign = Number(salesCmp.delta || 0) >= 0 ? '+' : '';
        const deltaPct = Number(salesCmp.delta_percent || 0).toFixed(2);
        const color = Number(salesCmp.delta || 0) >= 0 ? 'text-green-400' : 'text-red-400';
        const info = this.performanceInfoText('مقایسه فروش کل بازه انتخاب‌شده با بازه زمانی قبلی هم‌اندازه.', 'خروجی summary comparison در performance dashboard.', 'delta = فروش بازه فعلی - فروش بازه قبل؛ درصد تغییر = delta / فروش بازه قبل × ۱۰۰.');
        return `
            <div class="glass p-3 border-r-4 border-sky-500">
                <div class="text-[11px] text-gray-400 mb-1 flex items-center gap-1">${label}${this.renderSaleOsInfoTooltip(info, { title: comparison?.label || 'مقایسه با بازه قبل' })}</div>
                <div class="text-xs text-gray-400">فروش بازه قبل: ${this.formatNumber(salesCmp.previous || 0)} تومان</div>
                <div class="text-sm font-bold ${color} mt-1">تغییر: ${sign}${this.formatNumber(salesCmp.delta || 0)} تومان</div>
                <div class="text-[11px] ${color}">(${sign}${deltaPct}%)</div>
            </div>
        `;
    },

    renderPerformanceChartActions(chartKey, sourceKey) {
        return `
            <div class="flex items-center gap-1">
                <button class="px-2 py-1 text-[11px] rounded bg-gray-700 hover:bg-gray-600" onclick="app.exportPerformanceChart('${chartKey}','png')">PNG</button>
                <button class="px-2 py-1 text-[11px] rounded bg-gray-700 hover:bg-gray-600" onclick="app.exportPerformanceChart('${chartKey}','svg')">SVG</button>
                <button class="px-2 py-1 text-[11px] rounded bg-gray-700 hover:bg-gray-600" onclick="app.exportPerformanceChart('${chartKey}','pdf')">PDF</button>
                <button class="px-2 py-1 text-[11px] rounded bg-blue-700 hover:bg-blue-600" onclick="app.exportPerformanceChartCsv('${sourceKey}')">CSV</button>
            </div>
        `;
    },

    renderPerformanceChartControls(type, total) {
        const isSales = type === 'sales';
        const sizeKey = isSales ? 'salesSize' : 'activitySize';
        const offsetKey = isSales ? 'salesOffset' : 'activityOffset';
        const state = this.state.performanceChartWindow || {};
        const size = Math.max(7, Number(state[sizeKey] || 14));
        const maxOffset = Math.max(0, Number(total || 0) - size);
        const offset = Math.min(maxOffset, Math.max(0, Number(state[offsetKey] || 0)));

        if (!total || total <= 7) return '';

        return `
            <div class="mt-3 grid grid-cols-1 gap-2 text-xs text-gray-300 bg-gray-900/40 border border-gray-700 rounded-lg p-2">
                <div class="flex items-center flex-wrap gap-2">
                    <span class="text-gray-400">بازه نمایش:</span>
                    <button type="button" class="px-2 py-1 rounded ${size === 7 ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600'}" onclick="app.setPerformanceChartWindow('${type}', 7)">۷ روز</button>
                    <button type="button" class="px-2 py-1 rounded ${size === 14 ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600'}" onclick="app.setPerformanceChartWindow('${type}', 14)">۱۴ روز</button>
                    <button type="button" class="px-2 py-1 rounded ${size === 30 ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600'}" onclick="app.setPerformanceChartWindow('${type}', 30)">۳۰ روز</button>
                    <button type="button" class="px-2 py-1 rounded ${size >= total ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600'}" onclick="app.setPerformanceChartWindow('${type}', ${Math.max(total, 7)})">همه</button>
                </div>
                <div class="flex items-center justify-between gap-2">
                    <div class="flex items-center gap-2">
                        <button type="button" class="px-2 py-1 rounded bg-gray-700 hover:bg-gray-600" onclick="app.shiftPerformanceChartWindow('${type}', -${Math.max(1, Math.floor(size / 2))})">قبلی</button>
                        <button type="button" class="px-2 py-1 rounded bg-gray-700 hover:bg-gray-600" onclick="app.shiftPerformanceChartWindow('${type}', ${Math.max(1, Math.floor(size / 2))})">بعدی</button>
                        <button type="button" class="px-2 py-1 rounded bg-gray-700 hover:bg-gray-600" onclick="app.jumpPerformanceChartToLatest('${type}')">آخرین بازه</button>
                    </div>
                    <span class="text-gray-400">نقطه ${this.formatNumber(offset + 1)} تا ${this.formatNumber(Math.min(offset + size, total))} از ${this.formatNumber(total)}</span>
                </div>
            </div>
        `;
    },

    togglePerformanceChartDrag(type) {},

    setPerformanceChartWindow(type, size) {
        const isSales = type === 'sales';
        const sizeKey = isSales ? 'salesSize' : 'activitySize';
        const offsetKey = isSales ? 'salesOffset' : 'activityOffset';
        if (!this.state.performanceChartWindow) {
            this.state.performanceChartWindow = { salesSize: 14, salesOffset: 0, activitySize: 14, activityOffset: 0 };
        }
        const num = Math.max(7, Number(size || 14));
        this.state.performanceChartWindow[sizeKey] = num;
        const total = type === 'sales'
            ? ((this.state.performanceDashboard?.charts?.daily_sales || []).length)
            : ((this.state.performanceDashboard?.charts?.daily_activities || []).length);
        const currentSize = Math.max(7, Number(this.state.performanceChartWindow[sizeKey] || 14));
        const maxOffset = Math.max(0, Number(total || 0) - currentSize);
        if (Number(this.state.performanceChartWindow[offsetKey] || 0) > maxOffset) {
            this.state.performanceChartWindow[offsetKey] = maxOffset;
        }
        this.renderPerformanceDashboard(this.state.performanceDashboard || {});
    },

    shiftPerformanceChartWindow(type, delta) {
        const isSales = type === 'sales';
        const offsetKey = isSales ? 'salesOffset' : 'activityOffset';
        const sizeKey = isSales ? 'salesSize' : 'activitySize';
        if (!this.state.performanceChartWindow) return;
        const current = Number(this.state.performanceChartWindow[offsetKey] || 0);
        const size = Math.max(7, Number(this.state.performanceChartWindow[sizeKey] || 14));
        const total = type === 'sales'
            ? ((this.state.performanceDashboard?.charts?.daily_sales || []).length)
            : ((this.state.performanceDashboard?.charts?.daily_activities || []).length);
        const maxOffset = Math.max(0, Number(total || 0) - size);
        this.state.performanceChartWindow[offsetKey] = Math.max(0, Math.min(maxOffset, current + Number(delta || 0)));
        this.renderPerformanceDashboard(this.state.performanceDashboard || {});
    },

    jumpPerformanceChartToLatest(type) {
        const isSales = type === 'sales';
        const offsetKey = isSales ? 'salesOffset' : 'activityOffset';
        const sizeKey = isSales ? 'salesSize' : 'activitySize';
        const total = type === 'sales'
            ? ((this.state.performanceDashboard?.charts?.daily_sales || []).length)
            : ((this.state.performanceDashboard?.charts?.daily_activities || []).length);
        const size = Math.max(7, Number(this.state.performanceChartWindow?.[sizeKey] || 14));
        const maxOffset = Math.max(0, Number(total || 0) - size);
        this.state.performanceChartWindow[offsetKey] = maxOffset;
        this.renderPerformanceDashboard(this.state.performanceDashboard || {});
    },

    async openPerformanceChartDrilldown(tableId, metricKey, rowContext = {}) {
        if (!this.canPerformanceFeatureClient('performance.drilldown')) {
            Swal.fire('خطا', 'دسترسی Drilldown داشبورد عملکرد ندارید', 'error');
            return;
        }
        this.state.kpiDrilldownContext = {
            mode: 'performance',
            kind: 'performance_chart',
            tableId,
            metricKey,
            rowContext: { ...(rowContext || {}) }
        };
        const subtitle = document.getElementById('kpi-drilldown-subtitle');
        if (subtitle) {
            subtitle.textContent = `نمودار | ${tableId} | ${metricKey}`;
        }
        const container = document.getElementById('kpi-drilldown-content');
        if (container) {
            container.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داده‌های مرتبط...</div>';
        }
        this.openModal('modalKpiDrilldown');

        const filters = this.state.performanceFilters || {};
        const payload = {
            table_id: tableId,
            metric_key: metricKey,
            date_from: filters.date_from || '',
            date_to: filters.date_to || '',
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.join(',') : '',
            product_ids: Array.isArray(filters.product_ids) ? filters.product_ids.join(',') : '',
            deal_statuses: '',
            deal_lead_types: '',
            row_date: rowContext.date || '',
            row_owner_didar_id: rowContext.owner_didar_id || '',
            row_owner_name: rowContext.owner_name || '',
            row_product_name: rowContext.product_name || '',
            row_failure_reason: rowContext.failure_reason || '',
            row_activity_type: rowContext.activity_type || '',
            row_stage: rowContext.stage || ''
        };

        this.appendCriteriaToKpiPayload(payload, 'performance');
        const res = await this.req('get_kpi_drilldown', payload);
        if (res.status !== 'success') {
            if (container) container.innerHTML = `<div class="text-sm text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت جزئیات')}</div>`;
            return;
        }
        this.state.kpiDrilldown = {
            tableId,
            metricKey,
            row: rowContext || null,
            data: res,
            loading: false
        };
        if (container) container.innerHTML = this.renderKpiDrilldownContent(res);
    },

    async openConversionCardDrilldown(source, metricKey) {
        if (source === 'performance') {
            return this.openPerformanceChartDrilldown('performance_cards', metricKey, {});
        }
        if (!this.canKpiFeatureClient('kpi.view')) {
            Swal.fire('خطا', 'دسترسی داشبورد KPI ندارید', 'error');
            return;
        }
        this.state.kpiDrilldownContext = {
            mode: 'kpi',
            kind: 'conversion_card',
            tableId: 'performance_cards',
            metricKey,
            rowContext: {}
        };
        const subtitle = document.getElementById('kpi-drilldown-subtitle');
        if (subtitle) {
            subtitle.textContent = `کارت نرخ تبدیل | performance_cards | ${metricKey}`;
        }
        const container = document.getElementById('kpi-drilldown-content');
        if (container) {
            container.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داده‌های مرتبط...</div>';
        }
        this.openModal('modalKpiDrilldown');
        const filters = this.state.kpiFilters || {};
        const payload = {
            table_id: 'performance_cards',
            metric_key: metricKey,
            date_from: filters.date_from || '',
            date_to: filters.date_to || '',
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.join(',') : '',
            product_ids: Array.isArray(filters.product_ids) ? filters.product_ids.join(',') : '',
            deal_statuses: Array.isArray(filters.deal_statuses) ? filters.deal_statuses.join(',') : '',
            deal_lead_types: Array.isArray(filters.deal_lead_types) ? filters.deal_lead_types.join(',') : '',
            row_date: '',
            row_owner_didar_id: '',
            row_owner_name: '',
            row_product_name: '',
            row_failure_reason: '',
            row_activity_type: '',
            row_stage: ''
        };
        this.appendCriteriaToKpiPayload(payload, 'kpi');
        const res = await this.req('get_kpi_drilldown', payload);
        if (res.status !== 'success') {
            if (container) container.innerHTML = `<div class="text-sm text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت جزئیات')}</div>`;
            return;
        }
        this.state.kpiDrilldown = {
            tableId: 'performance_cards',
            metricKey,
            row: null,
            data: res,
            loading: false
        };
        if (container) container.innerHTML = this.renderKpiDrilldownContent(res);
    },

    exportPerformanceChartCsv(sourceKey) {
        const source = this.state.performanceChartSource?.[sourceKey];
        if (!Array.isArray(source) || source.length === 0) {
            Swal.fire('خطا', 'داده‌ای برای خروجی نمودار وجود ندارد', 'error');
            return;
        }
        const headers = Object.keys(source[0] || {});
        const lines = [headers.map((h) => this.csvEscape(h)).join(',')];
        source.forEach((row) => {
            const cells = headers.map((h) => this.csvEscape(row[h]));
            lines.push(cells.join(','));
        });
        const csvText = "\uFEFF" + lines.join('\n');
        const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `performance_${sourceKey}_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.csv`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    },

    async exportPerformanceChart(chartKey, format) {
        const chart = this.state.performanceCharts?.[chartKey];
        if (!chart) {
            Swal.fire('خطا', 'نمودار در دسترس نیست', 'error');
            return;
        }
        const ts = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
        if (format === 'svg') {
            const svg = chart?.el?.querySelector('svg');
            if (!svg) {
                Swal.fire('خطا', 'SVG نمودار پیدا نشد', 'error');
                return;
            }
            const blob = new Blob([svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = `performance_${chartKey}_${ts}.svg`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(link.href);
            return;
        }

        const uri = await chart.dataURI();
        if (format === 'png') {
            const link = document.createElement('a');
            link.href = uri.imgURI;
            link.download = `performance_${chartKey}_${ts}.png`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            return;
        }

        if (format === 'pdf') {
            try { await this.ensureVendor('jspdf'); } catch (e) {}
            if (!window.jspdf || !window.jspdf.jsPDF) {
                Swal.fire('خطا', 'کتابخانه PDF بارگذاری نشده است', 'error');
                return;
            }
            const pdf = new window.jspdf.jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
            const pageWidth = pdf.internal.pageSize.getWidth();
            const pageHeight = pdf.internal.pageSize.getHeight();
            const margin = 24;
            pdf.setFont('helvetica', 'bold');
            pdf.setFontSize(12);
            pdf.text(`Performance Chart: ${chartKey}`, margin, 24);
            pdf.addImage(uri.imgURI, 'PNG', margin, 36, pageWidth - margin * 2, pageHeight - 70, undefined, 'FAST');
            pdf.save(`performance_${chartKey}_${ts}.pdf`);
        }
    },

    renderPerformanceTabContent(tabs, activeTab) {
        const sections = [];

        if (activeTab === 'activity') {
            sections.push(
                { key: 'activity_summary', title: 'خلاصه فعالیت روزانه', tableId: 'activity_summary', rows: tabs?.activity?.activity_summary || [] },
                { key: 'activity_type_details', title: 'جزئیات نوع فعالیت', tableId: 'activity_type_details', rows: tabs?.activity?.activity_type_details || [] }
            );
        } else if (activeTab === 'sales_deals') {
            sections.push(
                { key: 'deal_status', title: 'وضعیت معاملات', tableId: 'deal_status', rows: tabs?.sales_deals?.deal_status || [] },
                { key: 'lead_status', title: 'وضعیت لیدها', tableId: 'lead_status', rows: tabs?.sales_deals?.lead_status || [] },
                { key: 'conversion_status', title: 'وضعیت نرخ تبدیل', tableId: 'conversion_status', rows: tabs?.sales_deals?.conversion_status || [] }
            );
        } else if (activeTab === 'payments') {
            sections.push(
                { key: 'payment_mix', title: 'پرداخت‌ها', tableId: 'payment_mix', rows: tabs?.payments?.payment_mix || [] },
                { key: 'payment_status', title: 'وضعیت پرداخت‌ها', tableId: 'payment_status', rows: tabs?.payments?.payment_status || [] }
            );
        } else if (activeTab === 'products') {
            sections.push(
                { key: 'product_status', title: 'وضعیت محصولات', tableId: 'product_status', rows: tabs?.products?.product_status || [] },
                { key: 'product_sold_by_owner_day', title: 'محصولات فروخته‌شده', tableId: 'product_sold_by_owner_day', rows: tabs?.products?.product_sold_by_owner_day || [] },
                { key: 'product_requests_by_owner_day', title: 'محصولات درخواستی', tableId: 'product_requests_by_owner_day', rows: tabs?.products?.product_requests_by_owner_day || [] },
                { key: 'product_failed_by_owner_day', title: 'محصولات فروش‌نرفته', tableId: 'product_failed_by_owner_day', rows: tabs?.products?.product_failed_by_owner_day || [] }
            );
        } else {
            sections.push(
                { key: 'failure_reasons', title: 'دلایل شکست', tableId: 'failure_reasons', rows: tabs?.failure_reasons?.failure_reasons || [] }
            );
        }

        const visibleSections = sections.filter(section => this.canPerformanceFeatureClient(`performance.table.${section.tableId}`));
        if (!visibleSections.length) {
            return `<div class="text-center text-gray-500 py-6 text-sm">دسترسی به جدول‌های این تب ندارید</div>`;
        }
        return visibleSections.map(section => `
            <div class="perf-panel mb-4">
                <div class="perf-panel-title" style="color:#67e8f9"><i class="fa-solid fa-table-cells"></i> ${this.escapeHtml(section.title)}${this.renderSaleOsInfoTooltip(this.getKpiAdvancedTableDefinition(section.tableId)?.description ? [
                    `تعریف: ${this.getKpiAdvancedTableDefinition(section.tableId).description.definition || ''}`,
                    `منبع داده: ${this.getKpiAdvancedTableDefinition(section.tableId).description.source || ''}`,
                    `فرمول: ${this.getKpiAdvancedTableDefinition(section.tableId).description.formula || ''}`
                ].filter(Boolean) : this.performanceInfoText(section.title, `جدول ${section.tableId}.`, 'براساس ردیف‌های همین بخش و فیلترهای فعال داشبورد.'), { title: section.title })}</div>
                ${this.renderPerformanceSimpleTable(section)}
            </div>
        `).join('');
    },

    formatPerformanceCell(value, type) {
        if (type === 'amount') return `${this.formatNumber(Number(value || 0))} تومان`;
        if (type === 'percent') return `${Number(value || 0).toFixed(2)}%`;
        if (type === 'day') return `${Number(value || 0).toFixed(2)} روز`;
        if (type === 'count') return this.formatNumber(Number(value || 0));
        if (type === 'date') return this.formatKpiDate(value);
        return this.escapeHtml(String(value ?? '-'));
    },

    async openPerformanceDrilldown(tableId, metricKey, sectionKey, rowIndex) {
        if (!this.canPerformanceFeatureClient('performance.drilldown')) {
            Swal.fire('خطا', 'دسترسی Drilldown داشبورد عملکرد ندارید', 'error');
            return;
        }
        const rows = this.state.performanceRenderedRows?.[sectionKey] || [];
        const row = rows?.[rowIndex];
        if (!row) return;
        this.state.kpiDrilldownContext = {
            mode: 'performance',
            kind: 'performance_table',
            tableId,
            metricKey,
            sectionKey,
            rowIndex
        };

        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const col = definition?.columns?.find(c => c.key === metricKey);
        const metricLabel = col?.label || metricKey;
        const tableTitle = definition?.title || tableId;

        const subtitle = document.getElementById('kpi-drilldown-subtitle');
        if (subtitle) {
            subtitle.textContent = `${tableTitle} | ${metricLabel} | تاریخ: ${row.date ? this.formatKpiDate(row.date) : 'همه'} | کارشناس: ${row.owner_name || 'همه'}`;
        }
        const container = document.getElementById('kpi-drilldown-content');
        if (container) {
            container.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داده‌های مرتبط...</div>';
        }
        this.openModal('modalKpiDrilldown');

        const filters = this.state.performanceFilters || {};
        const payload = {
            table_id: tableId,
            metric_key: metricKey,
            date_from: filters.date_from || '',
            date_to: filters.date_to || '',
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.join(',') : '',
            product_ids: Array.isArray(filters.product_ids) ? filters.product_ids.join(',') : '',
            deal_statuses: '',
            deal_lead_types: '',
            row_date: row.date || '',
            row_owner_didar_id: row.owner_didar_id || '',
            row_owner_name: row.owner_name || '',
            row_product_name: row.product_name || '',
            row_failure_reason: row.failure_reason || '',
            row_activity_type: row.activity_type || ''
        };

        this.appendCriteriaToKpiPayload(payload, 'performance');
        const res = await this.req('get_kpi_drilldown', payload);
        if (res.status !== 'success') {
            if (container) container.innerHTML = `<div class="text-sm text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت جزئیات')}</div>`;
            return;
        }
        this.state.kpiDrilldown = {
            tableId,
            metricKey,
            row: row || null,
            data: res,
            loading: false
        };
        if (container) container.innerHTML = this.renderKpiDrilldownContent(res);
    },

    renderPerformanceSimpleTable(section) {
        const data = Array.isArray(section?.rows) ? section.rows : [];
        if (data.length === 0) {
            return '<div class="text-xs text-gray-500 py-3">داده‌ای برای این بخش وجود ندارد.</div>';
        }
        const tableId = section.tableId;
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const definitionColumns = Array.isArray(definition?.columns) ? definition.columns : [];
        const rowKeys = Object.keys(data[0] || {});
        let cols = definitionColumns.filter(col => rowKeys.includes(col.key) && col.key !== 'owner_didar_id');
        if (cols.length === 0) {
            cols = rowKeys
                .filter(k => k !== 'owner_didar_id')
                .map(k => ({ key: k, label: k, type: k === 'date' ? 'date' : (typeof data[0][k] === 'number' ? 'count' : 'text') }));
        }

        this.state.performanceRenderedRows[section.key] = data;

        const header = cols.map(col => `<th class="border border-gray-700 p-2 text-xs">${this.escapeHtml(col.label)}${this.renderSaleOsInfoTooltip(this.getPerformanceTableColumnInfo(tableId, col.key, col.label, col), { title: col.label })}</th>`).join('');
        const numericColMax = {};
        cols.forEach(col => {
            if (['count', 'amount', 'percent', 'day'].includes(col.type)) {
                numericColMax[col.key] = Math.max(0, ...data.map(r => Number(r[col.key] || 0)));
            }
        });

        const body = data.slice(0, 250).map((row, idx) => {
            const tds = cols.map(col => {
                const formatted = this.formatPerformanceCell(row[col.key], col.type);
                const clickable = !['text', 'date'].includes(col.type);
                let heatStyle = '';
                if (numericColMax[col.key] && numericColMax[col.key] > 0) {
                    const v = Number(row[col.key] || 0);
                    const ratio = Math.max(0, Math.min(1, v / numericColMax[col.key]));
                    const alpha = Math.min(0.40, ratio * 0.40);
                    if (col.type === 'percent') {
                        heatStyle = `background-color: rgba(168,85,247,${alpha.toFixed(3)});`;
                    } else if (col.type === 'amount') {
                        heatStyle = `background-color: rgba(16,185,129,${alpha.toFixed(3)});`;
                    } else if (col.type === 'day') {
                        heatStyle = `background-color: rgba(245,158,11,${alpha.toFixed(3)});`;
                    } else {
                        heatStyle = `background-color: rgba(59,130,246,${alpha.toFixed(3)});`;
                    }
                }
                if (!clickable) {
                    return `<td class="border border-gray-700/60 p-2 text-xs text-center" style="${heatStyle}">${formatted}</td>`;
                }
                return `<td class="border border-gray-700/60 p-2 text-xs text-center" style="${heatStyle}">
                    <button type="button" class="text-cyan-300 hover:text-cyan-200 hover:underline transition-colors w-full" onclick="app.openPerformanceDrilldown('${tableId}','${col.key}','${section.key}',${idx})">${formatted}</button>
                </td>`;
            }).join('');
            return `<tr class="${idx % 2 === 0 ? 'bg-gray-900/30' : 'bg-gray-900/10'}">${tds}</tr>`;
        }).join('');
        return `<div class="overflow-x-auto"><table class="w-full min-w-[900px] text-xs"><thead><tr class="bg-gray-800">${header}</tr></thead><tbody>${body}</tbody></table></div>`;
    },

    destroyPerformanceChart(key) {
        if (this.state.performanceCharts?.[key]) {
            try { this.state.performanceCharts[key].destroy(); } catch (e) {}
            this.state.performanceCharts[key] = null;
        }
    },

    async renderPerformanceCharts(charts) {
        if (!this.canPerformanceFeatureClient('performance.view')) {
            return;
        }
        try { await this.ensureVendor('apexcharts'); } catch (e) {}
        if (typeof ApexCharts === 'undefined') {
            console.warn('ApexCharts library not loaded.');
            return;
        }

        const common = {
            chart: {
                toolbar: { show: false },
                zoom: { enabled: false },
                selection: { enabled: false },
                animations: { enabled: true },
                fontFamily: 'inherit',
                background: 'transparent'
            },
            theme: { mode: 'dark' },
            grid: { borderColor: '#374151' },
            legend: { labels: { colors: '#d1d5db' } },
            xaxis: { labels: { style: { colors: '#9ca3af' } } },
            yaxis: { labels: { style: { colors: '#9ca3af' } } },
            tooltip: { theme: 'dark' }
        };

        this.destroyPerformanceChart('dailySales');
        this.destroyPerformanceChart('dailyActivities');
        this.destroyPerformanceChart('dealStatus');
        this.destroyPerformanceChart('productShare');
        this.destroyPerformanceChart('conversionFunnel');

        const canChartDailySales = this.canPerformanceFeatureClient('performance.chart.daily_sales');
        const canChartDailyActivities = this.canPerformanceFeatureClient('performance.chart.daily_activities');
        const canChartDealStatus = this.canPerformanceFeatureClient('performance.chart.deal_status');
        const canChartProductShare = this.canPerformanceFeatureClient('performance.chart.product_share');
        const canChartFunnel = this.canPerformanceFeatureClient('performance.chart.conversion_funnel');

        const dailySalesRaw = Array.isArray(charts.daily_sales) ? charts.daily_sales : [];
        const dailySalesPrevRaw = Array.isArray(charts.daily_sales_previous) ? charts.daily_sales_previous : [];
        this.state.performanceChartSource.daily_sales = dailySalesRaw;
        const salesWindow = this.state.performanceChartWindow || {};
        const salesSize = Math.max(7, Number(salesWindow.salesSize || 14));
        const salesOffset = Math.max(0, Number(salesWindow.salesOffset || 0));
        const salesMaxOffset = Math.max(0, dailySalesRaw.length - salesSize);
        const salesSlice = dailySalesRaw.slice(Math.min(salesOffset, salesMaxOffset), Math.min(salesOffset, salesMaxOffset) + salesSize);
        const salesPrevSlice = dailySalesPrevRaw.slice(Math.min(salesOffset, Math.max(0, dailySalesPrevRaw.length - salesSize)), Math.min(salesOffset, Math.max(0, dailySalesPrevRaw.length - salesSize)) + salesSize);
        const salesLabels = salesSlice.map(i => this.formatKpiDate(i.payment_date || i.date || ''));
        const salesAmountSeries = salesSlice.map(i => Number(i.collected_amount || 0));
        const salesDealsSeries = salesSlice.map(i => Number(i.deal_count || 0));
        const salesPrevSeries = salesPrevSlice.map(i => Number(i.collected_amount || 0));
        const targetDaily = Number((this.state.performanceDashboard?.summary_cards?.target_daily_amount) || 0);
        if (canChartDailySales && document.querySelector('#perf-chart-daily-sales')) this.state.performanceCharts.dailySales = new ApexCharts(document.querySelector('#perf-chart-daily-sales'), {
            ...common,
            chart: {
                ...common.chart,
                type: 'line',
                height: 320,
                events: {
                    dataPointSelection: (event, chartContext, config) => {
                        const point = salesSlice[config.dataPointIndex];
                        if (!point) return;
                        this.openPerformanceChartDrilldown('performance_charts', 'daily_sales_point', {
                            date: point.date || point.payment_date || ''
                        });
                    }
                }
            },
            annotations: targetDaily > 0 ? {
                yaxis: [{
                    y: targetDaily,
                    borderColor: '#f59e0b',
                    label: { text: `هدف روزانه: ${this.formatNumber(targetDaily)}`, style: { color: '#111827', background: '#f59e0b' } }
                }]
            } : undefined,
            series: [
                { name: 'فروش (تومان)', type: 'line', data: salesAmountSeries },
                { name: 'تعداد معامله', type: 'column', data: salesDealsSeries },
                { name: 'فروش بازه قبل', type: 'line', data: salesPrevSeries }
            ],
            legend: {
                ...common.legend,
                formatter: (seriesName, opts) => {
                    const series = opts?.w?.globals?.series?.[opts.seriesIndex] || [];
                    const total = Array.isArray(series)
                        ? series.reduce((sum, val) => sum + Number(val || 0), 0)
                        : Number(series || 0);
                    return `${seriesName} (${this.formatNumber(Math.round(total))})`;
                }
            },
            dataLabels: { enabled: false },
            xaxis: { categories: salesLabels, labels: { style: { colors: '#9ca3af' } } },
            stroke: { width: [3, 0, 2], dashArray: [0, 0, 6] },
            yaxis: [
                {
                    title: { text: 'مبلغ فروش', style: { color: '#9ca3af' } },
                    labels: {
                        style: { colors: '#9ca3af' },
                        formatter: (val) => this.formatNumber(Math.round(Number(val || 0)))
                    }
                },
                {
                    opposite: true,
                    title: { text: 'تعداد معامله', style: { color: '#9ca3af' } },
                    labels: {
                        style: { colors: '#9ca3af' },
                        formatter: (val) => this.formatNumber(Math.round(Number(val || 0)))
                    }
                }
            ],
            tooltip: {
                y: {
                    formatter: (val, opts) => {
                        const name = opts?.seriesIndex === 1 ? 'معامله' : 'تومان';
                        return `${this.formatNumber(Math.round(Number(val || 0)))} ${name}`;
                    }
                }
            }
        });
        if (canChartDailySales && this.state.performanceCharts.dailySales) this.state.performanceCharts.dailySales.render();

        const dailyActivitiesRaw = Array.isArray(charts.daily_activities) ? charts.daily_activities : [];
        const dailyActivitiesPrevRaw = Array.isArray(charts.daily_activities_previous) ? charts.daily_activities_previous : [];
        const dailyActivitiesByTypeRaw = Array.isArray(charts.daily_activities_by_type) ? charts.daily_activities_by_type : [];
        this.state.performanceChartSource.daily_activities = dailyActivitiesRaw;
        this.state.performanceChartSource.daily_activities_by_type = dailyActivitiesByTypeRaw;
        const activitySize = Math.max(7, Number(salesWindow.activitySize || 14));
        const activityOffset = Math.max(0, Number(salesWindow.activityOffset || 0));
        const actMaxOffset = Math.max(0, dailyActivitiesRaw.length - activitySize);
        const activitySlice = dailyActivitiesRaw.slice(Math.min(activityOffset, actMaxOffset), Math.min(activityOffset, actMaxOffset) + activitySize);
        const activityPrevSlice = dailyActivitiesPrevRaw.slice(Math.min(activityOffset, Math.max(0, dailyActivitiesPrevRaw.length - activitySize)), Math.min(activityOffset, Math.max(0, dailyActivitiesPrevRaw.length - activitySize)) + activitySize);
        const actLabels = activitySlice.map(i => this.formatKpiDate(i.date || ''));
        const activityDateRawLabels = activitySlice.map(i => (i.date || ''));
        const typeTotals = {};
        dailyActivitiesByTypeRaw.forEach((r) => {
            const k = r.activity_type || 'فعالیت';
            typeTotals[k] = (typeTotals[k] || 0) + Number(r.activities_count || 0);
        });
        const topTypes = Object.entries(typeTotals)
            .sort((a, b) => Number(b[1]) - Number(a[1]))
            .slice(0, 4)
            .map((entry) => entry[0]);
        const activityTypeSeries = topTypes.map((typeName) => {
            const data = activityDateRawLabels.map((rawDate) => {
                const found = dailyActivitiesByTypeRaw.find((r) => (r.date || '') === rawDate && (r.activity_type || 'فعالیت') === typeName);
                return Number(found?.activities_count || 0);
            });
            return { name: typeName, data };
        });
        const otherSeries = {
            name: 'سایر',
            data: activityDateRawLabels.map((rawDate) => {
                return dailyActivitiesByTypeRaw
                    .filter((r) => (r.date || '') === rawDate && !topTypes.includes(r.activity_type || 'فعالیت'))
                    .reduce((sum, r) => sum + Number(r.activities_count || 0), 0);
            })
        };
        if (otherSeries.data.some((v) => Number(v) > 0)) {
            activityTypeSeries.push(otherSeries);
        }
        const totalActivitySeries = {
            name: 'کل فعالیت‌ها',
            type: 'line',
            data: activitySlice.map((i) => Number(i.activities_count || 0))
        };
        const prevActivitySeries = {
            name: 'کل فعالیت بازه قبل',
            type: 'line',
            data: activityPrevSlice.map((i) => Number(i.activities_count || 0))
        };
        if (canChartDailyActivities && document.querySelector('#perf-chart-daily-activities')) this.state.performanceCharts.dailyActivities = new ApexCharts(document.querySelector('#perf-chart-daily-activities'), {
            ...common,
            chart: {
                ...common.chart,
                type: 'bar',
                stacked: true,
                height: 420,
                events: {
                    dataPointSelection: (event, chartContext, config) => {
                        const rawDate = activityDateRawLabels[config.dataPointIndex];
                        const typeName = topTypes[config.seriesIndex] || '';
                        this.openPerformanceChartDrilldown('activity_type_details', 'activity_count_total', {
                            date: rawDate || '',
                            activity_type: typeName
                        });
                    }
                }
            },
            plotOptions: {
                bar: {
                    columnWidth: '55%'
                }
            },
            series: [...activityTypeSeries, totalActivitySeries, prevActivitySeries],
            dataLabels: { enabled: false },
            xaxis: { categories: actLabels, labels: { style: { colors: '#9ca3af' } } },
            stroke: { width: activityTypeSeries.map(() => 0).concat([3, 2]), dashArray: activityTypeSeries.map(() => 0).concat([0, 6]) },
            colors: ['#60a5fa','#34d399','#f59e0b','#a78bfa','#14b8a6','#ef4444','#94a3b8'],
            yaxis: {
                labels: {
                    style: { colors: '#9ca3af' },
                    formatter: (val) => this.formatNumber(Math.round(Number(val || 0)))
                }
            },
            legend: {
                ...common.legend,
                position: 'bottom',
                horizontalAlign: 'center',
                itemMargin: { horizontal: 8, vertical: 6 },
                formatter: (seriesName, opts) => {
                    const series = opts?.w?.globals?.series?.[opts.seriesIndex] || [];
                    const total = Array.isArray(series)
                        ? series.reduce((sum, val) => sum + Number(val || 0), 0)
                        : Number(series || 0);
                    return `${seriesName} (${this.formatNumber(Math.round(total))})`;
                }
            },
            tooltip: {
                y: {
                    formatter: (val) => `${this.formatNumber(Math.round(Number(val || 0)))} فعالیت`
                }
            }
        });
        if (canChartDailyActivities && this.state.performanceCharts.dailyActivities) this.state.performanceCharts.dailyActivities.render();

        const dealStatus = Array.isArray(charts.deal_status_breakdown) ? charts.deal_status_breakdown : [];
        this.state.performanceChartSource.deal_status_breakdown = dealStatus;
        if (canChartDealStatus && document.querySelector('#perf-chart-deal-status')) this.state.performanceCharts.dealStatus = new ApexCharts(document.querySelector('#perf-chart-deal-status'), {
            ...common,
            chart: {
                ...common.chart,
                type: 'donut',
                height: 320,
                events: {
                    dataPointSelection: (event, chartContext, config) => {
                        const item = dealStatus[config.dataPointIndex];
                        if (!item) return;
                        const map = {
                            'جاری': ['performance_cards', 'open_pipeline_count'],
                            'دارای فروش': ['performance_cards', 'sales_total'],
                            'بدون فروش': ['performance_cards', 'without_payment_count'],
                            'ناموفق': ['performance_cards', 'lost_count']
                        };
                        const route = map[item.status] || ['performance_cards', 'open_pipeline_count'];
                        this.openPerformanceChartDrilldown(route[0], route[1], {});
                    }
                }
            },
            labels: dealStatus.map(i => i.status || '-'),
            series: dealStatus.map(i => Number(i.count || 0)),
            legend: {
                ...common.legend,
                formatter: (seriesName, opts) => `${seriesName} (${this.formatNumber(opts.w.globals.series[opts.seriesIndex] || 0)})`
            },
            tooltip: {
                y: {
                    formatter: (val) => `${this.formatNumber(Math.round(Number(val || 0)))} مورد`
                }
            }
        });
        if (canChartDealStatus && this.state.performanceCharts.dealStatus) this.state.performanceCharts.dealStatus.render();

        const productShare = Array.isArray(charts.product_sales_share) ? charts.product_sales_share : [];
        this.state.performanceChartSource.product_sales_share = productShare;
        if (canChartProductShare && document.querySelector('#perf-chart-product-share')) this.state.performanceCharts.productShare = new ApexCharts(document.querySelector('#perf-chart-product-share'), {
            ...common,
            chart: {
                ...common.chart,
                type: 'pie',
                height: 320,
                events: {
                    dataPointSelection: (event, chartContext, config) => {
                        const item = productShare[config.dataPointIndex];
                        if (!item) return;
                        this.openPerformanceChartDrilldown('performance_charts', 'product_sales_share_item', {
                            product_name: item.product_name || ''
                        });
                    }
                }
            },
            labels: productShare.map(i => i.product_name || '-'),
            series: productShare.map(i => Number(i.deal_count || 0)),
            legend: {
                ...common.legend,
                formatter: (seriesName, opts) => `${seriesName} (${this.formatNumber(opts.w.globals.series[opts.seriesIndex] || 0)})`
            },
            tooltip: {
                y: {
                    formatter: (val) => `${this.formatNumber(Math.round(Number(val || 0)))} فروش`
                }
            }
        });
        if (canChartProductShare && this.state.performanceCharts.productShare) this.state.performanceCharts.productShare.render();

        const funnel = Array.isArray(charts.conversion_funnel) ? charts.conversion_funnel : [];
        this.state.performanceChartSource.conversion_funnel = funnel;
        if (canChartFunnel && document.querySelector('#perf-chart-conversion-funnel')) this.state.performanceCharts.conversionFunnel = new ApexCharts(document.querySelector('#perf-chart-conversion-funnel'), {
            ...common,
            chart: {
                ...common.chart,
                type: 'bar',
                height: 320,
                events: {
                    dataPointSelection: (event, chartContext, config) => {
                        const item = funnel[config.dataPointIndex];
                        if (!item) return;
                        this.openPerformanceChartDrilldown('performance_charts', 'conversion_funnel_stage', { stage: item.stage || '' });
                    }
                }
            },
            plotOptions: { bar: { horizontal: true, distributed: true, barHeight: '68%' } },
            series: [{ name: 'تعداد', data: funnel.map((f) => Number(f.count || 0)) }],
            xaxis: {
                categories: funnel.map((f) => f.stage || '-'),
                labels: { style: { colors: '#9ca3af' } }
            },
            yaxis: { labels: { style: { colors: '#9ca3af' } } },
            dataLabels: {
                enabled: true,
                formatter: (v) => this.formatNumber(v)
            },
            tooltip: {
                y: { formatter: (v) => `${this.formatNumber(v)} مورد` }
            }
        });
        if (canChartFunnel && this.state.performanceCharts.conversionFunnel) this.state.performanceCharts.conversionFunnel.render();
    },

    initPerformanceChartPan(type, selector, total) {
        return;
    },

    // ========== KPI DASHBOARD ==========
    async showKPIDashboard() {
        if (!this.canKpiFeatureClient('kpi.view')) {
            Swal.fire('خطا', 'دسترسی به داشبورد KPI ندارید', 'error');
            return;
        }
        this.openModal('modalKPI');
        if (!this.state.kpiFilters.date_from) {
            const range = this.getJalaliMonthRange();
            this.state.kpiFilters.date_from = range.date_from;
        }
        if (!this.state.kpiFilters.date_to) {
            const range = this.getJalaliMonthRange();
            this.state.kpiFilters.date_to = range.date_to;
        }
        await this.loadKpiFilterData();
        await this.loadKPIDashboard({ useState: true });
    },

    initKpiDatePickers() {
        console.log('[DEBUG] Initializing KPI datepickers...');
        console.log('[DEBUG] jQuery available:', typeof $ !== 'undefined');
        console.log('[DEBUG] $.fn available:', typeof $?.fn !== 'undefined');
        console.log('[DEBUG] $.fn.persianDatepicker available:', typeof $.fn?.persianDatepicker === 'function');

        // Wait for elements to be rendered
        const self = this;
        setTimeout(function() {
            if (typeof $ === 'undefined') {
                console.warn('[DEBUG] jQuery not available for KPI datepicker');
                return;
            }

            const $dateFrom = $('#kpi-date-from');
            const $dateTo = $('#kpi-date-to');

            console.log('[DEBUG] $dateFrom element:', $dateFrom.length > 0 ? 'found' : 'not found');
            console.log('[DEBUG] $dateTo element:', $dateTo.length > 0 ? 'found' : 'not found');

            const initOnce = ($input, desiredValue, label) => {
                if (!($input.length > 0) || typeof $.fn?.persianDatepicker !== 'function') {
                    return false;
                }
                if ($input.data('kpiDatepickerInit') !== 1) {
                    try {
                        $input.persianDatepicker('destroy');
                    } catch (error) {
                    }
                    $input.persianDatepicker({
                        format: 'YYYY/MM/DD',
                        initialValue: false,
                        autoClose: true,
                        observer: false
                    });
                    $input.data('kpiDatepickerInit', 1);
                    console.log(`[DEBUG] KPI ${label} datepicker initialized`);
                }
                const inputEl = $input.get(0);
                if (document.activeElement !== inputEl) {
                    const normalizedDesired = desiredValue ? (toPersianDate(desiredValue) || desiredValue) : '';
                    if (normalizedDesired && String($input.val() || '') !== String(normalizedDesired)) {
                        $input.val(normalizedDesired);
                    }
                }
                return true;
            };

            if (!initOnce($dateFrom, (self.state.kpiFilters || {}).date_from || '', 'Date From')) {
                console.warn('[DEBUG] KPI Date From datepicker initialization failed', {
                    exists: $dateFrom.length > 0,
                    hasPersianDatepicker: typeof $.fn?.persianDatepicker === 'function'
                });
            }

            if (!initOnce($dateTo, (self.state.kpiFilters || {}).date_to || '', 'Date To')) {
                console.warn('[DEBUG] KPI Date To datepicker initialization failed', {
                    exists: $dateTo.length > 0,
                    hasPersianDatepicker: typeof $.fn?.persianDatepicker === 'function'
                });
            }
        }, 1000);
    },

    async loadKpiFilterData(resourceKey = 'kpi') {
        // Load users for owner filter
        const ownerResourceKey = String(resourceKey || 'kpi').trim() || 'kpi';
        if (this.isAdminLikeForAnalytics() || this.hasCrossUserScopeClient(ownerResourceKey)) {
            try {
                const usersRes = await this.req('get_didar_users', { resource_key: ownerResourceKey });
                if (usersRes.status === 'success') {
                    this.state.kpiUsers = (usersRes.users || []).map(user => ({
                        didar_user_id: user.owner_id || user.didar_user_id || '',
                        display_name: user.display_name || user.email || '',
                        email: user.email || '',
                        is_active: user.is_active ?? 1
                    }));
                } else {
                    console.warn('Failed to load users:', usersRes.message);
                    this.state.kpiUsers = [];
                }
            } catch (error) {
                console.error('Error loading users:', error);
                this.state.kpiUsers = [];
            }

            if (this.isAdminLikeForAnalytics() && this.state.kpiUsers.length === 0) {
                try {
                    const usersRes = await this.req('get_users_list');
                    if (usersRes.status === 'success') {
                        this.state.kpiUsers = usersRes.users || [];
                    } else {
                        console.warn('Failed to load users:', usersRes.message);
                        this.state.kpiUsers = [];
                    }
                } catch (error) {
                    console.error('Error loading users:', error);
                    this.state.kpiUsers = [];
                }
            }
        } else {
            this.state.kpiUsers = [{
                didar_user_id: this.state.user?.didar_id || '',
                display_name: this.state.user?.name || '',
                is_active: 1
            }];
        }
        this.populateKpiOwnerFilter();

        // Load products for product filter
        try {
            const productsRes = await this.req('get_kpi_products');
            if (productsRes.status === 'success') {
                this.state.kpiProducts = productsRes.products || [];
                this.populateKpiProductFilter();
            } else {
                console.warn('Failed to load products:', productsRes.message);
                this.state.kpiProducts = [];
                this.populateKpiProductFilter();
            }
        } catch (error) {
            console.error('Error loading products:', error);
            this.state.kpiProducts = [];
            this.populateKpiProductFilter();
        }
    },

    populateKpiOwnerFilter() {
        const select = document.getElementById('kpi-owner-filter');
        if (!select) return;

        const selectedValues = Array.isArray(this.state.kpiFilters?.owner_didar_ids)
            ? this.state.kpiFilters.owner_didar_ids.map(v => String(v))
            : [];
        select.innerHTML = '';

        const allOption = document.createElement('option');
        allOption.value = '__all__';
        allOption.textContent = 'همه مسئول‌ها';
        allOption.selected = selectedValues.length === 0 || selectedValues.includes('__all__');
        select.appendChild(allOption);

        // فقط کاربران active را فیلتر کن
        const activeUsers = (this.state.kpiUsers || []).filter(user => Number(user.is_active) === 1);

        activeUsers.forEach(user => {
            const opt = document.createElement('option');
            opt.value = user.didar_user_id || user.owner_id || '';
            if (!opt.value) return;
            const name = user.display_name || user.email || (user.first_name + ' ' + user.last_name);
            opt.textContent = name;
            if (selectedValues.includes(String(opt.value))) {
                opt.selected = true;
            }
            select.appendChild(opt);
        });
    },

    populateKpiProductFilter() {
        const select = document.getElementById('kpi-product-filter');
        if (!select) return;

        const selectedValues = Array.isArray(this.state.kpiFilters?.product_ids)
            ? this.state.kpiFilters.product_ids.map(v => String(v))
            : [];
        select.innerHTML = '';

        const allOption = document.createElement('option');
        allOption.value = '__all__';
        allOption.textContent = 'همه محصولات';
        allOption.selected = selectedValues.length === 0 || selectedValues.includes('__all__');
        select.appendChild(allOption);

        this.state.kpiProducts.forEach(product => {
            const opt = document.createElement('option');
            opt.value = product.id;
            opt.textContent = product.name;
            if (!opt.value) return;
            if (selectedValues.includes(String(product.id))) {
                opt.selected = true;
            }
            select.appendChild(opt);
        });
    },

    async loadKPIDashboard(options = {}) {
        if (!this.canKpiFeatureClient('kpi.view')) {
            Swal.fire('خطا', 'دسترسی به داشبورد KPI ندارید', 'error');
            return;
        }
        const useState = options.useState === true;
        const stateFilters = this.state.kpiFilters || {};
        const defaultRange = this.getJalaliMonthRange();
        let dateFrom = stateFilters.date_from || defaultRange.date_from;
        let dateTo = stateFilters.date_to || defaultRange.date_to;
        let ownerIds = Array.isArray(stateFilters.owner_didar_ids) ? [...stateFilters.owner_didar_ids] : [];
        let productIds = Array.isArray(stateFilters.product_ids) ? [...stateFilters.product_ids] : [];
        let dealStatuses = Array.isArray(stateFilters.deal_statuses) ? [...stateFilters.deal_statuses] : [];
        let dealLeadTypes = Array.isArray(stateFilters.deal_lead_types) ? [...stateFilters.deal_lead_types] : [];
        let criteria = this.normalizeKpiCriteria(stateFilters.criteria || {});
        if (ownerIds.length === 0 && stateFilters.owner_didar_id) {
            ownerIds = [stateFilters.owner_didar_id];
        }
        if (productIds.length === 0 && stateFilters.product_id) {
            productIds = [stateFilters.product_id];
        }

        if (!useState) {
            const dateFromJ = (document.getElementById('kpi-date-from')?.value || '').trim();
            const dateToJ = (document.getElementById('kpi-date-to')?.value || '').trim();

            if (dateFromJ) {
                const dateFromG = persianToGregorian(this.toEnglishDigits(dateFromJ), '00:00');
                if (dateFromG) {
                    dateFrom = String(dateFromG).split(' ')[0];
                } else if (/^\d{4}-\d{2}-\d{2}$/.test(dateFromJ)) {
                    dateFrom = dateFromJ;
                }
            }

            if (dateToJ) {
                const dateToG = persianToGregorian(this.toEnglishDigits(dateToJ), '23:59');
                if (dateToG) {
                    dateTo = String(dateToG).split(' ')[0];
                } else if (/^\d{4}-\d{2}-\d{2}$/.test(dateToJ)) {
                    dateTo = dateToJ;
                }
            }

            ownerIds = this.getCustomMultiSelectValues('kpi-owner-filter');
            productIds = this.getCustomMultiSelectValues('kpi-product-filter');
            dealStatuses = this.getCustomMultiSelectValues('kpi-deal-status-filter');
            dealLeadTypes = this.getCustomMultiSelectValues('kpi-deal-lead-type-filter');
            criteria = this.collectKpiCriteriaFromDom(false);
        }

        const ownerId = ownerIds.length === 1 ? ownerIds[0] : '';
        const productId = productIds.length === 1 ? productIds[0] : '';

        this.state.kpiFilters = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_id: ownerId,
            product_id: productId,
            owner_didar_ids: ownerIds,
            product_ids: productIds,
            deal_statuses: dealStatuses,
            deal_lead_types: dealLeadTypes,
            criteria
        };

        const payload = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_id: ownerId,
            product_id: productId,
            owner_didar_ids: ownerIds.join(','),
            product_ids: productIds.join(','),
            deal_statuses: dealStatuses.join(','),
            deal_lead_types: dealLeadTypes.join(',')
        };
        const commissionPayload = {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_id: ownerId
        };
        const commissionRes = await this.req('get_commission_kpis', commissionPayload);
        this.state.commissionKpis = commissionRes.status === 'success' ? (commissionRes || {}) : null;
        this.appendCriteriaToKpiPayload(payload, 'kpi');
        const kpiLoadRequestId = (Number(this.state.kpiLoadRequestId || 0) + 1);
        this.state.kpiLoadRequestId = kpiLoadRequestId;
        this.state.kpiSplitPayload = { ...payload };
        this.state.kpiTabsLoaded = false;
        let usedSplit = false;
        try {
            const sectionReq = async (section) => this.req('get_kpi_section', { ...payload, section });
            const summaryRes = await sectionReq('summary');
            if (summaryRes.status !== 'success') throw new Error(summaryRes.message || 'summary failed');
            if (this.state.kpiLoadRequestId !== kpiLoadRequestId) return;
            usedSplit = true;
            if (summaryRes.filters && summaryRes.filters.criteria) {
                this.state.kpiFilters = {
                    ...(this.state.kpiFilters || {}),
                    criteria: this.normalizeKpiCriteria(summaryRes.filters.criteria)
                };
            }
            this.state.kpiLastData = this.mergeKpiSection({}, summaryRes.data || {}, 'summary');
            this.renderKPIDashboard(this.state.kpiLastData);

            const chartsRes = await sectionReq('charts');
            if (chartsRes.status === 'success') {
                if (this.state.kpiLoadRequestId !== kpiLoadRequestId) return;
                this.state.kpiLastData = this.mergeKpiSection(this.state.kpiLastData || {}, chartsRes.data || {}, 'charts');
                this.renderKPIDashboard(this.state.kpiLastData);
            }

            setTimeout(() => {
                this.loadKpiDashboardTabsSection({ requestId: kpiLoadRequestId });
            }, 0);
        } catch (e) {
            console.warn('KPI split load failed, fallback to full endpoint', e);
        }

        if (!usedSplit) {
            const res = await this.req('get_kpi', payload);
            if (res.status === 'success') {
                if (res.filters && res.filters.criteria) {
                    this.state.kpiFilters = {
                        ...(this.state.kpiFilters || {}),
                        criteria: this.normalizeKpiCriteria(res.filters.criteria)
                    };
                }
                this.state.kpiTabsLoaded = true;
                this.renderKPIDashboard(res.kpi);
            } else {
                Swal.fire('خطا', res.message || 'خطا در بارگذاری KPI', 'error');
            }
        }
    },

    async loadKpiDashboardTabsSection(options = {}) {
        const requestId = Number(options.requestId || this.state.kpiLoadRequestId || 0);
        if (!this.state.kpiSplitPayload) return;
        if (this.state.kpiTabsLoaded) return;
        if (this.state.kpiTabsLoading) return;
        this.state.kpiTabsLoading = true;
        try {
            const res = await this.req('get_kpi_section', { ...this.state.kpiSplitPayload, section: 'tabs' });
            if (res.status === 'success' && this.state.kpiLoadRequestId === requestId) {
                this.state.kpiLastData = this.mergeKpiSection(this.state.kpiLastData || {}, res.data || {}, 'tabs');
                this.state.kpiTabsLoaded = true;
                this.renderKPIDashboard(this.state.kpiLastData || {});
            }
        } catch (e) {
            console.warn('KPI tabs lazy load failed', e);
        } finally {
            this.state.kpiTabsLoading = false;
        }
    },

    mergeKpiSection(current, sectionData, section) {
        const out = current ? JSON.parse(JSON.stringify(current)) : {};
        Object.assign(out, sectionData || {});
        if (section === 'summary') {
            out.daily_sales = out.daily_sales || [];
            out.lost_sales = out.lost_sales || [];
            out.product_sales = out.product_sales || [];
            out.daily_operational = out.daily_operational || {};
            out.advanced_tables = out.advanced_tables || {};
        }
        return out;
    },

    renderKPIDashboard(kpi) {
        const content = document.getElementById('kpi-dashboard-content');
        if (!content) return;
        this.state.kpiLastData = kpi || {};

        const filters = this.state.kpiFilters || {};
        const criteria = this.normalizeKpiCriteria(filters.criteria || {});
        const sales = kpi.sales || {};
        const settled = kpi.settled || {};
        const outstanding = kpi.outstanding || {};
        const lost = kpi.lost_sales || [];
        const openPipeline = kpi.open_pipeline || {};
        const wonSales = kpi.won_sales || {};
        const dealsWithoutPayments = kpi.deals_without_payments || {};
        const productSales = kpi.product_sales || [];
        const dealConversion = kpi.deal_conversion || {};
        const leadConversion = kpi.lead_conversion || {};
        const timeToConversion = kpi.time_to_conversion || {};
        const target = kpi.target_achievement || {};
        const dailyOperational = kpi.daily_operational || {};
        const advancedTables = kpi.advanced_tables || {};
        const commissionKpis = this.state.commissionKpis || {};
        const repKpis = commissionKpis.rep || {};
        const managerKpis = commissionKpis.manager || {};
        const canSeeTeamCommissionMetrics = this.isAdminOrCrmSpecialist();

        this.state.kpiAdvancedTables = advancedTables;
        this.ensureKpiAdvancedUiState();

        // Calculate percentages for progress bar
        const achievementPercent = target.achievement_percent || 0;
        const progressColor = achievementPercent >= 100 ? 'bg-green-500' :
                             achievementPercent >= 75 ? 'bg-blue-500' :
                             achievementPercent >= 50 ? 'bg-yellow-500' : 'bg-red-500';

        // Format Toman amount (already in Toman, no conversion needed)
        const formatToman = (amount) => {
            const toman = parseFloat(amount) || 0;
            return this.formatNumber(toman);
        };
        const lostReasonSummaryHtml = (Array.isArray(lost) ? lost : [])
            .map(item => ({
                reason: item.failure_reason_text || item.failure_reason_code || 'نامشخص',
                count: Number(item.total_lost || 0)
            }))
            .filter(item => item.count > 0)
            .sort((a, b) => b.count - a.count)
            .slice(0, 3)
            .map(item => `<div class="text-[10px] text-gray-500">${this.escapeHtml(item.reason)}: ${this.formatNumber(item.count)}</div>`)
            .join('');

        const dateFromJalali = filters.date_from ? (toPersianDate(filters.date_from) || filters.date_from) : '';
        const dateToJalali = filters.date_to ? (toPersianDate(filters.date_to) || filters.date_to) : '';
        
        const tableIds = Object.keys(advancedTables || {});
        const navMenu = tableIds.length > 0 ? `
            <div class="flex overflow-x-auto gap-2 mt-4 pb-2 no-scrollbar scroll-smooth border-t border-gray-700 pt-4">
                ${tableIds.map(tableId => {
                    const def = this.getKpiAdvancedTableDefinition(tableId);
                    const title = (def?.title || '').replace(/^\d+\)\s*/, '');
                    const isVisible = this.state.kpiAdvancedVisibleTables?.[tableId] !== false;
                    return `
                        <div class="whitespace-nowrap bg-gray-800 border border-gray-700 text-gray-300 px-2 py-1.5 rounded-full text-[11px] transition-all flex items-center gap-2 shadow-sm">
                            <input type="checkbox" ${isVisible ? 'checked' : ''} onchange="app.toggleKpiAdvancedTableVisibility('${tableId}', this.checked)">
                            <button onclick="app.scrollToKpiTable('kpi-adv-panel-${tableId}')" 
                                    class="text-gray-300 hover:text-blue-200 transition-colors"
                                    title="رفتن به جدول ${title}">
                                ${title}
                            </button>
                        </div>
                    `;
                }).join('')}
            </div>
        ` : '';

        const activeFilterChips = this.renderKpiActiveFilterChips(filters);

        content.innerHTML = `
            <!-- Header Filters -->
            <div class="glass p-4 md:p-6 mb-6 border border-gray-700/70 shadow-xl bg-gray-900/95 backdrop-blur-md">
                <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-4">
                    <div class="text-sm text-gray-300 font-bold">فیلترهای داشبورد KPI</div>
                    <div class="flex flex-wrap items-center gap-2">
                        <button onclick="app.setKpiQuickRange('today')" class="text-xs px-3 py-1.5 rounded bg-gray-700 hover:bg-gray-600 text-white">امروز</button>
                        <button onclick="app.setKpiQuickRange('week')" class="text-xs px-3 py-1.5 rounded bg-gray-700 hover:bg-gray-600 text-white">۷ روز اخیر</button>
                        <button onclick="app.setKpiQuickRange('month')" class="text-xs px-3 py-1.5 rounded bg-gray-700 hover:bg-gray-600 text-white">ماه جاری</button>
                        <button onclick="app.clearKpiFilters()" class="text-xs px-3 py-1.5 rounded bg-red-700 hover:bg-red-600 text-white">پاک‌کردن فیلترها</button>
                    </div>
                </div>

                <div id="kpi-filters-container">
                    <div class="grid grid-cols-1 md:grid-cols-4 gap-3">
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">از تاریخ</label>
                            <input type="text" id="kpi-date-from" class="w-full bg-gray-800 border border-gray-700 rounded p-2 text-white"
                                   value="${dateFromJalali}">
                        </div>
                        <div>
                            <label class="block text-xs text-gray-400 mb-1">تا تاریخ</label>
                            <input type="text" id="kpi-date-to" class="w-full bg-gray-800 border border-gray-700 rounded p-2 text-white"
                                   value="${dateToJalali}">
                        </div>
                        <div>
                            ${this.renderCustomMultiSelect('kpi-owner-filter', 'مسئول (چندانتخابی)', this.getKpiOwnerData(), filters.owner_didar_ids || [])}
                        </div>
                        <div>
                            ${this.renderCustomMultiSelect('kpi-product-filter', 'محصول (چندانتخابی)', this.getKpiProductData(), filters.product_ids || [])}
                        </div>
                    </div>
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
                        <div>
                            ${this.renderCustomMultiSelect('kpi-deal-status-filter', 'وضعیت معامله (چندانتخابی)', this.getKpiDealStatusData(), filters.deal_statuses || [])}
                        </div>
                        <div>
                            ${this.renderCustomMultiSelect('kpi-deal-lead-type-filter', 'نوع معامله (چندانتخابی)', this.getKpiDealLeadTypeData(), filters.deal_lead_types || [])}
                        </div>
                    </div>
                    ${this.isAdminLikeForAnalytics() ? `
                    <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-6 gap-3 mt-4">
                        ${this.renderKpiCriteriaSelect('kpi_sales_date_basis', criteria.kpi_sales_date_basis)}
                        ${this.renderKpiCriteriaSelect('kpi_sales_count_rule', criteria.kpi_sales_count_rule)}
                        ${this.renderKpiCriteriaSelect('kpi_payment_date_basis', criteria.kpi_payment_date_basis)}
                        ${this.renderKpiCriteriaSelect('kpi_payment_count_rule', criteria.kpi_payment_count_rule)}
                        ${this.renderKpiCriteriaSelect('kpi_payment_status_scope', criteria.kpi_payment_status_scope)}
                        ${this.renderKpiCriteriaSelect('legacy_deal_scope', criteria.legacy_deal_scope)}
                    </div>` : ''}
                    
                    <div class="mt-4 flex justify-end">
                        <button onclick="app.loadKPIDashboard()" class="bg-blue-600 hover:bg-blue-500 text-white px-6 py-2 rounded-lg font-bold transition">
                            <i class="fa-solid fa-search ml-2"></i> اعمال فیلتر
                        </button>
                    </div>
                </div>

                ${navMenu}
                <div class="mt-4">${activeFilterChips}</div>
            </div>

            <!-- KPI Cards Row 1 -->
            <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
                <!-- Sales Card -->
                <div class="glass p-6 border-r-4 border-blue-500">
                    <div class="text-sm text-gray-400 mb-2">فروش کل</div>
                    <div class="text-3xl font-bold bg-gradient-to-r from-amber-200 to-yellow-500 bg-clip-text text-transparent mb-2">${formatToman(sales.total_collected_amount || 0)} تومان</div>
                    <div class="text-sm text-gray-400">تعداد معامله: ${sales.deal_count || 0}</div>
                    <div class="text-xs text-gray-500 mt-2">مبلغ قرارداد: ${formatToman(sales.total_contract_amount || 0)} تومان</div>
                </div>

                <!-- Target Achievement Card -->
                <div class="glass p-6 border-r-4 ${progressColor}">
                    <div class="text-sm text-gray-400 mb-2">درصد تحقق تارگت</div>
                    <div class="text-3xl font-bold text-white mb-2">${achievementPercent}%</div>
                    ${target.has_target ? `
                        <div class="w-full bg-gray-700 rounded-full h-2.5 mb-2">
                            <div class="${progressColor} h-2.5 rounded-full" style="width: ${Math.min(achievementPercent, 100)}%"></div>
                        </div>
                        <div class="text-xs text-gray-400">
                            هدف: ${formatToman(target.target_amount)} تومان | واقعی: ${formatToman(target.actual_amount)} تومان
                        </div>
                    ` : `
                        <div class="text-xs text-gray-400">هدف تعریف نشده</div>
                    `}
                </div>

                <!-- Outstanding Card -->
                <div class="glass p-6 border-r-4 border-red-500">
                    <div class="text-sm text-gray-400 mb-2">مانده حساب</div>
                    <div class="text-3xl font-bold text-red-400 mb-2">${formatToman(outstanding.outstanding_amount || 0)} تومان</div>
                    <div class="text-sm text-gray-400">تعداد معامله بدهکار: ${outstanding.deal_count || 0}</div>
                    <div class="text-xs text-gray-500 mt-2">قرارداد: ${formatToman(outstanding.total_contract_amount || 0)} تومان | وصول شده: ${formatToman(outstanding.total_collected || 0)} تومان</div>
                </div>
            </div>

            <!-- KPI Cards Row 2 -->
            <div class="grid grid-cols-1 md:grid-cols-6 gap-4 mb-6">
                <div class="glass p-4 border-r-4 border-green-500">
                    <div class="text-sm text-gray-400">تسویه شده کامل</div>
                    <div class="text-xl font-bold text-white">${settled.deal_count || 0}</div>
                    <div class="text-xs text-gray-500 mt-1">مبلغ کل: ${formatToman(settled.total_amount || 0)} تومان</div>
                    <div class="text-xs text-gray-500">وصول شده: ${formatToman(settled.total_collected_amount || 0)} تومان</div>
                </div>
                <div class="glass p-4 border-r-4 border-yellow-500">
                    <div class="text-sm text-gray-400">فروش ناموفق</div>
                    <div class="text-xl font-bold text-white">${this.sumLostSales(lost)}</div>
                    <div class="text-xs text-gray-500 mt-1">مبلغ کل: ${formatToman(this.sumLostSalesAmount(lost))} تومان</div>
                    ${lostReasonSummaryHtml}
                </div>
                <div class="glass p-4 border-r-4 border-emerald-500">
                    <div class="text-sm text-gray-400">فروش موفق (Won)</div>
                    <div class="text-xl font-bold text-white">${wonSales.deal_count || 0}</div>
                    <div class="text-xs text-gray-500 mt-1">مبلغ کل: ${formatToman(wonSales.total_amount || 0)} تومان</div>
                </div>
                <div class="glass p-4 border-r-4 border-purple-500">
                    <div class="text-sm text-gray-400 flex items-center gap-2">
                        فروش جاری
                        <span class="flex h-2 w-2 relative">
                            <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-400 opacity-75"></span>
                            <span class="relative inline-flex rounded-full h-2 w-2 bg-purple-500"></span>
                        </span>
                    </div>
                    <div class="text-xl font-bold text-white">${openPipeline.deal_count || 0}</div>
                    <div class="text-xs text-gray-500 mt-1">پتانسیل: ${formatToman(openPipeline.total_potential_amount || 0)} تومان</div>
                </div>
                <div class="glass p-4 border-r-4 border-pink-500 cursor-pointer hover:border-cyan-400 transition-colors"
                     onclick="app.openConversionCardDrilldown('kpi','deal_conversion_rate')"
                     title="نمایش جزئیات">
                    <div class="text-sm text-gray-400">نرخ تبدیل معاملات</div>
                    <div class="text-xl font-bold text-white">${dealConversion.conversion_rate || 0}%</div>
                    <div class="text-xs text-gray-500 mt-1">از ${dealConversion.total_deals || 0} معامله</div>
                    <div class="text-xs text-gray-500">عدم تبدیل: ${dealConversion.non_conversion_rate || 0}%</div>
                    <div class="text-[10px] text-cyan-300 mt-2">برای مشاهده جزئیات کلیک کنید</div>
                </div>
                <div class="glass p-4 border-r-4 border-blue-500 cursor-pointer hover:border-cyan-400 transition-colors"
                     onclick="app.openConversionCardDrilldown('kpi','lead_conversion_rate')"
                     title="نمایش جزئیات">
                    <div class="text-sm text-gray-400">نرخ تبدیل لید جدید</div>
                    <div class="text-xl font-bold text-white">${leadConversion.conversion_rate || 0}%</div>
                    <div class="text-xs text-gray-500 mt-1">از ${leadConversion.total_new_leads || 0} لید جدید</div>
                    <div class="text-xs text-gray-500">عدم تبدیل: ${leadConversion.non_conversion_rate || 0}%</div>
                    <div class="text-[10px] text-cyan-300 mt-2">برای مشاهده جزئیات کلیک کنید</div>
                </div>
            </div>
            <div class="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-6 gap-4 mb-6">
                <div class="glass p-4 border-r-4 border-emerald-500">
                    <div class="text-sm text-gray-400">پورسانت</div>
                    <div class="text-xl font-bold text-white">${formatToman(repKpis.tce || 0)} تومان</div>
                    <div class="text-xs text-gray-500 mt-1">قطعی</div>
                </div>
            </div>

            <!-- Deals Without Payments Row -->
            ${dealsWithoutPayments.deal_count > 0 ? `
            <div class="glass p-6 mb-6 border-r-4 border-yellow-500">
                <div class="flex items-center justify-between mb-4">
                    <div>
                        <div class="text-lg font-bold text-yellow-400 mb-2">⚠️ معاملات بدون پرداخت</div>
                        <div class="text-sm text-gray-400">معاملاتی که مبلغ دارند اما هنوز پرداختی ثبت نشده</div>
                    </div>
                    <div class="text-right">
                        <div class="text-3xl font-bold text-yellow-400">${dealsWithoutPayments.deal_count || 0}</div>
                        <div class="text-sm text-gray-400">معامله</div>
                    </div>
                </div>
                <div class="mt-4 pt-4 border-t border-gray-700">
                    <div class="text-sm text-gray-400 mb-1">مجموع مبلغ قابل پرداخت:</div>
                    <div class="text-2xl font-bold text-white">${formatToman(dealsWithoutPayments.total_potential_amount || 0)} تومان</div>
                </div>
            </div>
            ` : ''}

            <!-- Product Sales Counts -->
            <div class="glass p-6 mb-6 border-r-4 border-indigo-500">
                <div class="flex items-center justify-between mb-4">
                    <div>
                        <div class="text-lg font-bold text-indigo-400 mb-1">فروش محصولات (بر اساس پرداخت)</div>
                        <div class="text-sm text-gray-400">تعداد معاملاتی که در بازه انتخاب‌شده حداقل یک پرداخت دارند</div>
                    </div>
                </div>
                ${productSales.length > 0 ? `
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
                        ${productSales.map(item => `
                            <div class="bg-gray-800/50 p-4 rounded flex items-center justify-between">
                                <div class="text-sm text-gray-300">${item.product_name || '-'}</div>
                                <div class="text-xl font-bold text-white">${item.deal_count || 0}</div>
                            </div>
                        `).join('')}
                    </div>
                ` : `
                    <div class="text-sm text-gray-500">داده‌ای برای این بازه وجود ندارد.</div>
                `}
            </div>

            <!-- Charts Row -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
                <!-- Daily Sales Chart -->
                <div class="glass p-6">
                    <h3 class="text-lg font-bold mb-4 text-yellow-500">فروش روزانه</h3>
                    <div id="daily-sales-chart" class="h-64"></div>
                    ${this.renderDailySalesChart(kpi.daily_sales || [])}
                </div>

                <!-- Lost Sales Reasons Chart -->
                <div class="glass p-6">
                    <h3 class="text-lg font-bold mb-4 text-yellow-500">دلایل شکست معاملات</h3>
                    <div id="lost-sales-chart" class="h-64"></div>
                    ${this.renderLostSalesChart(lost)}
                </div>
            </div>

            <!-- Lead Conversion Details -->
            <div class="glass p-6 mb-6">
                <h3 class="text-lg font-bold mb-4 text-yellow-500">جزئیات نرخ تبدیل لید جدید</h3>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <div class="bg-gray-800/50 p-4 rounded">
                        <div class="text-sm text-gray-400">لیدهای جدید</div>
                        <div class="text-2xl font-bold text-white">${leadConversion.total_new_leads || 0}</div>
                    </div>
                    <div class="bg-gray-800/50 p-4 rounded">
                        <div class="text-sm text-gray-400">تبدیل شده</div>
                        <div class="text-2xl font-bold text-green-400">${leadConversion.converted_leads || 0}</div>
                    </div>
                    <div class="bg-gray-800/50 p-4 rounded">
                        <div class="text-sm text-gray-400">نرخ عدم تبدیل</div>
                        <div class="text-2xl font-bold text-red-400">${leadConversion.non_conversion_rate || 0}%</div>
                    </div>
                </div>
            </div>

            <!-- Time to Conversion -->
            <div class="glass p-6">
                <h3 class="text-lg font-bold mb-4 text-yellow-500">میانگین زمان تبدیل</h3>
                <div class="text-3xl font-bold text-white mb-2">
                    ${Math.round(timeToConversion.avg_conversion_days || 0)} روز
                </div>
                <div class="text-sm text-gray-400">
                    مبنای محاسبه: از ثبت معامله تا اولین پرداخت ثبت شده
                </div>
            </div>

            <!-- Daily Operational KPI -->
            <div class="glass p-6 mt-6">
                <h3 class="text-lg font-bold mb-4 text-yellow-500">KPI روزانه عملیاتی (کلی و تفکیک کارشناس)</h3>
                ${this.renderDailyOperationalKpi(dailyOperational)}
            </div>

            <!-- Advanced KPI Tables -->
            <div class="glass p-6 mt-6">
                <h3 class="text-lg font-bold mb-4 text-yellow-500">جدول‌های تحلیلی KPI</h3>
                ${this.renderKpiAdvancedTablesSection()}
            </div>

            <!-- Deal Details Table -->
            <div class="glass p-6 mt-6">
                <div class="flex justify-between items-center mb-4">
                    <h3 class="text-lg font-bold text-yellow-500">جزئیات معاملات</h3>
                    <button onclick="app.toggleKpiDealDetails()" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded transition">
                        <i class="fa-solid fa-eye ml-2"></i> ${this.state.showKpiDealDetails ? 'مخفی کن' : 'نمایش'}
                    </button>
                </div>
                <div id="kpi-deal-details-table" class="${this.state.showKpiDealDetails ? '' : 'hidden'}">
                    <div class="overflow-x-auto">
                        <table class="w-full text-xs border-collapse">
                            <thead class="sticky top-0 bg-gray-700">
                                <tr>
                                    <th class="border border-gray-600 p-2 text-right">عنوان معامله</th>
                                    <th class="border border-gray-600 p-2 text-right">مسئول</th>
                                    <th class="border border-gray-600 p-2 text-right">وضعیت</th>
                                    <th class="border border-gray-600 p-2 text-right">مبلغ قرارداد</th>
                                    <th class="border border-gray-600 p-2 text-right">پرداخت شده</th>
                                    <th class="border border-gray-600 p-2 text-right">مانده</th>
                                    <th class="border border-gray-600 p-2 text-right">اولین پرداخت</th>
                                </tr>
                            </thead>
                            <tbody id="kpi-deals-table-body" class="bg-gray-900">
                                <tr class="text-center text-gray-500">
                                    <td colspan="8" class="py-8">در حال بارگذاری...</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>

            <!-- Back to Top Button -->
            <button onclick="app.scrollToKpiTop()" class="fixed bottom-6 left-6 bg-gray-700 hover:bg-gray-600 text-white p-3 rounded-full shadow-xl z-50 transition-opacity duration-300 opacity-80 hover:opacity-100 border border-gray-600">
                <i class="fa-solid fa-arrow-up"></i>
            </button>
        `;

        this.initCustomMultiSelectListeners();
        this.initKpiDatePickers();
    },

    initKpiNativeMultiSelects() {
        // Legacy native select init - keeping for reference or fallback if needed
        const ids = [
            'kpi-owner-filter',
            'kpi-product-filter',
            'kpi-deal-status-filter',
            'kpi-deal-lead-type-filter'
        ];
        ids.forEach((id) => {
            const el = document.getElementById(id);
            if (!el || el.dataset.kpiNativeBound === '1') return;
            // Native select logic removed in favor of custom component
        });
    },

    // ========== Custom Multi-Select Component ==========

    getKpiOwnerData() {
        const options = [{ value: '__all__', label: 'همه مسئول‌ها' }];
        if (this.state.kpiUsers && this.state.kpiUsers.length > 0) {
            // Ensure we are comparing as numbers and handle potential string values
            const activeUsers = this.state.kpiUsers.filter(user => {
                const isActive = user.is_active !== undefined ? Number(user.is_active) : 1;
                return isActive === 1;
            });
            
            activeUsers.forEach(user => {
                const name = user.display_name || user.email || (user.first_name + ' ' + (user.last_name || ''));
                const didarId = user.didar_user_id || user.owner_id || '';
                if (didarId) {
                    options.push({ value: String(didarId), label: name.trim() });
                }
            });
        }
        return options;
    },

    getKpiProductData() {
        const options = [{ value: '__all__', label: 'همه محصولات' }];
        if (this.state.kpiProducts && this.state.kpiProducts.length > 0) {
            this.state.kpiProducts.forEach(product => {
                if (product.id) {
                    options.push({ value: String(product.id), label: product.name || 'محصول' });
                }
            });
        }
        return options;
    },

    getKpiDealStatusData() {
        return [
            { value: 'pending', label: 'جاری' },
            { value: 'won', label: 'موفق' },
            { value: 'lost', label: 'ناموفق' }
        ];
    },

    getKpiDealLeadTypeData() {
        return [
            { value: 'new', label: 'معامله لید جدید' },
            { value: 'old', label: 'معامله لید قدیمی' }
        ];
    },

    renderCustomMultiSelect(id, label, options, selectedValues, compact = false) {
        // Ensure selectedValues are strings
        const selectedSet = new Set((selectedValues || []).map(String));
        
        // Handle logic for "All" selection
        const isAllSelected = selectedSet.has('__all__') || selectedSet.size === 0;
        
        // Determine display text
        let buttonText = 'انتخاب کنید...';
        if (isAllSelected && options.some(o => o.value === '__all__')) {
            const allLabel = options.find(o => o.value === '__all__')?.label || 'همه';
            buttonText = allLabel;
        } else if (selectedSet.size > 0) {
            // Find labels for selected values (excluding __all__)
            const selectedLabels = options
                .filter(o => o.value !== '__all__' && selectedSet.has(String(o.value)))
                .map(o => o.label);
            
            if (selectedLabels.length === 1) {
                buttonText = selectedLabels[0];
            } else if (selectedLabels.length > 1) {
                buttonText = `${selectedLabels[0]} (+${selectedLabels.length - 1} مورد دیگر)`;
            }
        }

        const optionsHtml = options.map(opt => {
            const isSelected = isAllSelected && opt.value === '__all__' ? true : selectedSet.has(String(opt.value));
            const selectedClass = isSelected ? ' is-selected' : '';
            return `
                <label class="flex items-center px-3 py-2.5 hover:bg-gray-800 cursor-pointer border-b border-gray-700/50 last:border-0 transition-colors select-item group${selectedClass}" data-label="${opt.label.toLowerCase()}">
                    <div class="relative flex items-center">
                        <input type="checkbox" value="${opt.value}" 
                            class="peer appearance-none w-4 h-4 border border-gray-500 rounded bg-gray-700 checked:bg-blue-600 checked:border-blue-600 focus:ring-2 focus:ring-blue-500/40 transition-all"
                            ${isSelected ? 'checked' : ''}
                            onchange="app.handleCustomMultiSelectChange('${id}', '${opt.value}', this)">
                        <i class="fa-solid fa-check text-white text-[10px] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 peer-checked:opacity-100 transition-opacity pointer-events-none"></i>
                    </div>
                    <span class="text-sm text-gray-300 group-hover:text-white mr-3 select-none truncate transition-colors">${opt.label}</span>
                </label>
            `;
        }).join('');

        const wrapperClass = compact
            ? 'relative custom-multiselect-container custom-multiselect-compact'
            : 'relative custom-multiselect-container';
        const labelClass = compact
            ? 'block text-[10px] text-gray-400 mb-1 font-medium px-1'
            : 'block text-xs text-gray-400 mb-1.5 font-medium px-1';
        const buttonClass = compact
            ? 'relative w-full h-8 bg-gray-950/80 border border-gray-700/80 rounded-md py-1 px-2 text-white text-right flex justify-between items-center focus:outline-none focus:border-blue-500 hover:border-gray-600 transition-all shadow-sm group'
            : 'relative w-full bg-gray-800 border border-gray-700 rounded-lg py-2.5 px-3 text-white text-right flex justify-between items-center focus:outline-none focus:border-blue-500 hover:border-gray-600 transition-all shadow-sm group';
        const textClass = compact
            ? 'truncate text-xs font-medium text-gray-200 group-hover:text-white transition-colors'
            : 'truncate text-sm font-medium text-gray-200 group-hover:text-white transition-colors';
        const dropdownClass = compact
            ? 'hidden absolute top-[calc(100%+5px)] left-0 right-0 min-w-[260px] bg-gray-950 border border-gray-600 rounded-md shadow-2xl ring-1 ring-cyan-300/10 flex flex-col overflow-hidden animate-fade-in-down'
            : 'hidden absolute top-[calc(100%+6px)] left-0 right-0 min-w-[240px] bg-gray-900 border border-gray-600 rounded-lg shadow-2xl ring-1 ring-white/10 flex flex-col overflow-hidden animate-fade-in-down';

        return `
            <div class="${wrapperClass}" id="wrapper-${id}" style="z-index: 7000; overflow: visible;">
                <label class="${labelClass}">${label}</label>
                <button type="button" 
                    onclick="app.toggleCustomMultiSelect('${id}')"
                    class="${buttonClass}">
                    <span id="text-${id}" class="${textClass}">${buttonText}</span>
                    <i id="icon-${id}" class="fa-solid fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
                </button>
                
                <div id="dropdown-${id}" class="${dropdownClass}" style="z-index: 60000;">
                    <div class="custom-multiselect-searchbar p-2 border-b border-gray-700 bg-gray-900/95 backdrop-blur sticky top-0 z-10">
                        <div class="relative">
                            <i class="fa-solid fa-search absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 text-xs pointer-events-none"></i>
                            <input type="text" 
                                placeholder="جستجو..." 
                                class="custom-multiselect-search w-full bg-gray-800 border border-gray-700 rounded-md py-1.5 pr-9 pl-2 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-blue-500 focus:bg-gray-700 transition-all"
                                autocomplete="off"
                                onkeyup="app.filterCustomMultiSelect('${id}', this.value)">
                        </div>
                    </div>
                    <div class="custom-multiselect-options max-h-64 overflow-y-auto custom-scrollbar p-1 space-y-0.5">
                        ${optionsHtml}
                    </div>
                </div>
            </div>
        `;
    },

    filterCustomMultiSelect(id, query) {
        const wrapper = document.getElementById(`wrapper-${id}`);
        if (!wrapper) return;
        
        const items = wrapper.querySelectorAll('.select-item');
        const q = (query || '').toLowerCase().trim();
        
        items.forEach(item => {
            const label = item.getAttribute('data-label') || '';
            if (label.includes(q)) {
                item.classList.remove('hidden');
            } else {
                item.classList.add('hidden');
            }
        });
    },

    toggleCustomMultiSelect(id) {
        const dropdown = document.getElementById(`dropdown-${id}`);
        const icon = document.getElementById(`icon-${id}`);
        const wrapper = document.getElementById(`wrapper-${id}`);
        if (!dropdown) return;
        
        const isHidden = dropdown.classList.contains('hidden');
        
        // Close others
        document.querySelectorAll('[id^="dropdown-"]').forEach(el => {
            if (el.id !== `dropdown-${id}`) el.classList.add('hidden');
        });
        document.querySelectorAll('[id^="icon-"]').forEach(el => {
            if (el.id !== `icon-${id}`) el.classList.remove('rotate-180');
        });
        document.querySelectorAll('.custom-multiselect-container').forEach(el => {
            if (el.id !== `wrapper-${id}`) el.style.zIndex = '7000';
        });
        
        if (isHidden) {
            if (wrapper) wrapper.style.zIndex = '30000';
            dropdown.classList.remove('hidden');
            if (icon) icon.classList.add('rotate-180');
            
            // Focus search input
            const searchInput = dropdown.querySelector('input[type="text"]');
            if (searchInput) {
                setTimeout(() => searchInput.focus(), 50);
            }
        } else {
            if (wrapper) wrapper.style.zIndex = '7000';
            dropdown.classList.add('hidden');
            if (icon) icon.classList.remove('rotate-180');
        }
    },

    handleCustomMultiSelectChange(id, value, checkbox) {
        const wrapper = document.getElementById(`wrapper-${id}`);
        const checkboxes = wrapper.querySelectorAll('input[type="checkbox"]');
        
        if (value === '__all__') {
            if (checkbox.checked) {
                // Deselect all others
                checkboxes.forEach(cb => {
                    if (cb.value !== '__all__') cb.checked = false;
                });
            } else {
                // Cannot deselect 'all' if it's the only one, or maybe just let it be empty
            }
        } else {
            // If another option is selected, deselect 'all'
            const allCheckbox = wrapper.querySelector('input[value="__all__"]');
            if (allCheckbox && checkbox.checked) {
                allCheckbox.checked = false;
            }
        }

        // Calculate state
        const checked = Array.from(checkboxes).filter(cb => cb.checked);
        const textSpan = document.getElementById(`text-${id}`);
        
        const allChecked = checked.find(cb => cb.value === '__all__');
        
        if (allChecked) {
            // Find label for __all__
             const label = checkbox.value === '__all__' 
                ? checkbox.closest('label').querySelector('span').textContent 
                : wrapper.querySelector('input[value="__all__"]').closest('label').querySelector('span').textContent;
            textSpan.textContent = label;
        } else if (checked.length === 0) {
            textSpan.textContent = 'انتخاب کنید...';
        } else if (checked.length === 1) {
            textSpan.textContent = checked[0].closest('label').querySelector('span').textContent;
        } else {
            const firstLabel = checked[0].closest('label').querySelector('span').textContent;
            textSpan.textContent = `${firstLabel} (+${checked.length - 1} مورد دیگر)`;
        }
    },
    
    getCustomMultiSelectValues(id) {
        const wrapper = document.getElementById(`wrapper-${id}`);
        if (!wrapper) return [];
        const checkboxes = wrapper.querySelectorAll('input[type="checkbox"]:checked');
        const values = Array.from(checkboxes).map(cb => cb.value);
        
        if (values.includes('__all__')) return []; // Return empty array implies "all" in backend logic usually
        return values;
    },

    initCustomMultiSelectListeners() {
        if (this._customMultiSelectListenersInitialized) return;
        
        document.addEventListener('click', (e) => {
            if (!e.target.closest('.custom-multiselect-container')) {
                document.querySelectorAll('[id^="dropdown-"]').forEach(el => {
                    el.classList.add('hidden');
                });
                document.querySelectorAll('.custom-multiselect-container').forEach(el => {
                    el.style.zIndex = '7000';
                });
                document.querySelectorAll('[id^="icon-"]').forEach(el => {
                    el.classList.remove('rotate-180');
                });
            }
        });
        
        this._customMultiSelectListenersInitialized = true;
    },

    closeAllCustomMultiSelectDropdowns() {
        document.querySelectorAll('[id^="dropdown-"]').forEach(el => {
            el.classList.add('hidden');
        });
        document.querySelectorAll('[id^="icon-"]').forEach(el => {
            el.classList.remove('rotate-180');
        });
        if (document.activeElement && typeof document.activeElement.blur === 'function') {
            document.activeElement.blur();
        }
    },

    isDidarUiEnabled() {
        return APP_DIDAR_UI_ENABLED;
    },

    showDidarUiDisabledMessage() {
        Swal.fire('غیرفعال', 'ارتباط با CRM خارجی در این نسخه غیرفعال شده است.', 'info');
    },

    applyDidarUiDisabledState() {
        if (this.isDidarUiEnabled()) {
            return;
        }
        document.querySelectorAll('[onclick*="openDidarSearchModal"], [onclick*="syncDidarUsers"]').forEach((element) => {
            element.classList.add('hidden');
            element.setAttribute('aria-hidden', 'true');
        });
        ['modalDidarSearch', 'modalDidarPerson'].forEach((id) => {
            const modal = document.getElementById(id);
            if (modal) {
                modal.classList.add('hidden');
            }
        });
    },

    normalizeKpiMultiSelect(elementId) {
        const element = document.getElementById(elementId);
        if (!element) return;
        const options = Array.from(element.options || []);
        const allOption = options.find(opt => String(opt.value) === '__all__');
        if (!allOption) return;
        const selectedOthers = options.filter(opt => String(opt.value) !== '__all__' && opt.selected);
        if (selectedOthers.length > 0) {
            allOption.selected = false;
        } else {
            allOption.selected = true;
        }
    },

    toggleKpiDealDetails() {
        this.state.showKpiDealDetails = !this.state.showKpiDealDetails;
        if (this.state.showKpiDealDetails) {
            this.loadKpiDealDetails();
        }
    },

    async loadKpiDealDetails() {
        const tbody = document.getElementById('kpi-deals-table-body');
        if (!tbody) return;

        tbody.innerHTML = '<tr class="text-center"><td colspan="8" class="py-8">در حال بارگذاری...</td></tr>';

        const filters = this.state.kpiFilters || {};
        const dateFrom = filters.date_from || this.getFirstDayOfMonth();
        const dateTo = filters.date_to || this.getLastDayOfMonth();
        const ownerId = filters.owner_didar_id || '';
        const productId = filters.product_id || '';
        const ownerIds = Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids : (ownerId ? [ownerId] : []);
        const productIds = Array.isArray(filters.product_ids) ? filters.product_ids : (productId ? [productId] : []);
        const dealStatuses = Array.isArray(filters.deal_statuses) ? filters.deal_statuses : [];
        const dealLeadTypes = Array.isArray(filters.deal_lead_types) ? filters.deal_lead_types : [];

        const res = await this.req('get_kpi_deals', {
            date_from: dateFrom,
            date_to: dateTo,
            owner_didar_id: ownerId,
            product_id: productId,
            owner_didar_ids: ownerIds.join(','),
            product_ids: productIds.join(','),
            deal_statuses: dealStatuses.join(','),
            deal_lead_types: dealLeadTypes.join(',')
        });

        if (res.status === 'success') {
            this.renderKpiDealDetailsTable(res.deals || []);
        } else {
            tbody.innerHTML = '<tr class="text-center text-red-400"><td colspan="8">خطا در بارگذاری: ' + (res.message || 'نامشخص') + '</td></tr>';
        }
    },

    renderKpiDealDetailsTable(deals) {
        const tbody = document.getElementById('kpi-deals-table-body');
        if (!tbody) return;

        if (!deals || deals.length === 0) {
            tbody.innerHTML = '<tr class="text-center text-gray-500"><td colspan="8">معامله‌ای در این بازه وجود ندارد</td></tr>';
            return;
        }

        const formatToman = (amount) => {
            const toman = parseFloat(amount) || 0;
            return this.formatNumber(toman);
        };

        tbody.innerHTML = deals.map((deal, index) => {
            const outstanding = (deal.payable_amount || 0) - (deal.collected_amount || 0);
            const firstPaymentDate = deal.first_payment_date ? this.formatKpiDate(deal.first_payment_date.slice(0, 10)) : '-';

            return `
                <tr class="${index % 2 === 0 ? 'bg-gray-800' : 'bg-gray-900'}">
                    <td class="border border-gray-700 p-2 text-right">${deal.title || '-'}</td>
                    <td class="border border-gray-700 p-2 text-right">${deal.owner_name || '-'}</td>
                    <td class="border border-gray-700 p-2 text-right">
                        <span class="px-2 py-1 rounded text-xs ${
                            deal.status === 'Won' ? 'bg-green-600' : 
                            deal.status === 'Lost' ? 'bg-red-600' : 'bg-blue-600'
                        }">
                            ${deal.status === 'Won' ? 'موفق' : deal.status === 'Lost' ? 'ناموفق' : 'جاری'}
                        </span>
                    </td>
                    <td class="border border-gray-700 p-2 text-right">${formatToman(deal.payable_amount)}</td>
                    <td class="border border-gray-700 p-2 text-right text-green-400">${formatToman(deal.collected_amount || 0)}</td>
                    <td class="border border-gray-700 p-2 text-right ${outstanding > 0 ? 'text-red-400' : ''}">${formatToman(outstanding)}</td>
                    <td class="border border-gray-700 p-2 text-right">${firstPaymentDate}</td>
                </tr>
            `;
        }).join('');
    },

    renderDailySalesChart(dailySales) {
        if (!dailySales || dailySales.length === 0) {
            return '<div class="text-center text-gray-500 py-8">داده‌ای برای نمایش وجود ندارد</div>';
        }

        const amounts = dailySales.map(d => (parseFloat(d.collected_amount) || 0));
        const maxAmount = Math.max(...amounts, 0);
        const summary = dailySales.map(d => {
            const amount = (parseFloat(d.collected_amount) || 0);
            return `${d.date}: ${this.formatNumber(amount)} تومان`;
        }).join('<br>');

        return `
            <div class="flex items-end space-x-1 h-48 overflow-x-auto pb-2">
                ${dailySales.map(d => {
                    const amount = (parseFloat(d.collected_amount) || 0);
                    const height = maxAmount > 0 ? (amount / maxAmount * 100) : 0;
                    const displayAmount = this.formatNumber(amount);

                    return `
                        <div class="flex-shrink-0 flex flex-col items-center">
                            <div class="w-8 bg-blue-500 rounded-t hover:bg-blue-400 transition cursor-pointer"
                                 style="height: ${Math.max(height, 6)}%"
                                 title="${d.date}: ${displayAmount} تومان">
                            </div>
                            <div class="text-xs text-gray-500 mt-1 transform -rotate-45 origin-left">${d.date.slice(5)}</div>
                        </div>
                    `;
                }).join('')}
            </div>
            <div class="text-xs text-gray-400 mt-3 leading-6">${summary}</div>
        `;
    },

    renderLostSalesChart(lostSales) {
        if (!lostSales || lostSales.length === 0) {
            return '<div class="text-center text-gray-500 py-8">فروش ناموفقی در این بازه وجود ندارد</div>';
        }

        const total = lostSales.reduce((sum, item) => sum + Number(item.total_lost || 0), 0);

        return `
            <div class="space-y-3">
                ${lostSales.map(item => {
                    const count = item.total_lost || 0;
                    const percent = total > 0 ? (count / total * 100) : 0;
                    const reason = item.failure_reason_text || item.failure_reason_code || 'نامشخص';

                    return `
                        <div>
                            <div class="flex justify-between text-sm mb-1">
                                <span class="text-gray-300">${reason}</span>
                                <span class="text-white">${count} (${percent.toFixed(1)}%)</span>
                            </div>
                            <div class="w-full bg-gray-700 rounded-full h-2">
                                <div class="bg-red-500 h-2 rounded-full" style="width: ${percent}%"></div>
                            </div>
                        </div>
                    `;
                }).join('')}
            </div>
        `;
    },

    renderDailyOperationalKpi(dailyOperational) {
        const tables = {
            overall_daily: Array.isArray(dailyOperational.overall_daily) ? dailyOperational.overall_daily : [],
            by_expert_daily: Array.isArray(dailyOperational.by_expert_daily) ? dailyOperational.by_expert_daily : [],
            activity_type_by_expert_daily: Array.isArray(dailyOperational.activity_type_by_expert_daily) ? dailyOperational.activity_type_by_expert_daily : [],
            new_leads_activity_by_expert_daily: Array.isArray(dailyOperational.new_leads_activity_by_expert_daily) ? dailyOperational.new_leads_activity_by_expert_daily : []
        };

        if (Object.values(tables).every(rows => rows.length === 0)) {
            return '<div class="text-center text-gray-500 py-6">داده‌ای برای KPI عملیاتی روزانه در این بازه وجود ندارد.</div>';
        }

        this.state.kpiDailyOperationalTables = tables;
        this.ensureKpiDailyOperationalUiState();
        return this.renderKpiDailyOperationalSection();
    },

    ensureKpiDailyOperationalUiState() {
        if (!this.state.kpiDailyOperationalUi) {
            this.state.kpiDailyOperationalUi = {};
        }
        const defs = this.getKpiDailyOperationalDefinitions();
        Object.keys(defs).forEach((tableId) => {
            const def = defs[tableId];
            const existing = this.state.kpiDailyOperationalUi[tableId] || {};
            const visibleColumns = existing.visibleColumns || {};
            def.columns.forEach((col) => {
                if (visibleColumns[col.key] === undefined) {
                    visibleColumns[col.key] = true;
                }
            });
            this.state.kpiDailyOperationalUi[tableId] = {
                search: existing.search || '',
                sortKey: existing.sortKey || def.defaultSortKey || 'date',
                sortDir: existing.sortDir || 'asc',
                hideZeroRows: !!existing.hideZeroRows,
                visibleColumns
            };
        });
    },

    getKpiDailyOperationalDefinitions() {
        return {
            overall_daily: {
                title: 'نمای کلی روزانه',
                rowTotalUnit: 'count',
                defaultSortKey: 'date',
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'new_leads_count', label: 'لید جدید', type: 'count', summable: true },
                    { key: 'new_deals_for_existing_leads_count', label: 'معامله جدید برای لید قبلی', type: 'count', summable: true },
                    { key: 'activities_count', label: 'کل فعالیت‌ها', type: 'count', summable: true },
                    { key: 'new_leads_with_activity_count', label: 'لید جدید با فعالیت', type: 'count', summable: true },
                    { key: 'leads_created_with_payment_count', label: 'لید ثبت‌شده با پرداخت', type: 'count', summable: true },
                    { key: 'deals_created_with_payment_count', label: 'معامله ثبت‌شده با پرداخت', type: 'count', summable: true },
                    { key: 'leads_without_activity_count', label: 'لید بدون فعالیت', type: 'count', summable: true },
                    { key: 'deals_without_activity_count', label: 'معامله بدون فعالیت', type: 'count', summable: true }
                ]
            },
            by_expert_daily: {
                title: 'تفکیک روزانه بر اساس کارشناس',
                rowTotalUnit: 'count',
                defaultSortKey: 'date',
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'new_leads_count', label: 'لید جدید', type: 'count', summable: true },
                    { key: 'new_deals_for_existing_leads_count', label: 'معامله جدید برای لید قبلی', type: 'count', summable: true },
                    { key: 'activities_count', label: 'کل فعالیت‌ها', type: 'count', summable: true },
                    { key: 'new_leads_with_activity_count', label: 'لید جدید با فعالیت', type: 'count', summable: true },
                    { key: 'leads_created_with_payment_count', label: 'لید ثبت‌شده با پرداخت', type: 'count', summable: true },
                    { key: 'deals_created_with_payment_count', label: 'معامله ثبت‌شده با پرداخت', type: 'count', summable: true },
                    { key: 'leads_without_activity_count', label: 'لید بدون فعالیت', type: 'count', summable: true },
                    { key: 'deals_without_activity_count', label: 'معامله بدون فعالیت', type: 'count', summable: true }
                ]
            },
            activity_type_by_expert_daily: {
                title: 'نوع فعالیت روزانه به تفکیک کارشناس',
                rowTotalUnit: 'count',
                defaultSortKey: 'report_date',
                columns: [
                    { key: 'report_date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'activity_type', label: 'نوع فعالیت', type: 'text' },
                    { key: 'total_count', label: 'تعداد', type: 'count', summable: true }
                ]
            },
            new_leads_activity_by_expert_daily: {
                title: 'لیدهای جدید با فعالیت در همان روز (نوع فعالیت + کارشناس)',
                rowTotalUnit: 'count',
                defaultSortKey: 'report_date',
                columns: [
                    { key: 'report_date', label: 'تاریخ', type: 'date' },
                    { key: 'lead_owner_name', label: 'مسئول لید', type: 'text' },
                    { key: 'activity_owner_name', label: 'کارشناس فعالیت', type: 'text' },
                    { key: 'activity_type', label: 'نوع فعالیت', type: 'text' },
                    { key: 'leads_count', label: 'تعداد لید', type: 'count', summable: true },
                    { key: 'activity_count', label: 'تعداد فعالیت', type: 'count', summable: true }
                ]
            }
        };
    },

    renderKpiDailyOperationalSection() {
        const defs = this.getKpiDailyOperationalDefinitions();
        const tableIds = Object.keys(defs).filter((tableId) => {
            const rows = this.state.kpiDailyOperationalTables?.[tableId];
            return Array.isArray(rows) && rows.length > 0;
        });
        if (tableIds.length === 0) {
            return '<div class="text-center text-gray-500 py-6">داده‌ای برای KPI عملیاتی روزانه وجود ندارد.</div>';
        }

        const panels = tableIds
            .map(tableId => `<div id="kpi-daily-panel-${tableId}" class="mt-4">${this.renderKpiDailyOperationalPanel(tableId)}</div>`)
            .join('');

        return `
            <div class="text-xs text-gray-400 mb-3">
                این جداول تعاملی هستند: مرتب‌سازی، فیلتر داخلی، مخفی/نمایش ستون، مخفی‌سازی ردیف‌های صفر و خروجی CSV.
            </div>
            <div class="mb-4 flex justify-end">
                <button onclick="app.exportAllKpiDailyOperationalTables()" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded text-sm">
                    خروجی همه جدول‌های روزانه (CSV)
                </button>
            </div>
            ${panels}
        `;
    },

    renderKpiDailyOperationalPanel(tableId) {
        const defs = this.getKpiDailyOperationalDefinitions();
        const definition = defs[tableId];
        if (!definition) return '';
        const rows = Array.isArray(this.state.kpiDailyOperationalTables?.[tableId]) ? this.state.kpiDailyOperationalTables[tableId] : [];
        const ui = this.state.kpiDailyOperationalUi?.[tableId] || { search: '', hideZeroRows: false };
        const visibleColumns = this.getKpiDailyOperationalVisibleColumns(tableId);

        const columnToggles = definition.columns.map(col => `
            <label class="inline-flex items-center ml-3 mb-2 text-xs text-gray-300">
                <input type="checkbox" ${visibleColumns.some(v => v.key === col.key) ? 'checked' : ''}
                    onchange="app.toggleKpiDailyOperationalColumn('${tableId}','${col.key}', this.checked)" class="ml-1">
                ${col.label}
            </label>
        `).join('');

        return `
            <div class="border border-gray-700 rounded-lg p-4">
                <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-3">
                    <div class="text-sm font-bold text-blue-300">${definition.title}</div>
                    <button onclick="app.exportKpiDailyOperationalCsv('${tableId}')" class="bg-blue-700 hover:bg-blue-600 text-white px-3 py-2 rounded text-xs">
                        خروجی CSV
                    </button>
                </div>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
                    <input type="text" value="${this.escapeHtml(ui.search || '')}" oninput="app.setKpiDailyOperationalSearch('${tableId}', this.value)"
                        class="bg-gray-800 border border-gray-700 rounded p-2 text-sm text-white" placeholder="فیلتر داخلی جدول...">
                    <label class="inline-flex items-center text-xs text-gray-300 bg-gray-800 border border-gray-700 rounded p-2">
                        <input type="checkbox" class="ml-2" ${ui.hideZeroRows ? 'checked' : ''} onchange="app.toggleKpiDailyOperationalHideZeroRows('${tableId}', this.checked)">
                        مخفی‌کردن ردیف‌های با جمع صفر
                    </label>
                    <div class="bg-gray-800 border border-gray-700 rounded p-2 text-xs">${columnToggles}</div>
                </div>
                ${rows.length === 0 ? '<div class="text-xs text-gray-500">رکوردی در این جدول وجود ندارد.</div>' : this.renderKpiDailyOperationalGrid(tableId)}
            </div>
        `;
    },

    getKpiDailyOperationalVisibleColumns(tableId) {
        const defs = this.getKpiDailyOperationalDefinitions();
        const definition = defs[tableId];
        const ui = this.state.kpiDailyOperationalUi?.[tableId] || {};
        if (!definition) return [];
        return definition.columns.filter(col => ui.visibleColumns?.[col.key] !== false);
    },

    getKpiDailyOperationalProcessedRows(tableId) {
        const defs = this.getKpiDailyOperationalDefinitions();
        const definition = defs[tableId];
        const ui = this.state.kpiDailyOperationalUi?.[tableId] || {};
        const rows = Array.isArray(this.state.kpiDailyOperationalTables?.[tableId]) ? [...this.state.kpiDailyOperationalTables[tableId]] : [];
        if (!definition) return [];

        const search = (ui.search || '').trim().toLowerCase();
        let filtered = rows;
        if (search) {
            filtered = filtered.filter(row => {
                return definition.columns.some(col => String(row[col.key] ?? '').toLowerCase().includes(search));
            });
        }

        if (ui.hideZeroRows) {
            const sumCols = definition.columns.filter(c => c.summable);
            filtered = filtered.filter(row => {
                const rowSum = sumCols.reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
                return rowSum > 0;
            });
        }

        const sortKey = ui.sortKey || definition.defaultSortKey || 'date';
        const sortDir = ui.sortDir === 'desc' ? -1 : 1;
        const sortCol = definition.columns.find(c => c.key === sortKey);
        filtered.sort((a, b) => {
            const va = a[sortKey];
            const vb = b[sortKey];
            if (sortCol && ['count', 'amount', 'percent', 'day'].includes(sortCol.type)) {
                return ((Number(va || 0) - Number(vb || 0)) * sortDir);
            }
            return String(va ?? '').localeCompare(String(vb ?? ''), 'fa', { numeric: true }) * sortDir;
        });

        if (definition.showOwnerTotals) {
            filtered = this.injectKpiAdvancedOwnerTotals(filtered, definition);
        }

        return filtered;
    },

    injectKpiAdvancedOwnerTotals(rows, definition) {
        if (!Array.isArray(rows) || rows.length === 0) return rows;
        const preparedRows = rows.map((row) => {
            const next = { ...row };
            if (definition?.title === '5) وضعیت نرخ تبدیل') {
                next.owner_range_conversion_rate = null;
            }
            return next;
        });
        const output = [...preparedRows];
        const byOwner = {};

        preparedRows.forEach((row) => {
            const ownerKey = `${row.owner_didar_id || ''}__${row.owner_name || ''}`;
            if (!byOwner[ownerKey]) {
                byOwner[ownerKey] = [];
            }
            byOwner[ownerKey].push(row);
        });

        const ownerTotals = {};
        Object.keys(byOwner).forEach((ownerKey) => {
            const ownerRows = byOwner[ownerKey];
            const totalRow = {
                date: 'جمع کارشناس',
                owner_name: `${ownerRows[0].owner_name || '-'} (جمع)`,
                owner_didar_id: ownerRows[0].owner_didar_id || '',
                __owner_total__: true
            };

            definition.columns.forEach((col) => {
                if (col.summable) {
                    totalRow[col.key] = ownerRows.reduce((sum, row) => sum + Number(row[col.key] || 0), 0);
                } else if (col.aggregation === 'rate') {
                    const num = ownerRows.reduce((sum, row) => sum + Number(row[col.numeratorKey] || 0), 0);
                    const den = ownerRows.reduce((sum, row) => sum + Number(row[col.denominatorKey] || 0), 0);
                    totalRow[col.key] = den !== 0 ? (num / den) * 100 : 0;
                } else if (col.aggregation === 'rate_inverse') {
                    const num = ownerRows.reduce((sum, row) => sum + Number(row[col.numeratorKey] || 0), 0);
                    const den = ownerRows.reduce((sum, row) => sum + Number(row[col.denominatorKey] || 0), 0);
                    totalRow[col.key] = den !== 0 ? ((den - num) / den) * 100 : 0;
                } else if (col.aggregation === 'average') {
                    const sumVal = ownerRows.reduce((sum, row) => sum + Number(row[col.sumKey] || 0), 0);
                    const countVal = ownerRows.reduce((sum, row) => sum + Number(row[col.countKey] || 0), 0);
                    totalRow[col.key] = countVal !== 0 ? (sumVal / countVal) : 0;
                }
            });
            if (definition?.title === '5) وضعیت نرخ تبدیل') {
                const ownerNum = ownerRows.reduce((sum, row) => sum + Number(row.sales_total_count || 0), 0);
                const ownerDen = ownerRows.reduce((sum, row) => sum + Number(row.total_deals_count || 0), 0);
                totalRow.owner_range_conversion_rate = ownerDen !== 0 ? (ownerNum / ownerDen) * 100 : 0;
            }
            ownerTotals[ownerKey] = totalRow;
        });

        Object.keys(ownerTotals).forEach((ownerKey) => {
            output.push(ownerTotals[ownerKey]);
        });

        return output;
    },

    renderKpiDailyOperationalGrid(tableId) {
        const defs = this.getKpiDailyOperationalDefinitions();
        const definition = defs[tableId];
        if (!definition) return '';

        const rows = this.getKpiDailyOperationalProcessedRows(tableId);
        const visibleColumns = this.getKpiDailyOperationalVisibleColumns(tableId);
        const ui = this.state.kpiDailyOperationalUi?.[tableId] || {};
        const totals = {};
        
        // Calculate max values for heatmap
        const maxValues = {};
        visibleColumns.forEach(col => {
            if (['count', 'amount'].includes(col.type)) {
                maxValues[col.key] = Math.max(...rows.map(r => Number(r[col.key] || 0)), 0);
            }
            if (col.summable) {
                totals[col.key] = rows.reduce((sum, row) => sum + Number(row[col.key] || 0), 0);
            }
        });

        const hasRowTotal = !!definition.rowTotalUnit;
        
        const headerHtml = visibleColumns.map((col, index) => {
            const sortIcon = ui.sortKey === col.key ? (ui.sortDir === 'asc' ? '▲' : '▼') : '';
            // Sticky logic for first column
            const stickyClass = index === 0 ? 'sticky right-0 z-10 bg-gray-800 border-l border-gray-600 shadow-md' : '';
            return `<th class="border border-gray-700 p-2 cursor-pointer select-none ${stickyClass}" onclick="app.setKpiDailyOperationalSort('${tableId}','${col.key}')">${col.label} ${sortIcon}</th>`;
        }).join('');

        const rowTotalFor = (row) => {
            if (!hasRowTotal) return 0;
            return visibleColumns
                .filter(c => c.summable && c.type === definition.rowTotalUnit)
                .reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
        };

        const bodyHtml = rows.map(row => {
            const cells = visibleColumns.map((col, index) => {
                // Heatmap style
                let style = '';
                if (['count', 'amount'].includes(col.type) && maxValues[col.key] > 0) {
                    const val = Number(row[col.key] || 0);
                    const ratio = val / maxValues[col.key];
                    if (ratio > 0) {
                        const alpha = Math.min(ratio * 0.4, 0.4); 
                        style = `background-color: rgba(16, 185, 129, ${alpha});`;
                    }
                }
                
                // Sticky logic
                // Ensure bg-gray-900 is set if no heatmap, to cover scrolled content
                const stickyClass = index === 0 ? 'sticky right-0 z-10 bg-gray-900 border-l border-gray-700 shadow-md' : '';
                
                return `<td class="border border-gray-700 p-2 ${stickyClass}" style="${style}">${this.formatKpiAdvancedCell(row[col.key], col.type)}</td>`;
            }).join('');
            
            const totalCell = hasRowTotal ? `<td class="border border-gray-700 p-2 font-bold">${this.formatKpiAdvancedCell(rowTotalFor(row), definition.rowTotalUnit)}</td>` : '';
            return `<tr class="hover:bg-gray-800/70 transition-colors">${cells}${totalCell}</tr>`;
        }).join('');

        const footerCells = visibleColumns.map((col, index) => {
            const stickyClass = index === 0 ? 'sticky right-0 z-10 bg-gray-800 border-l border-gray-600 shadow-md' : '';
            if (index === 0) return `<td class="border border-gray-700 p-2 font-bold ${stickyClass}">جمع</td>`;
            if (col.summable) return `<td class="border border-gray-700 p-2 font-bold">${this.formatKpiAdvancedCell(totals[col.key] || 0, col.type)}</td>`;
            return '<td class="border border-gray-700 p-2">-</td>';
        }).join('');
        const footerTotal = hasRowTotal
            ? `<td class="border border-gray-700 p-2 font-bold">${this.formatKpiAdvancedCell(
                Object.keys(totals).reduce((sum, key) => {
                    const col = visibleColumns.find(c => c.key === key);
                    return (col && col.type === definition.rowTotalUnit) ? (sum + Number(totals[key] || 0)) : sum;
                }, 0),
                definition.rowTotalUnit
            )}</td>`
            : '';

        return `
            <div class="overflow-x-auto rounded-lg custom-scrollbar">
                <table class="w-full text-xs border-collapse">
                    <thead class="bg-gray-800"><tr>${headerHtml}${hasRowTotal ? '<th class="border border-gray-700 p-2">جمع ردیف</th>' : ''}</tr></thead>
                    <tbody class="bg-gray-900">${bodyHtml || `<tr><td colspan="${visibleColumns.length + (hasRowTotal ? 1 : 0)}" class="p-3 text-center text-gray-500">رکوردی مطابق فیلتر وجود ندارد.</td></tr>`}</tbody>
                    <tfoot class="bg-gray-800/70"><tr>${footerCells}${footerTotal}</tr></tfoot>
                </table>
            </div>
        `;
    },

    setKpiDailyOperationalSearch(tableId, value) {
        if (!this.state.kpiDailyOperationalUi?.[tableId]) return;
        this.state.kpiDailyOperationalUi[tableId].search = value || '';
        this.refreshKpiDailyOperationalPanel(tableId);
    },

    setKpiDailyOperationalSort(tableId, sortKey) {
        const ui = this.state.kpiDailyOperationalUi?.[tableId];
        if (!ui) return;
        if (ui.sortKey === sortKey) {
            ui.sortDir = ui.sortDir === 'asc' ? 'desc' : 'asc';
        } else {
            ui.sortKey = sortKey;
            ui.sortDir = 'asc';
        }
        this.refreshKpiDailyOperationalPanel(tableId);
    },

    toggleKpiDailyOperationalColumn(tableId, colKey, checked) {
        if (!this.state.kpiDailyOperationalUi?.[tableId]) return;
        this.state.kpiDailyOperationalUi[tableId].visibleColumns[colKey] = !!checked;
        this.refreshKpiDailyOperationalPanel(tableId);
    },

    toggleKpiDailyOperationalHideZeroRows(tableId, checked) {
        if (!this.state.kpiDailyOperationalUi?.[tableId]) return;
        this.state.kpiDailyOperationalUi[tableId].hideZeroRows = !!checked;
        this.refreshKpiDailyOperationalPanel(tableId);
    },

    refreshKpiDailyOperationalPanel(tableId) {
        const panel = document.getElementById(`kpi-daily-panel-${tableId}`);
        if (!panel) return;
        panel.innerHTML = this.renderKpiDailyOperationalPanel(tableId);
    },

    exportKpiDailyOperationalCsv(tableId) {
        const defs = this.getKpiDailyOperationalDefinitions();
        const definition = defs[tableId];
        if (!definition) return;
        const visibleColumns = this.getKpiDailyOperationalVisibleColumns(tableId);
        const rows = this.getKpiDailyOperationalProcessedRows(tableId);
        const hasRowTotal = !!definition.rowTotalUnit;
        const headers = visibleColumns.map(c => c.label);
        if (hasRowTotal) headers.push('جمع ردیف');
        const lines = [headers.join(',')];

        rows.forEach(row => {
            const cells = visibleColumns.map(col => this.csvEscape(this.formatKpiAdvancedCell(row[col.key], col.type)));
            if (hasRowTotal) {
                const rowTotal = visibleColumns
                    .filter(c => c.summable && c.type === definition.rowTotalUnit)
                    .reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
                cells.push(this.csvEscape(this.formatKpiAdvancedCell(rowTotal, definition.rowTotalUnit)));
            }
            lines.push(cells.join(','));
        });

        const csvText = "\uFEFF" + lines.join('\n');
        const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `kpi_daily_${tableId}_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.csv`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    },

    exportAllKpiDailyOperationalTables() {
        const defs = this.getKpiDailyOperationalDefinitions();
        const tableIds = Object.keys(defs).filter((tableId) => {
            const rows = this.state.kpiDailyOperationalTables?.[tableId];
            return Array.isArray(rows) && rows.length > 0;
        });
        tableIds.forEach((tableId, index) => {
            setTimeout(() => this.exportKpiDailyOperationalCsv(tableId), index * 250);
        });
    },

    ensureKpiAdvancedUiState() {
        const tables = this.state.kpiAdvancedTables || {};
        if (!this.state.kpiAdvancedUi) {
            this.state.kpiAdvancedUi = {};
        }
        if (!this.state.kpiAdvancedVisibleTables) {
            this.state.kpiAdvancedVisibleTables = {};
        }

        Object.keys(tables).forEach((tableId) => {
            const definition = this.getKpiAdvancedTableDefinition(tableId);
            if (!definition) return;
            const existing = this.state.kpiAdvancedUi[tableId] || {};
            const visibleColumns = existing.visibleColumns || {};
            definition.columns.forEach((col) => {
                if (visibleColumns[col.key] === undefined) {
                    visibleColumns[col.key] = col.defaultVisible !== false;
                }
            });
            this.state.kpiAdvancedUi[tableId] = {
                search: existing.search || '',
                sortKey: existing.sortKey || definition.defaultSortKey || 'date',
                sortDir: existing.sortDir || 'asc',
                hideZeroRows: !!existing.hideZeroRows,
                visibleColumns
            };
            if (this.state.kpiAdvancedVisibleTables[tableId] === undefined) {
                this.state.kpiAdvancedVisibleTables[tableId] = true;
            }
        });

        Object.keys(this.state.kpiAdvancedVisibleTables).forEach((tableId) => {
            if (!tables[tableId]) {
                delete this.state.kpiAdvancedVisibleTables[tableId];
            }
        });
    },

    getKpiAdvancedTableDefinition(tableId) {
        const defs = {
            summary_by_user: {
                title: 'خلاصه عملکرد کارشناسان',
                rowTotalUnit: null,
                defaultSortKey: 'conversion_rate_total',
                description: {
                    definition: 'این جدول یک نمای کلی از عملکرد هر کارشناس در بازه انتخابی می‌دهد؛ یعنی چند معامله داشته، چند فروش واقعی ثبت کرده، چقدر وصولی داشته و چقدر مانده دارد.',
                    source: 'اطلاعات معاملات، پرداخت‌ها، لیدها و مسئول هر رکورد که داخل همین سیستم ثبت شده‌اند.',
                    formula: 'هر معامله‌ای که حداقل یک پرداخت داشته باشد «فروش» حساب می‌شود. نرخ تبدیل هم از تقسیم تعداد فروش بر کل معاملات همان کارشناس به‌دست می‌آید.',
                    goal: 'برای مقایسه عملکرد کارشناسان، تشخیص افراد با پیگیری بهتر، و تصمیم‌گیری برای بهبود فروش استفاده می‌شود.'
                },
                columns: [
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'total_deals_count', label: 'کل معاملات', type: 'count', summable: true },
                    { key: 'sales_total_count', label: 'تعداد فروش', type: 'count', summable: true },
                    { key: 'conversion_rate_total', label: 'نرخ تبدیل کل', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_total_count', denominatorKey: 'total_deals_count' },
                    { key: 'total_new_deals_count', label: 'معاملات لید جدید', type: 'count', summable: true },
                    { key: 'sales_new_count', label: 'فروش لید جدید', type: 'count', summable: true },
                    { key: 'conversion_rate_new', label: 'نرخ تبدیل لید جدید', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_new_count', denominatorKey: 'total_new_deals_count' },
                    { key: 'total_collected_amount', label: 'جمع دریافتی', type: 'amount', summable: true },
                    { key: 'outstanding_amount_sum', label: 'جمع مانده', type: 'amount', summable: true }
                ]
            },
            deal_status: {
                title: '1) وضعیت معاملات',
                rowTotalUnit: 'count',
                rowTotalColumns: ['deal_total'],
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول نشان می‌دهد در هر روز برای هر کارشناس چند معامله ثبت شده و این معاملات در چه وضعیتی هستند: جاری، دارای فروش، بدون فروش و همچنین وضعیت سیستمی (موفق/ناموفق).',
                    source: 'داده‌های معاملات، اطلاعات لید مربوط به هر معامله، اطلاعات مسئول معامله و پرداخت‌های ثبت‌شده داخل سیستم.',
                    formula: 'اگر برای یک معامله حداقل یک پرداخت ثبت شده باشد «دارای فروش» است. اگر هیچ پرداختی نداشته باشد «بدون فروش» است. جدید یا قدیمی بودن هم از مقایسه تاریخ ثبت لید با تاریخ ثبت معامله محاسبه می‌شود.',
                    goal: 'این جدول کمک می‌کند بفهمیم حجم واقعی فروش از کدام بخش می‌آید و کدام معاملات هنوز به فروش نرسیده‌اند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'deal_total', label: 'کل معاملات', type: 'count', summable: true },
                    { key: 'deal_new_total', label: 'کل معاملات لید جدید', type: 'count', summable: true },
                    { key: 'deal_old_total', label: 'کل معاملات لید قدیمی', type: 'count', summable: true },
                    { key: 'deal_pending_total', label: 'کل معاملات جاری', type: 'count', summable: true },
                    { key: 'deal_pending_new', label: 'جاری لید جدید', type: 'count', summable: true },
                    { key: 'deal_pending_old', label: 'جاری لید قدیمی', type: 'count', summable: true },
                    { key: 'deal_with_sales_total', label: 'کل معاملات دارای فروش', type: 'count', summable: true },
                    { key: 'deal_with_sales_new', label: 'دارای فروش لید جدید', type: 'count', summable: true },
                    { key: 'deal_with_sales_old', label: 'دارای فروش لید قدیمی', type: 'count', summable: true },
                    { key: 'deal_without_sales_total', label: 'کل معاملات بدون فروش', type: 'count', summable: true },
                    { key: 'deal_without_sales_new', label: 'بدون فروش لید جدید', type: 'count', summable: true },
                    { key: 'deal_without_sales_old', label: 'بدون فروش لید قدیمی', type: 'count', summable: true },
                    { key: 'deal_won_total', label: 'کل معاملات موفق (سیستمی)', type: 'count', summable: true, defaultVisible: false },
                    { key: 'deal_won_new', label: 'موفق لید جدید (سیستمی)', type: 'count', summable: true, defaultVisible: false },
                    { key: 'deal_won_old', label: 'موفق لید قدیمی (سیستمی)', type: 'count', summable: true, defaultVisible: false },
                    { key: 'deal_lost_total', label: 'کل معاملات ناموفق (سیستمی)', type: 'count', summable: true, defaultVisible: false },
                    { key: 'deal_lost_new', label: 'ناموفق لید جدید (سیستمی)', type: 'count', summable: true, defaultVisible: false },
                    { key: 'deal_lost_old', label: 'ناموفق لید قدیمی (سیستمی)', type: 'count', summable: true, defaultVisible: false }
                ]
            },
            lead_status: {
                title: '2) وضعیت لیدها',
                rowTotalUnit: 'count',
                rowTotalColumns: ['total_leads'],
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول وضعیت لیدهای ثبت‌شده هر روز را با زبان فروش نشان می‌دهد: لید دارای فروش، لید بدون فروش، فروش جدید و فروش قبلی.',
                    source: 'اطلاعات لیدها، مسئول لید، تاریخ اولین پرداخت هر لید و وضعیت داشتن یا نداشتن معامله جاری.',
                    formula: 'اگر لید هر پرداختی داشته باشد «دارای فروش» است. اگر اولین پرداخت دقیقاً در همان روز ثبت لید باشد «فروش جدید» است. اگر اولین پرداخت قبل از روز ثبت لید باشد «فروش قبلی» است. لید بدون هیچ پرداختی، «بدون فروش» حساب می‌شود.',
                    goal: 'کمک می‌کند دقیق ببینیم رشد واقعی فروش روزانه از لیدهای تازه بوده یا از لیدهایی که از قبل به فروش رسیده بودند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'total_leads', label: 'تعداد کل لیدها', type: 'count', summable: true },
                    { key: 'leads_with_sales', label: 'لید دارای فروش', type: 'count', summable: true },
                    { key: 'leads_with_new_sales', label: 'فروش جدید', type: 'count', summable: true },
                    { key: 'leads_with_previous_sales', label: 'لید با فروش قبلی', type: 'count', summable: true },
                    { key: 'leads_without_sales', label: 'لید بدون فروش', type: 'count', summable: true },
                    { key: 'leads_without_sales_with_current_deal', label: 'بدون فروش با معامله جاری', type: 'count', summable: true },
                    { key: 'leads_without_sales_without_current_deal', label: 'بدون فروش بدون معامله جاری', type: 'count', summable: true },
                    { key: 'new_leads_count', label: 'تعداد لید جدید', type: 'count', summable: true }
                ]
            },
            payment_mix: {
                title: '3) پرداخت‌ها',
                rowTotalUnit: 'count',
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول نشان می‌دهد لیدهای جدید و قدیمی در هر روز در چه وضعیتی از نظر پرداخت هستند: با پرداخت، بدون پرداخت یا شکست‌خورده.',
                    source: 'اطلاعات معاملات همان روز، وضعیت پرداخت هر معامله و نوع لید (جدید/قدیمی).',
                    formula: 'برای لیدهایی که معامله دارند، اگر پرداخت ثبت شده باشد «با پرداخت» و اگر ثبت نشده باشد «بدون پرداخت» در نظر گرفته می‌شوند.',
                    goal: 'برای پیدا کردن لیدهایی که هنوز ظرفیت تبدیل به فروش دارند و باید سریع‌تر پیگیری شوند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'new_leads_with_payment', label: 'لید جدید با پرداخت', type: 'count', summable: true },
                    { key: 'new_leads_without_payment', label: 'لید جدید بدون پرداخت', type: 'count', summable: true },
                    { key: 'new_leads_failed', label: 'لید جدید ناموفق', type: 'count', summable: true },
                    { key: 'old_leads_with_payment', label: 'لید قدیمی با پرداخت', type: 'count', summable: true },
                    { key: 'old_leads_without_payment', label: 'لید قدیمی بدون پرداخت', type: 'count', summable: true },
                    { key: 'old_leads_failed', label: 'لید قدیمی ناموفق', type: 'count', summable: true }
                ]
            },
            payment_status: {
                title: '4) وضعیت پرداخت‌ها',
                rowTotalUnit: null,
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول وضعیت مالی معاملات را نشان می‌دهد: چند معامله هنوز مانده حساب دارند، چند معامله کامل تسویه شده‌اند و مجموع مبالغ هرکدام چقدر است.',
                    source: 'مبلغ کل قرارداد، مجموع پرداخت‌های ثبت‌شده و وضعیت پرداخت هر معامله در سیستم.',
                    formula: 'اگر مجموع پرداخت از مبلغ قرارداد کمتر باشد، معامله بدهکار است. اگر برابر یا بیشتر باشد، معامله تسویه‌شده محسوب می‌شود.',
                    goal: 'برای کنترل دقیق وصولی‌ها، مدیریت مانده‌ها و برنامه‌ریزی پیگیری مالی استفاده می‌شود.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'outstanding_deals_count', label: 'معامله با مانده حساب', type: 'count', summable: true },
                    { key: 'settled_deals_count', label: 'معامله تسویه‌شده', type: 'count', summable: true },
                    { key: 'outstanding_amount_sum', label: 'جمع مانده حساب', type: 'amount', summable: true },
                    { key: 'settled_contract_amount_sum', label: 'جمع تسویه‌شده', type: 'amount', summable: true },
                    { key: 'payments_sum', label: 'جمع کل پرداخت‌ها', type: 'amount', summable: true }
                ]
            },
            conversion_status: {
                title: '5) وضعیت نرخ تبدیل',
                rowTotalUnit: null,
                showOwnerTotals: true,
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول درصد تبدیل معاملات به فروش را برای کل، لید جدید و لید قدیمی نشان می‌دهد.',
                    source: 'تعداد کل معاملات و تعداد معاملاتی که واقعاً پرداخت داشته‌اند.',
                    formula: 'نرخ تبدیل برابر است با تعداد فروش تقسیم بر کل معاملات همان بخش، ضربدر ۱۰۰.',
                    goal: 'برای سنجش کیفیت تبدیل و بررسی اینکه کدام بخش نیاز به بهبود در فرآیند فروش دارد.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'owner_range_conversion_rate', label: 'نرخ تبدیل کل بازه کارشناس', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_total_count', denominatorKey: 'total_deals_count' },
                    { key: 'total_deals_count', label: 'کل معاملات', type: 'count', summable: true },
                    { key: 'total_new_deals_count', label: 'کل معاملات لید جدید', type: 'count', summable: true },
                    { key: 'total_old_deals_count', label: 'کل معاملات لید قدیمی', type: 'count', summable: true },
                    { key: 'conversion_rate_total', label: 'نرخ تبدیل کل', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_total_count', denominatorKey: 'total_deals_count' },
                    { key: 'non_conversion_rate_total', label: 'نرخ عدم تبدیل کل', type: 'percent', aggregation: 'rate_inverse', numeratorKey: 'sales_total_count', denominatorKey: 'total_deals_count' },
                    { key: 'conversion_rate_new', label: 'نرخ تبدیل لید جدید', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_new_count', denominatorKey: 'total_new_deals_count' },
                    { key: 'non_conversion_rate_new', label: 'نرخ عدم تبدیل لید جدید', type: 'percent', aggregation: 'rate_inverse', numeratorKey: 'sales_new_count', denominatorKey: 'total_new_deals_count' },
                    { key: 'conversion_rate_old', label: 'نرخ تبدیل لید قدیمی', type: 'percent', aggregation: 'rate', numeratorKey: 'sales_old_count', denominatorKey: 'total_old_deals_count' },
                    { key: 'non_conversion_rate_old', label: 'نرخ عدم تبدیل لید قدیمی', type: 'percent', aggregation: 'rate_inverse', numeratorKey: 'sales_old_count', denominatorKey: 'total_old_deals_count' },
                    { key: 'sales_total_count', label: 'تعداد فروش کل', type: 'count', summable: true },
                    { key: 'sales_new_count', label: 'تعداد فروش لید جدید', type: 'count', summable: true },
                    { key: 'sales_old_count', label: 'تعداد فروش لید قدیمی', type: 'count', summable: true }
                ]
            },
            conversion_time: {
                title: '6) میانگین زمان تبدیل',
                rowTotalUnit: null,
                showOwnerTotals: true,
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول نشان می‌دهد به طور میانگین چند روز طول می‌کشد یک معامله به اولین پرداخت برسد یا به وضعیت شکست برسد.',
                    source: 'تاریخ ثبت معامله، تاریخ اولین پرداخت و تاریخ ناموفق شدن معامله.',
                    formula: 'زمان تبدیل از اختلاف تاریخ اولین پرداخت و تاریخ ثبت معامله محاسبه می‌شود. زمان شکست هم از اختلاف تاریخ ناموفق شدن و تاریخ ثبت معامله.',
                    goal: 'برای سنجش سرعت تیم فروش و پیدا کردن گلوگاه‌های تأخیر در مسیر تبدیل.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'avg_conversion_days_total', label: 'میانگین تبدیل کل (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_conversion_days_total', countKey: 'count_conversion_days_total' },
                    { key: 'avg_conversion_days_new', label: 'میانگین تبدیل لید جدید (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_conversion_days_new', countKey: 'count_conversion_days_new' },
                    { key: 'avg_conversion_days_old', label: 'میانگین تبدیل لید قدیمی (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_conversion_days_old', countKey: 'count_conversion_days_old' },
                    { key: 'avg_failure_days_total', label: 'میانگین شکست کل (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_failure_days_total', countKey: 'count_failure_days_total' },
                    { key: 'avg_failure_days_new', label: 'میانگین شکست لید جدید (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_failure_days_new', countKey: 'count_failure_days_new' },
                    { key: 'avg_failure_days_old', label: 'میانگین شکست لید قدیمی (روز)', type: 'day', aggregation: 'average', sumKey: 'sum_failure_days_old', countKey: 'count_failure_days_old' },
                    { key: 'payments_after_deal_count', label: 'پرداخت بعد از معامله', type: 'count', summable: true },
                    { key: 'payments_before_deal_count', label: 'پرداخت قبل از معامله', type: 'count', summable: true },
                    { key: 'payments_same_day_count', label: 'پرداخت همان‌روز معامله', type: 'count', summable: true }
                ]
            },
            failure_reasons: {
                title: '7) دلایل شکست',
                rowTotalUnit: 'count',
                rowTotalColumns: ['failed_new_count', 'failed_old_count'],
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول دلیل‌هایی را که باعث ناموفق شدن معامله شده‌اند، به تفکیک کارشناس و نوع لید نمایش می‌دهد.',
                    source: 'دلایل ثبت‌شده برای معاملات ناموفق در سیستم فروش.',
                    formula: 'فقط معاملاتی در این جدول شمارش می‌شوند که واقعاً در وضعیت ناموفق ثبت شده‌اند.',
                    goal: 'برای تشخیص علت‌های پرتکرار از دست‌رفتن فروش و اصلاح فرآیند مذاکره یا پیگیری.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'failure_reason', label: 'عنوان دلیل', type: 'text' },
                    { key: 'failed_without_current_count', label: 'ناموفق بدون معامله جاری', type: 'count', summable: true },
                    { key: 'failed_new_count', label: 'ناموفق لید جدید', type: 'count', summable: true },
                    { key: 'failed_old_count', label: 'ناموفق لید قدیمی', type: 'count', summable: true }
                ]
            },
            product_status: {
                title: '8) وضعیت محصولات',
                rowTotalUnit: 'count',
                rowTotalColumns: ['sales_total_count'],
                defaultSortKey: 'date',
                description: {
                    definition: 'این جدول نشان می‌دهد هر محصول چند فروش داشته و این فروش‌ها از لید جدید بوده یا لید قدیمی.',
                    source: 'محصولات هر معامله به‌همراه وضعیت پرداخت همان معامله.',
                    formula: 'فقط معامله‌هایی به عنوان فروش محصول شمرده می‌شوند که حداقل یک پرداخت داشته باشند.',
                    goal: 'برای تحلیل عملکرد محصولات و تمرکز روی محصولاتی که فروش بهتری دارند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'product_name', label: 'نام محصول', type: 'text' },
                    { key: 'sales_total_count', label: 'تعداد فروش کل', type: 'count', summable: true },
                    { key: 'sales_new_count', label: 'تعداد فروش لید جدید', type: 'count', summable: true },
                    { key: 'sales_old_count', label: 'تعداد فروش لید قدیمی', type: 'count', summable: true }
                ]
            },
            product_sold_by_owner_day: {
                title: '8-1) محصولات فروخته‌شده',
                rowTotalUnit: 'count',
                rowTotalColumns: ['sold_total_count'],
                defaultSortKey: 'date',
                description: {
                    definition: 'تعداد فروش هر محصول در هر روز و هر کارشناس با تفکیک لید جدید و قدیمی.',
                    source: 'deals + deal_products + products + payments',
                    formula: 'فقط معاملاتی محاسبه می‌شوند که حداقل یک پرداخت ثبت کرده باشند.',
                    goal: 'مشخص‌کردن اینکه هر کارشناس در هر روز از هر محصول چه مقدار فروش واقعی داشته است.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'product_name', label: 'نام محصول', type: 'text' },
                    { key: 'sold_new_count', label: 'فروش لید جدید', type: 'count', summable: true },
                    { key: 'sold_old_count', label: 'فروش لید قدیمی', type: 'count', summable: true },
                    { key: 'sold_total_count', label: 'فروش کل', type: 'count', summable: true }
                ]
            },
            product_requests_by_owner_day: {
                title: '8-2) کل محصولات درخواستی',
                rowTotalUnit: 'count',
                rowTotalColumns: ['requested_total_count'],
                defaultSortKey: 'date',
                description: {
                    definition: 'حجم درخواست محصول در معاملات و نسبت فروش‌رفته/فروش‌نرفته برای هر محصول، روز و کارشناس.',
                    source: 'deals + deal_products + products + payments',
                    formula: 'درخواست = وجود محصول در معامله؛ فروش‌رفته = has_payment=1؛ فروش‌نرفته = has_payment=0.',
                    goal: 'تحلیل شکاف بین تقاضای محصول و فروش واقعی برای تصمیم‌گیری تیم فروش.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'product_name', label: 'نام محصول', type: 'text' },
                    { key: 'requested_new_count', label: 'درخواست لید جدید', type: 'count', summable: true },
                    { key: 'requested_old_count', label: 'درخواست لید قدیمی', type: 'count', summable: true },
                    { key: 'requested_total_count', label: 'درخواست کل', type: 'count', summable: true },
                    { key: 'sold_total_count', label: 'فروش‌رفته', type: 'count', summable: true },
                    { key: 'unsold_total_count', label: 'فروش‌نرفته', type: 'count', summable: true }
                ]
            },
            product_failed_by_owner_day: {
                title: '8-3) فروش ناموفق محصولات',
                rowTotalUnit: 'count',
                rowTotalColumns: ['failed_total_count'],
                defaultSortKey: 'date',
                description: {
                    definition: 'تعداد معاملات هر محصول که هنوز به فروش نرسیده‌اند، با تفکیک لید جدید و قدیمی.',
                    source: 'deals + deal_products + products + payments',
                    formula: 'ناموفق در این جدول یعنی معامله محصولی که پرداختی ثبت نکرده است (has_payment=0).',
                    goal: 'تمرکز روی محصولات و کارشناسانی که بیشترین عدم‌تبدیل را دارند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'product_name', label: 'نام محصول', type: 'text' },
                    { key: 'failed_new_count', label: 'ناموفق لید جدید', type: 'count', summable: true },
                    { key: 'failed_old_count', label: 'ناموفق لید قدیمی', type: 'count', summable: true },
                    { key: 'failed_total_count', label: 'ناموفق کل', type: 'count', summable: true }
                ]
            },
            activity_summary: {
                title: '9-1) خلاصه فعالیت روزانه',
                rowTotalUnit: null,
                defaultSortKey: 'date',
                description: {
                    definition: 'نمای کلی عملکرد روزانه هر کارشناس را نشان می‌دهد: چه تعداد فعالیت ثبت شده، چند دقیقه زمان صرف شده و چه مقدار از لیدهای جدید/قدیمی پوشش پیگیری داشته‌اند.',
                    source: 'جدول activities به‌همراه persons و users.',
                    formula: 'تجمیع در سطح «تاریخ + کارشناس» انجام می‌شود و هر ردیف یک خلاصه یکتا از همان روز است.',
                    goal: 'برای کنترل حجم کار روزانه، کیفیت پیگیری و تشخیص روزهایی که لیدها بدون فعالیت مانده‌اند.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'total_activities', label: 'کل فعالیت‌ها', type: 'count', summable: true },
                    { key: 'total_duration_minutes', label: 'مدت کل فعالیت‌ها (دقیقه)', type: 'count', summable: true },
                    { key: 'avg_duration_minutes', label: 'میانگین مدت هر فعالیت (دقیقه)', type: 'count' },
                    { key: 'activities_new_lead', label: 'فعالیت روی لید جدید', type: 'count', summable: true },
                    { key: 'activities_old_lead', label: 'فعالیت روی لید قدیمی', type: 'count', summable: true },
                    { key: 'new_leads_without_activity', label: 'لید جدید بدون فعالیت', type: 'count', summable: true },
                    { key: 'old_leads_without_activity', label: 'لید قدیمی بدون فعالیت', type: 'count', summable: true },
                    { key: 'no_response_new', label: 'تماس بی‌پاسخ لید جدید', type: 'count', summable: true },
                    { key: 'no_response_old', label: 'تماس بی‌پاسخ لید قدیمی', type: 'count', summable: true },
                    { key: 'no_response_total', label: 'کل تماس بی‌پاسخ', type: 'count', summable: true }
                ]
            },
            activity_type_details: {
                title: '9-2) جزئیات نوع فعالیت',
                rowTotalUnit: null,
                defaultSortKey: 'date',
                description: {
                    definition: 'جزئیات نوع فعالیت را در هر روز و برای هر کارشناس نشان می‌دهد تا مشخص شود سهم واقعی هر نوع پیگیری چقدر بوده است.',
                    source: 'جدول activities با تجمیع بر اساس عنوان نوع فعالیت (activity_type_title).',
                    formula: 'هر ردیف فقط یک نوع فعالیت را نشان می‌دهد و ستون «درصد سهم از کل فعالیت روز» نسبت همان نوع به کل فعالیت‌های همان روزِ همان کارشناس است.',
                    goal: 'برای تحلیل ترکیب فعالیت‌ها (مثلاً تماس/پیگیری/چت) و بهبود سبک پیگیری تیم فروش.'
                },
                columns: [
                    { key: 'date', label: 'تاریخ', type: 'date' },
                    { key: 'owner_name', label: 'کارشناس', type: 'text' },
                    { key: 'activity_type', label: 'نوع فعالیت', type: 'text' },
                    { key: 'activity_count_total', label: 'تعداد این نوع فعالیت', type: 'count', summable: true },
                    { key: 'activity_type_new_count', label: 'تعداد روی لید جدید', type: 'count', summable: true },
                    { key: 'activity_type_old_count', label: 'تعداد روی لید قدیمی', type: 'count', summable: true },
                    { key: 'activity_type_duration_minutes', label: 'مدت این نوع فعالیت (دقیقه)', type: 'count', summable: true },
                    { key: 'avg_activity_type_duration_minutes', label: 'میانگین مدت این نوع (دقیقه)', type: 'count' },
                    { key: 'activity_share_percent', label: 'درصد سهم از کل فعالیت روز', type: 'percent' }
                ]
            }
        };

        return defs[tableId] || null;
    },

    renderKpiAdvancedTablesSection() {
        const tables = this.state.kpiAdvancedTables || {};
        const tableIds = this.getVisibleKpiAdvancedTableIds();
        if (tableIds.length === 0) {
            return '<div class="text-sm text-gray-500">برای بازه انتخاب‌شده داده تحلیلی پیشرفته موجود نیست.</div>';
        }

        // Navigation is now in the main header
        // Responsive scroll margin: small on mobile (relative header), large on desktop (sticky header)
        const panels = tableIds
            .map(tableId => `<div id="kpi-adv-panel-${tableId}" class="scroll-mt-[30rem] transition-all duration-500 mb-8">${this.renderKpiAdvancedTablePanel(tableId)}</div>`)
            .join('');

        return `
            <div class="mb-4 flex justify-end">
                <button onclick="app.exportAllKpiAdvancedTables()" class="bg-emerald-700 hover:bg-emerald-600 text-white px-4 py-2 rounded text-sm shadow-lg flex items-center gap-2 transition-colors">
                    <i class="fa-solid fa-file-csv"></i>
                    خروجی همه جدول‌ها (CSV)
                </button>
            </div>
            ${panels}
        `;
    },

    getVisibleKpiAdvancedTableIds() {
        const all = Object.keys(this.state.kpiAdvancedTables || {});
        return all.filter(tableId => this.state.kpiAdvancedVisibleTables?.[tableId] !== false);
    },

    toggleKpiAdvancedTableVisibility(tableId, isVisible) {
        if (!this.state.kpiAdvancedVisibleTables) {
            this.state.kpiAdvancedVisibleTables = {};
        }
        this.state.kpiAdvancedVisibleTables[tableId] = !!isVisible;
        if (this.state.kpiLastData) {
            this.renderKPIDashboard(this.state.kpiLastData);
        }
    },
    
    scrollToKpiTop() {
        const content = document.getElementById('kpi-dashboard-content');
        if (content) {
            content.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    },

    scrollToKpiTable(elementId) {
        const element = document.getElementById(elementId);
        if (element) {
            // Use scrollIntoView which handles overflow containers correctly
            element.scrollIntoView({ behavior: 'smooth', block: 'start' });

            // Highlight effect
            element.classList.add('ring-2', 'ring-blue-500', 'ring-offset-2', 'ring-offset-gray-900', 'rounded-lg');
            setTimeout(() => {
                element.classList.remove('ring-2', 'ring-blue-500', 'ring-offset-2', 'ring-offset-gray-900', 'rounded-lg');
            }, 2000);
        }
    },

    renderKpiAdvancedTablePanel(tableId) {
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const rows = Array.isArray(this.state.kpiAdvancedTables?.[tableId]) ? this.state.kpiAdvancedTables[tableId] : [];
        if (!definition) return '';
        const ui = this.state.kpiAdvancedUi?.[tableId] || { search: '' };
        const visibleColumns = this.getKpiAdvancedVisibleColumns(tableId);

        const columnToggles = definition.columns.map(col => `
            <label class="inline-flex items-center ml-3 mb-2 text-xs text-gray-300">
                <input type="checkbox" ${visibleColumns.some(v => v.key === col.key) ? 'checked' : ''}
                    onchange="app.toggleKpiAdvancedColumn('${tableId}','${col.key}', this.checked)" class="ml-1">
                ${col.label}
            </label>
        `).join('');

        const descriptionBlock = definition.description ? `
            <div class="mb-3 grid grid-cols-1 md:grid-cols-2 gap-2 text-xs">
                <div class="bg-gray-800 border border-gray-700 rounded p-2"><span class="text-gray-400">تعریف:</span> <span class="text-gray-200">${this.escapeHtml(definition.description.definition || '-')}</span></div>
                <div class="bg-gray-800 border border-gray-700 rounded p-2"><span class="text-gray-400">منبع داده:</span> <span class="text-gray-200">${this.escapeHtml(definition.description.source || '-')}</span></div>
                <div class="bg-gray-800 border border-gray-700 rounded p-2"><span class="text-gray-400">فرمول:</span> <span class="text-gray-200">${this.escapeHtml(definition.description.formula || '-')}</span></div>
                <div class="bg-gray-800 border border-gray-700 rounded p-2"><span class="text-gray-400">هدف:</span> <span class="text-gray-200">${this.escapeHtml(definition.description.goal || '-')}</span></div>
            </div>
        ` : '';

        return `
            <div class="border border-gray-700 rounded-lg p-4">
                <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-3">
                    <div class="text-sm font-bold text-blue-300">${definition.title}</div>
                    <div class="flex gap-2">
                        <button onclick="app.exportKpiAdvancedTableCsv('${tableId}')" class="bg-blue-700 hover:bg-blue-600 text-white px-3 py-2 rounded text-xs">خروجی CSV</button>
                    </div>
                </div>
                ${descriptionBlock}
                <div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
                    <input type="text" value="${this.escapeHtml(ui.search || '')}" oninput="app.setKpiAdvancedSearch('${tableId}', this.value)"
                        class="bg-gray-800 border border-gray-700 rounded p-2 text-sm text-white" placeholder="فیلتر داخلی جدول...">
                    <div class="bg-gray-800 border border-gray-700 rounded p-2 text-xs">
                        <label class="inline-flex items-center text-xs text-gray-300 mb-2">
                            <input type="checkbox" class="ml-1" ${ui.hideZeroRows ? 'checked' : ''} onchange="app.toggleKpiAdvancedHideZeroRows('${tableId}', this.checked)">
                            مخفی‌کردن ردیف‌های با جمع صفر
                        </label>
                        <div>${columnToggles}</div>
                    </div>
                </div>
                ${rows.length === 0 ? '<div class="text-xs text-gray-500">رکوردی در این جدول وجود ندارد.</div>' : this.renderKpiAdvancedTableGrid(tableId)}
            </div>
        `;
    },

    getKpiAdvancedVisibleColumns(tableId) {
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const ui = this.state.kpiAdvancedUi?.[tableId] || {};
        if (!definition) return [];
        return definition.columns.filter(col => ui.visibleColumns?.[col.key] !== false);
    },

    getKpiAdvancedProcessedRows(tableId) {
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const ui = this.state.kpiAdvancedUi?.[tableId] || {};
        const rows = Array.isArray(this.state.kpiAdvancedTables?.[tableId]) ? [...this.state.kpiAdvancedTables[tableId]] : [];
        if (!definition) return [];

        const search = (ui.search || '').trim().toLowerCase();
        let filtered = rows;
        if (search) {
            filtered = rows.filter(row => {
                return definition.columns.some(col => String(row[col.key] ?? '').toLowerCase().includes(search));
            });
        }

        if (ui.hideZeroRows) {
            const sumCols = definition.columns.filter(c => c.summable);
            filtered = filtered.filter(row => {
                const rowSum = sumCols.reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
                return rowSum > 0;
            });
        }

        const sortKey = ui.sortKey || definition.defaultSortKey || 'date';
        const sortDir = ui.sortDir === 'desc' ? -1 : 1;
        const sortCol = definition.columns.find(c => c.key === sortKey);
        filtered.sort((a, b) => {
            const va = a[sortKey];
            const vb = b[sortKey];
            if (sortCol && ['count', 'amount', 'percent', 'day'].includes(sortCol.type)) {
                return ((Number(va || 0) - Number(vb || 0)) * sortDir);
            }
            return String(va ?? '').localeCompare(String(vb ?? ''), 'fa', { numeric: true }) * sortDir;
        });

        return filtered;
    },

    getKpiAdvancedRowTotalColumns(definition, visibleColumns) {
        if (!definition || !definition.rowTotalUnit) return [];
        if (Array.isArray(definition.rowTotalColumns) && definition.rowTotalColumns.length > 0) {
            return definition.columns.filter((col) => definition.rowTotalColumns.includes(col.key));
        }
        return visibleColumns.filter(c => c.summable && c.type === definition.rowTotalUnit);
    },

    renderKpiAdvancedTableGrid(tableId) {
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        if (!definition) return '';
        if (this.isKpiProductPivotTable(tableId)) {
            return this.renderKpiProductPivotGrid(tableId);
        }

        const rows = this.getKpiAdvancedProcessedRows(tableId);
        const dataRows = rows.filter(row => !row.__owner_total__);
        if (!this.state.kpiAdvancedRenderedRows) {
            this.state.kpiAdvancedRenderedRows = {};
        }
        this.state.kpiAdvancedRenderedRows[tableId] = rows;
        const visibleColumns = this.getKpiAdvancedVisibleColumns(tableId);
        const ui = this.state.kpiAdvancedUi?.[tableId] || {};
        
        // --- Smart Footer Calculation ---
        const totals = {};
        const aggregationState = {};

        const summableKeys = [...new Set(visibleColumns.filter(col => col.summable).map(col => col.key))];
        const numeratorKeys = [...new Set(visibleColumns.filter(col => (col.aggregation === 'rate' || col.aggregation === 'rate_inverse') && col.numeratorKey).map(col => col.numeratorKey))];
        const denominatorKeys = [...new Set(visibleColumns.filter(col => (col.aggregation === 'rate' || col.aggregation === 'rate_inverse') && col.denominatorKey).map(col => col.denominatorKey))];
        const sumKeys = [...new Set(visibleColumns.filter(col => col.aggregation === 'average' && col.sumKey).map(col => col.sumKey))];
        const countKeys = [...new Set(visibleColumns.filter(col => col.aggregation === 'average' && col.countKey).map(col => col.countKey))];
        const allAggKeys = [...new Set([...summableKeys, ...numeratorKeys, ...denominatorKeys, ...sumKeys, ...countKeys])];

        dataRows.forEach((row) => {
            allAggKeys.forEach((key) => {
                aggregationState[key] = (aggregationState[key] || 0) + Number(row[key] || 0);
            });
        });

        // Compute Final Totals
        visibleColumns.forEach(col => {
            if (col.summable) {
                totals[col.key] = aggregationState[col.key] || 0;
            } else if (col.aggregation === 'rate') {
                const num = aggregationState[col.numeratorKey] || 0;
                const den = aggregationState[col.denominatorKey] || 0;
                totals[col.key] = den !== 0 ? (num / den) * 100 : 0;
            } else if (col.aggregation === 'rate_inverse') {
                const num = aggregationState[col.numeratorKey] || 0; // "Converted" count usually
                const den = aggregationState[col.denominatorKey] || 0; // "Total" count
                // rate_inverse usually means "Non-converted Rate" -> (Total - Converted) / Total
                totals[col.key] = den !== 0 ? ((den - num) / den) * 100 : 0;
            } else if (col.aggregation === 'average') {
                const sumVal = aggregationState[col.sumKey] || 0;
                const countVal = aggregationState[col.countKey] || 0;
                totals[col.key] = countVal !== 0 ? (sumVal / countVal) : 0;
            }
        });

        const hasRowTotal = !!definition.rowTotalUnit;
        const rowTotalColumns = this.getKpiAdvancedRowTotalColumns(definition, visibleColumns);
        const headerHtml = visibleColumns.map(col => {
            const sortIcon = ui.sortKey === col.key ? (ui.sortDir === 'asc' ? '▲' : '▼') : '';
            return `<th class="border border-gray-700 bg-gray-800 p-2 cursor-pointer select-none text-gray-300 hover:text-white transition-colors" onclick="app.setKpiAdvancedSort('${tableId}','${col.key}')">${col.label} <span class="text-xs opacity-70">${sortIcon}</span></th>`;
        }).join('');

        const rowTotalFor = (row) => {
            if (!hasRowTotal) return 0;
            return rowTotalColumns.reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
        };

        const bodyHtml = rows.map((row, idx) => {
            const isEven = idx % 2 === 0;
            const bgClass = row.__owner_total__ ? 'bg-indigo-900/20' : (isEven ? 'bg-gray-900/50' : 'bg-gray-900/30');
            const cells = visibleColumns.map(col => {
                const formatted = this.formatKpiAdvancedCell(row[col.key], col.type);
                const clickable = !row.__owner_total__ && !['date', 'text'].includes(col.type);
                if (!clickable) {
                    return `<td class="border border-gray-700/50 p-2 text-center text-gray-300">${formatted}</td>`;
                }
                return `<td class="border border-gray-700/50 p-2 text-center">
                    <button type="button" class="text-cyan-300 hover:text-cyan-200 hover:underline transition-colors" onclick="app.openKpiAdvancedDrilldown('${tableId}','${col.key}',${idx})">
                        ${formatted}
                    </button>
                </td>`;
            }).join('');
            const totalCell = hasRowTotal ? `<td class="border border-gray-700/50 p-2 font-bold text-center text-white bg-gray-800/20">${this.formatKpiAdvancedCell(rowTotalFor(row), definition.rowTotalUnit)}</td>` : '';
            return `<tr class="${bgClass} hover:bg-gray-800 transition-colors">${cells}${totalCell}</tr>`;
        }).join('');

        const footerCells = visibleColumns.map((col, index) => {
            if (index === 0) {
                return '<td class="border border-gray-700 p-2 font-bold text-white bg-gray-800">جمع کل</td>';
            }
            if (totals[col.key] !== undefined) {
                // Format logic
                const formatted = this.formatKpiAdvancedCell(totals[col.key], col.type);
                return `<td class="border border-gray-700 p-2 font-bold text-center text-white bg-gray-800" dir="ltr">${formatted}</td>`;
            }
            return '<td class="border border-gray-700 p-2 text-gray-600 text-center bg-gray-800">-</td>';
        }).join('');
        
        const footerTotalCell = hasRowTotal
            ? `<td class="border border-gray-700 p-2 font-bold text-center text-white bg-gray-800">${this.formatKpiAdvancedCell(dataRows.reduce((sum, row) => sum + rowTotalFor(row), 0), definition.rowTotalUnit)}</td>`
            : '';

        return `
            <div class="overflow-x-auto rounded-lg border border-gray-700 shadow-xl custom-scrollbar">
                <table class="w-full text-xs border-collapse min-w-[900px]">
                    <thead>
                        <tr>${headerHtml}${hasRowTotal ? '<th class="border border-gray-700 bg-gray-800 p-2 text-white">جمع ردیف</th>' : ''}</tr>
                    </thead>
                    <tbody>
                        ${bodyHtml || `<tr><td colspan="${visibleColumns.length + (hasRowTotal ? 1 : 0)}" class="p-6 text-center text-gray-500 italic">نتیجه‌ای برای این فیلتر وجود ندارد.</td></tr>`}
                    </tbody>
                    <tfoot class="sticky bottom-0 z-10 shadow-[0_-5px_15px_rgba(0,0,0,0.3)]">
                        <tr>${footerCells}${footerTotalCell}</tr>
                    </tfoot>
                </table>
            </div>
        `;
    },

    formatKpiAdvancedCell(value, type) {
        if (type === 'amount') return `${this.formatNumber(Number(value || 0))} تومان`;
        if (type === 'percent') {
            if (value === null || value === undefined || value === '') return '-';
            return `${Number(value || 0).toFixed(2)}%`;
        }
        if (type === 'day') return `${Number(value || 0).toFixed(2)} روز`;
        if (type === 'count') return this.formatNumber(Number(value || 0));
        if (type === 'date') return this.formatKpiDate(value);
        return this.escapeHtml(String(value ?? ''));
    },

    setKpiAdvancedSearch(tableId, value) {
        if (!this.state.kpiAdvancedUi?.[tableId]) return;
        this.state.kpiAdvancedUi[tableId].search = value || '';
        this.refreshKpiAdvancedPanel(tableId);
    },

    setKpiAdvancedSort(tableId, sortKey) {
        if (!this.state.kpiAdvancedUi?.[tableId]) return;
        const ui = this.state.kpiAdvancedUi[tableId];
        if (ui.sortKey === sortKey) {
            ui.sortDir = ui.sortDir === 'asc' ? 'desc' : 'asc';
        } else {
            ui.sortKey = sortKey;
            ui.sortDir = 'asc';
        }
        this.refreshKpiAdvancedPanel(tableId);
    },

    toggleKpiAdvancedColumn(tableId, colKey, checked) {
        if (!this.state.kpiAdvancedUi?.[tableId]) return;
        this.state.kpiAdvancedUi[tableId].visibleColumns[colKey] = !!checked;
        this.refreshKpiAdvancedPanel(tableId);
    },

    toggleKpiAdvancedHideZeroRows(tableId, checked) {
        if (!this.state.kpiAdvancedUi?.[tableId]) return;
        this.state.kpiAdvancedUi[tableId].hideZeroRows = !!checked;
        this.refreshKpiAdvancedPanel(tableId);
    },

    refreshKpiAdvancedPanel(tableId) {
        const panel = document.getElementById(`kpi-adv-panel-${tableId}`);
        if (!panel) return;
        panel.innerHTML = this.renderKpiAdvancedTablePanel(tableId);
    },

    exportKpiAdvancedTableCsv(tableId) {
        if (this.isKpiProductPivotTable(tableId)) {
            this.exportKpiProductPivotCsv(tableId);
            return;
        }
        const definition = this.getKpiAdvancedTableDefinition(tableId);
        if (!definition) return;
        const visibleColumns = this.getKpiAdvancedVisibleColumns(tableId);
        const rows = this.getKpiAdvancedProcessedRows(tableId);
        const hasRowTotal = !!definition.rowTotalUnit;
        const rowTotalColumns = this.getKpiAdvancedRowTotalColumns(definition, visibleColumns);

        const headers = visibleColumns.map(c => c.label);
        if (hasRowTotal) headers.push('جمع ردیف');

        const lines = [headers.join(',')];
        rows.forEach(row => {
            const cells = visibleColumns.map(col => this.csvEscape(this.formatKpiAdvancedCell(row[col.key], col.type)));
            if (hasRowTotal) {
                const rowTotal = rowTotalColumns.reduce((sum, c) => sum + Number(row[c.key] || 0), 0);
                cells.push(this.csvEscape(this.formatKpiAdvancedCell(rowTotal, definition.rowTotalUnit)));
            }
            lines.push(cells.join(','));
        });

        const csvText = "\uFEFF" + lines.join('\n');
        const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `kpi_${tableId}_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.csv`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    },

    exportKpiProductPivotCsv(tableId) {
        const model = this.buildKpiProductPivotModel(tableId);
        const products = model.products || [];
        const rows = model.rows || [];
        const isRequests = tableId === 'product_requests_by_owner_day';

        const headers = ['تاریخ', 'کارشناس'];
        products.forEach((p) => {
            headers.push(`${p} (ج)`);
            headers.push(`${p} (ق)`);
        });
        if (isRequests) {
            headers.push('کل درخواست');
            headers.push('فروش‌رفته');
            headers.push('فروش‌نرفته');
        }
        headers.push('جمع کل ردیف');

        const lines = [headers.map(v => this.csvEscape(v)).join(',')];
        rows.forEach((row) => {
            const cells = [
                this.csvEscape(this.formatKpiDate(row.date || '')),
                this.csvEscape(row.owner_name || '-')
            ];
            products.forEach((p) => {
                const pv = row.productValues?.[p] || { n: 0, o: 0, t: 0 };
                cells.push(this.csvEscape(this.formatNumber(Number(pv.n || 0))));
                cells.push(this.csvEscape(this.formatNumber(Number(pv.o || 0))));
            });
            if (isRequests) {
                cells.push(this.csvEscape(this.formatNumber(Number(row.requested_total_count || 0))));
                cells.push(this.csvEscape(this.formatNumber(Number(row.sold_total_count || 0))));
                cells.push(this.csvEscape(this.formatNumber(Number(row.unsold_total_count || 0))));
            }
            const rowSum = products.reduce((sum, p) => sum + Number(row.productValues?.[p]?.t || 0), 0);
            cells.push(this.csvEscape(this.formatNumber(Number(rowSum || 0))));
            lines.push(cells.join(','));
        });

        const csvText = "\uFEFF" + lines.join('\n');
        const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `kpi_${tableId}_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-')}.csv`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    },

    exportAllKpiAdvancedTables() {
        const tables = this.state.kpiAdvancedTables || {};
        const tableIds = Object.keys(tables || {});
        tableIds.forEach((tableId, index) => {
            setTimeout(() => this.exportKpiAdvancedTableCsv(tableId), index * 250);
        });
    },

    async openKpiAdvancedDrilldown(tableId, metricKey, rowIndex) {
        if (!this.canKpiFeatureClient('kpi.drilldown')) {
            Swal.fire('خطا', 'دسترسی Drilldown داشبورد KPI ندارید', 'error');
            return;
        }
        const rows = this.state.kpiAdvancedRenderedRows?.[tableId] || [];
        const row = rows?.[rowIndex];
        if (!row) return;
        this.state.kpiDrilldownContext = {
            mode: 'kpi',
            kind: 'kpi_advanced',
            tableId,
            metricKey,
            rowIndex
        };

        const definition = this.getKpiAdvancedTableDefinition(tableId);
        const col = definition?.columns?.find(c => c.key === metricKey);
        const metricLabel = col?.label || metricKey;
        const tableTitle = definition?.title || tableId;
        const rowDateText = row.date ? this.formatKpiDate(row.date) : 'همه تاریخ‌ها';
        const rowOwnerText = row.owner_name || 'همه کارشناسان';

        this.state.kpiDrilldown = {
            tableId,
            metricKey,
            row,
            data: null,
            loading: true
        };

        const subtitle = document.getElementById('kpi-drilldown-subtitle');
        if (subtitle) {
            subtitle.textContent = `${tableTitle} | ${metricLabel} | تاریخ: ${rowDateText} | کارشناس: ${rowOwnerText}`;
        }
        const container = document.getElementById('kpi-drilldown-content');
        if (container) {
            container.innerHTML = '<div class="text-sm text-gray-400">در حال بارگذاری داده‌های مرتبط...</div>';
        }
        this.openModal('modalKpiDrilldown');

        const filters = this.state.kpiFilters || {};
        const payload = {
            table_id: tableId,
            metric_key: metricKey,
            date_from: filters.date_from || '',
            date_to: filters.date_to || '',
            owner_didar_id: filters.owner_didar_id || '',
            product_id: filters.product_id || '',
            owner_didar_ids: Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.join(',') : '',
            product_ids: Array.isArray(filters.product_ids) ? filters.product_ids.join(',') : '',
            deal_statuses: Array.isArray(filters.deal_statuses) ? filters.deal_statuses.join(',') : '',
            deal_lead_types: Array.isArray(filters.deal_lead_types) ? filters.deal_lead_types.join(',') : '',
            row_date: row.date || '',
            row_owner_didar_id: row.owner_didar_id || '',
            row_owner_name: row.owner_name || '',
            row_product_name: row.product_name || '',
            row_failure_reason: row.failure_reason || '',
            row_activity_type: row.activity_type || ''
        };

        this.appendCriteriaToKpiPayload(payload, 'kpi');
        const res = await this.req('get_kpi_drilldown', payload);
        if (res.status !== 'success') {
            this.state.kpiDrilldown.loading = false;
            if (container) {
                container.innerHTML = `<div class="text-sm text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت جزئیات')}</div>`;
            }
            return;
        }

        this.state.kpiDrilldown.loading = false;
        this.state.kpiDrilldown.data = res;
        if (container) {
            container.innerHTML = this.renderKpiDrilldownContent(res);
        }
    },

    async openKpiAdvancedDrilldownWithProduct(tableId, metricKey, rowIndex, encodedProductName = '') {
        if (!this.canKpiFeatureClient('kpi.drilldown')) {
            Swal.fire('خطا', 'دسترسی Drilldown داشبورد KPI ندارید', 'error');
            return;
        }
        const rows = this.state.kpiAdvancedRenderedRows?.[tableId] || [];
        const row = rows?.[rowIndex];
        if (!row) return;
        const productName = decodeURIComponent(String(encodedProductName || ''));

        if (!this.state.kpiAdvancedRenderedRows) {
            this.state.kpiAdvancedRenderedRows = {};
        }
        this.state.kpiAdvancedRenderedRows[tableId] = rows.map((item, idx) => {
            if (idx !== rowIndex) return item;
            return { ...item, product_name: productName || item.product_name || '' };
        });

        await this.openKpiAdvancedDrilldown(tableId, metricKey, rowIndex);
    },

    async openLeadFromKpiDrilldown(contactId) {
        const safeContactId = String(contactId || '').trim();
        if (!safeContactId) return;
        this.closeAllCustomMultiSelectDropdowns();
        this.suspendKpiDrilldownForNestedModal();
        setTimeout(async () => {
            await this.openPersonProfile(safeContactId);
            if (document.getElementById('modalProfile')?.classList.contains('hidden')) {
                this.restoreSuspendedKpiDrilldown();
            }
        }, 50);
    },

    async openDealFromKpiDrilldown(dealId, initialTab = '') {
        const safeDealId = String(dealId || '').trim();
        if (!safeDealId) return;
        this.closeAllCustomMultiSelectDropdowns();
        this.suspendKpiDrilldownForNestedModal();
        setTimeout(async () => {
            await this.openDealModal(safeDealId, { initialTab: initialTab || 'info' });
            if (document.getElementById('modalDeal')?.classList.contains('hidden')) {
                this.restoreSuspendedKpiDrilldown();
            }
        }, 50);
    },

    flattenExportObject(input, prefix = '', output = {}) {
        const src = (input && typeof input === 'object') ? input : {};
        Object.keys(src).forEach((key) => {
            const value = src[key];
            const nextKey = prefix ? `${prefix}.${key}` : key;
            if (value === null || typeof value === 'undefined') {
                output[nextKey] = '';
                return;
            }
            if (Array.isArray(value)) {
                output[nextKey] = JSON.stringify(value);
                return;
            }
            if (typeof value === 'object') {
                this.flattenExportObject(value, nextKey, output);
                return;
            }
            output[nextKey] = value;
        });
        return output;
    },

    normalizeDrilldownExportDateFields(row) {
        const out = row && typeof row === 'object' ? { ...row } : {};
        Object.keys(out).forEach((key) => {
            const value = out[key];
            if (this.isExportEmptyValue(value)) return;
            if (typeof value !== 'string') return;
            const trimmed = value.trim();
            if (!trimmed) return;
            const lowerKey = key.toLowerCase();
            if (lowerKey.endsWith('_jalali') || lowerKey.endsWith('_persian')) return;

            const looksLikeDateTime = /^\d{4}-\d{2}-\d{2}(?:[ t]\d{2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
            const looksLikeDateKey = lowerKey.includes('date') || lowerKey.includes('time') || lowerKey.endsWith('_at');
            if (!looksLikeDateTime || !looksLikeDateKey) return;

            const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
            out[key] = isDateOnly ? (toPersianDate(trimmed) || trimmed) : (toPersianDateTime(trimmed) || trimmed);

            if (lowerKey === 'register_time' && !out.register_time_jalali) {
                out.register_time_jalali = toPersianDate(trimmed) || '';
            }
            if (lowerKey === 'payment_date' && !out.payment_date_persian) {
                out.payment_date_persian = toPersianDate(trimmed) || '';
            }
        });
        return out;
    },

    normalizeDrilldownExportRows(rows) {
        const src = Array.isArray(rows) ? rows : [];
        return src.map((item) => {
            const row = item && typeof item === 'object' ? { ...item } : {};
            Object.keys(row).forEach((key) => {
                if (this.isDrilldownExportFieldHidden(key)) {
                    delete row[key];
                }
            });
            if (Object.prototype.hasOwnProperty.call(row, 'acquaintance_source')) {
                row.acquaintance_source = this.getLookupLabel('lead_sources', row.acquaintance_source || '');
            }
            if (Object.prototype.hasOwnProperty.call(row, 'acquaintance_detail')) {
                row.acquaintance_detail = this.getLookupLabel('lead_source_details', row.acquaintance_detail || '');
            }
            if (Object.prototype.hasOwnProperty.call(row, 'content_topic')) {
                row.content_topic = this.getLookupLabel('content_topics', row.content_topic || '');
            }
            if (Object.prototype.hasOwnProperty.call(row, 'created_via')) {
                row.created_via = this.translateCreatedVia(row.created_via);
            }
            Object.keys(row).forEach((key) => {
                row[key] = this.translateDrilldownExportUiValue(key, row[key]);
            });
            return this.flattenExportObject(this.normalizeDrilldownExportDateFields(row));
        });
    },

    isDrilldownExportFieldHidden(field) {
        const normalized = String(field || '').replace(/^payment_\d+_/, '');
        return normalized === 'payment_source';
    },

    translateCreatedVia(value) {
        const raw = String(value || '').trim().toLowerCase();
        if (raw === 'manual') return 'دستی';
        if (raw === 'excel') return 'اکسل';
        if (raw === 'legacy') return 'قدیمی';
        return String(value || '');
    },

    translateDrilldownDealStatus(value) {
        const raw = String(value || '').trim().toLowerCase();
        if (raw === 'won') return 'موفق';
        if (raw === 'lost') return 'ناموفق';
        if (raw === 'pending') return 'جاری';
        return String(value || '');
    },

    translateDrilldownLeadSaleStatus(value) {
        const raw = String(value ?? '').trim().toLowerCase();
        if (raw === '1' || raw === 'success' || raw === 'successful' || raw === 'won') return 'موفق';
        if (raw === '0' || raw === 'failed' || raw === 'lost' || raw === 'fail') return 'ناموفق';
        return String(value || '');
    },

    translateDrilldownPipelineStage(value) {
        const raw = String(value || '').trim();
        const map = {
            'new': 'جدید',
            'contact_no_answer': 'تماس بی پاسخ',
            'contact_followup': 'تماس پیگیری',
            'deal_registered': 'ثبت معامله و لینک پرداخت',
            'payment': 'پرداخت',
            'payment_followup': 'پیگیری مانده حساب',
            'course_delivered': 'برگزاری دوره',
            'support': 'پشتیبانی',
            'with_purchase': 'با خرید',
            'without_purchase': 'بدون خرید',
            'refer_crm': 'ارجاع به تیم فروش',
        };
        return map[raw] || raw;
    },

    translateDrilldownPaymentMethod(value) {
        const raw = String(value || '').trim().toLowerCase();
        const map = {
            'card_to_card': 'کارت به کارت',
            'card_reader': 'کارت‌خوان',
            'sheba': 'شبا/پل',
            'shaba': 'شبا/پل',
            'sheba_pol': 'شبا/پل',
            'shaba_pol': 'شبا/پل',
            'cash': 'نقدی',
            'online': 'آنلاین',
            'installment': 'اقساطی',
            'check': 'چک',
            'other': 'سایر',
            'confirmed': 'تاییدشده',
            'pending': 'در انتظار',
            'failed': 'ناموفق'
        };
        return map[raw] || String(value || '');
    },

    translateDrilldownFailureReason(value) {
        const raw = String(value || '').trim();
        if (!raw) return '';
        const map = {
            sales_agent_underperformance: 'عدم توانمندی کارشناس فروش',
            customer_cancelled: 'انصراف مشتری',
            no_budget: 'نبود بودجه',
            duplicate: 'تکراری',
            wrong_number: 'شماره اشتباه',
            not_interested: 'عدم تمایل مشتری',
            competitor: 'انتخاب رقیب',
            no_response: 'عدم پاسخگویی',
            other: 'سایر'
        };
        return map[raw] || raw.replace(/_/g, ' ');
    },

    translateDrilldownExportUiValue(field, value) {
        if (this.isExportEmptyValue(value)) return '';
        const normalizedField = String(field || '').replace(/^payment_\d+_/, '');
        if (normalizedField === 'payment_source') return '';
        if (normalizedField === 'has_payment') {
            const raw = String(value ?? '').trim().toLowerCase();
            if (['1', 'true', 'yes', 'y'].includes(raw)) return 'بله';
            if (['0', 'false', 'no', 'n'].includes(raw)) return 'خیر';
            return value;
        }
        if (['status', 'deal_status', 'deal_status_group'].includes(normalizedField)) {
            return this.translateDrilldownDealStatus(value);
        }
        if (['sale_status', 'lead_sale_status'].includes(normalizedField)) {
            return this.translateDrilldownLeadSaleStatus(value);
        }
        if (['pipeline_stage', 'current_stage'].includes(normalizedField)) {
            return this.translateDrilldownPipelineStage(value);
        }
        if (normalizedField === 'failure_reason') {
            return this.translateDrilldownFailureReason(value);
        }
        if (['payment_method', 'payment_type', 'method'].includes(normalizedField)) {
            return this.translateDrilldownPaymentMethod(value);
        }
        if (normalizedField === 'payment_types') {
            return String(value || '')
                .split('|')
                .map((part) => this.translateDrilldownPaymentMethod(part.trim()))
                .filter(Boolean)
                .join(' | ');
        }
        if (['commission_status', 'finance_status', 'payment_status', 'cheque_status', 'cheque_collection_status', 'refund_status', 'payment_owner_source'].includes(normalizedField)) {
            return this.translateDrilldownExportCode(normalizedField, value);
        }
        return value;
    },

    translateDrilldownExportCode(field, value) {
        const rawValue = String(value ?? '').trim();
        if (!rawValue) return '';
        const raw = rawValue.toLowerCase();
        const key = String(field || '').replace(/^payment_\d+_/, '');
        const maps = {
            payment_source: {
                transaction: 'تراکنش مالی',
                transactions: 'تراکنش مالی',
                deal_json: 'تاریخچه پرداخت معامله',
                deals_json: 'تاریخچه پرداخت معامله',
                deal_json_fallback: 'تاریخچه پرداخت معامله (تکمیلی)',
                deal_payment: 'پرداخت معامله',
                manual: 'دستی',
                payment_records: 'دفتر پرداخت‌ها'
            },
            commission_status: {
                pending: 'در انتظار محاسبه',
                calculated: 'محاسبه شده',
                approved: 'تایید شده',
                ready_for_payout: 'آماده پرداخت',
                paid: 'پرداخت شده',
                refunded: 'عودت شده',
                clawback_pending: 'در انتظار اصلاح عودت',
                clawed_back: 'اصلاح عودت شده',
                not_eligible: 'غیرمشمول',
                void: 'باطل'
            },
            finance_status: {
                none: 'ثبت نشده',
                sepidar_pending: 'در انتظار ثبت سپیدار',
                sepidar_registered: 'ثبت شده در سپیدار'
            },
            payment_status: {
                confirmed: 'تاییدشده',
                pending: 'در انتظار',
                cancelled: 'لغوشده',
                canceled: 'لغوشده',
                refunded: 'عودت شده',
                failed: 'ناموفق',
                settled: 'تسویه شده',
                cleared: 'تسویه شده'
            },
            cheque_status: {
                not_cheque: 'غیر چکی',
                pending: 'در انتظار تایید',
                approved: 'تایید شده',
                rejected: 'رد شده'
            },
            cheque_collection_status: {
                not_cheque: 'غیر چکی',
                pending: 'در انتظار وصول',
                collected: 'وصول شده',
                returned: 'برگشتی',
                blataklif: 'بلاتکلیف'
            },
            refund_status: {
                none: 'بدون عودت',
                partial: 'عودت بخشی',
                full: 'عودت کامل'
            },
            payment_owner_source: {
                session: 'ثبت‌شده در لحظه پرداخت',
                deal_owner_backfill: 'بازسازی‌شده از مالک معامله',
                admin_override: 'اصلاح ادمین',
                unknown: 'نامشخص'
            }
        };
        const map = maps[key];
        return map?.[raw] || rawValue;
    },

    buildLeadLookupForExport(rows) {
        const src = Array.isArray(rows) ? rows : [];
        const map = {};
        src.forEach((item) => {
            const row = item && typeof item === 'object' ? item : {};
            const contactIds = [
                String(row.contact_id || '').trim(),
                String(row.contact_didar_id || '').trim(),
            ].filter(Boolean);
            if (!contactIds.length) return;
            const payload = {
                full_name: String(row.full_name || row.lead_name || row.contact_name || '').trim(),
                mobile_phone: String(row.mobile_phone || row.contact_mobile || '').trim(),
                secondary_mobile_phone: String(row.secondary_mobile_phone || '').trim(),
                customer_code: String(row.customer_code || '').trim(),
                sale_status: String(row.sale_status ?? row.lead_sale_status ?? '').trim(),
                lead_date: String(row.lead_date || row.lead_register_time || row.register_time || '').trim(),
                register_time_jalali: String(row.register_time_jalali || row.lead_register_time_jalali || '').trim(),
                acquaintance_source: String(row.acquaintance_source || row.lead_acquaintance_source || '').trim(),
                acquaintance_detail: String(row.acquaintance_detail || row.lead_acquaintance_detail || '').trim(),
            };
            contactIds.forEach((id) => {
                map[id] = payload;
            });
        });
        return map;
    },

    prepareDealRowsForExport(rows, leadLookup = {}) {
        const src = Array.isArray(rows) ? rows : [];
        return src.map((item) => {
            const row = item && typeof item === 'object' ? { ...item } : {};
            const paymentDetails = Array.isArray(row.payments_details) ? row.payments_details : [];
            const contactLookup = leadLookup[String(row.contact_id || row.contact_didar_id || '').trim()] || {};
            const leadName = String(
                row.contact_name
                || row.full_name
                || row.lead_name
                || contactLookup.full_name
                || paymentDetails.find((p) => p?.contact_name || p?.full_name || p?.lead_name)?.contact_name
                || ''
            ).trim();
            const leadMobile = String(
                row.contact_mobile
                || row.mobile_phone
                || contactLookup.mobile_phone
                || contactLookup.secondary_mobile_phone
                || paymentDetails.find((p) => p?.contact_mobile || p?.mobile_phone)?.contact_mobile
                || ''
            ).trim();
            const customerCode = String(
                row.customer_code
                || contactLookup.customer_code
                || paymentDetails.find((p) => p?.customer_code)?.customer_code
                || ''
            ).trim();
            const leadSaleStatusRaw = String(
                row.lead_sale_status
                ?? row.sale_status
                ?? contactLookup.sale_status
                ?? ''
            ).trim();
            const leadRegisterRaw = String(
                row.lead_register_time
                || row.lead_date
                || contactLookup.lead_date
                || ''
            ).trim();
            const leadRegisterJalali = String(
                row.lead_register_time_jalali
                || contactLookup.register_time_jalali
                || ''
            ).trim();
            const registerDateRaw = row.register_time || row.deal_date || '';
            row.contact_name = leadName;
            row.contact_mobile = leadMobile;
            row.customer_code = customerCode;
            row.lead_sale_status = this.translateDrilldownLeadSaleStatus(leadSaleStatusRaw);
            if (leadRegisterRaw || leadRegisterJalali) {
                row.lead_register_time_jalali = leadRegisterJalali || toPersianDate(leadRegisterRaw) || '';
            }
            delete row.lead_register_time;
            const leadAcquaintanceSource = String(row.lead_acquaintance_source || contactLookup.acquaintance_source || '').trim();
            const leadAcquaintanceDetail = String(row.lead_acquaintance_detail || contactLookup.acquaintance_detail || '').trim();
            row.invoice_code = row.invoice_code || paymentDetails.find((p) => p?.invoice_code)?.invoice_code || '';
            row.status = this.translateDrilldownDealStatus(row.status);
            row.deal_status_group = this.translateDrilldownDealStatus(row.deal_status_group);
            row.pipeline_stage = this.translateDrilldownPipelineStage(row.pipeline_stage);
            row.payment_method = this.translateDrilldownPaymentMethod(row.payment_method);
            row.payment_type = this.translateDrilldownPaymentMethod(row.payment_type);
            row.lead_acquaintance_source = this.getLookupLabel('lead_sources', leadAcquaintanceSource);
            row.lead_acquaintance_detail = this.getLookupLabel('lead_source_details', leadAcquaintanceDetail);
            row.acquaintance_source = this.getLookupLabel('lead_sources', row.acquaintance_source || '');
            row.acquaintance_detail = this.getLookupLabel('lead_source_details', row.acquaintance_detail || '');
            row.content_topic = this.getLookupLabel('content_topics', row.content_topic || '');
            row.created_via = this.translateCreatedVia(row.created_via || '');
            if (registerDateRaw) {
                row.register_time_jalali = toPersianDate(registerDateRaw) || row.register_time_jalali || '';
                row.register_time = toPersianDateTime(registerDateRaw) || row.register_time;
            }
            if (row.won_time) {
                row.won_time = toPersianDateTime(row.won_time) || row.won_time;
            }
            if (row.lost_time) {
                row.lost_time = toPersianDateTime(row.lost_time) || row.lost_time;
            }
            return row;
        });
    },

    preparePaymentRowsForExport(rows) {
        const src = Array.isArray(rows) ? rows : [];
        return src.map((item) => {
            const row = item && typeof item === 'object' ? { ...item } : {};
            const titleParts = this.splitDealTitleForDrilldownPaymentExport(row.deal_title || row.title || '');
            const leadName = String(
                row.contact_name || row.full_name || row.lead_name || row.deal_contact_name || ''
            ).trim();
            const leadMobile = String(
                row.contact_mobile || row.mobile_phone || titleParts.phone_number || ''
            ).trim();
            const customerCode = String(row.customer_code || '').trim();
            row.contact_name = leadName;
            row.contact_mobile = leadMobile;
            row.phone_number = row.phone_number || titleParts.phone_number || leadMobile;
            row.service_product = row.service_product || titleParts.service_product || row.products || '';
            row.customer_code = customerCode;
            row.invoice_code = row.invoice_code || '';
            row.deal_status = this.translateDrilldownDealStatus(row.deal_status);
            row.status = this.translateDrilldownDealStatus(row.status);
            row.pipeline_stage = this.translateDrilldownPipelineStage(row.pipeline_stage);
            row.payment_method = this.translateDrilldownPaymentMethod(row.payment_method || row.method);
            row.payment_type = this.translateDrilldownPaymentMethod(row.payment_type);
            row.method = this.translateDrilldownPaymentMethod(row.method || row.payment_method);
            row.payment_status = this.translateDrilldownPaymentMethod(row.payment_status);

            if (row.payment_date) {
                row.payment_date_persian = row.payment_date_persian || toPersianDate(row.payment_date) || '';
                row.payment_date = toPersianDateTime(row.payment_date) || row.payment_date;
            }
            if (row.check_date) {
                row.check_date = this.normalizeCheckDateForExport(row.check_date);
            }
            if (row.created_at) {
                row.created_at = toPersianDateTime(row.created_at) || row.created_at;
            }
            return row;
        });
    },

    buildDealPaymentDetailsMap(paymentsRaw) {
        const map = {};
        const rows = Array.isArray(paymentsRaw) ? paymentsRaw : [];
        const paymentTypeKeys = ['payment_type', 'payment_method', 'method', 'type', 'payment_kind'];
        const addForKey = (key, payment) => {
            const normalizedKey = String(key || '').trim();
            if (!normalizedKey) return;
            if (!map[normalizedKey]) map[normalizedKey] = [];
            map[normalizedKey].push(payment);
        };
        rows.forEach((payment) => {
            const row = payment && typeof payment === 'object' ? payment : {};
            const dealId = String(row.deal_id || '').trim();
            const didarDealId = String(row.didar_deal_id || '').trim();
            addForKey(`deal_id:${dealId}`, row);
            addForKey(`didar_deal_id:${didarDealId}`, row);
        });
        Object.keys(map).forEach((key) => {
            map[key].sort((a, b) => String(a.payment_date || '').localeCompare(String(b.payment_date || '')));
        });
        const summarizeTypes = (list) => {
            const out = new Set();
            list.forEach((p) => {
                paymentTypeKeys.forEach((typeKey) => {
                    const value = String(p?.[typeKey] || '').trim();
                    if (value) out.add(value);
                });
            });
            return Array.from(out).join(' | ');
        };
        return {
            raw: map,
            summarizeTypes
        };
    },

    enrichDealRowsWithPaymentDetails(dealsRaw, paymentsRaw) {
        const dealRows = Array.isArray(dealsRaw) ? dealsRaw : [];
        if (!dealRows.length) return [];
        const paymentMap = this.buildDealPaymentDetailsMap(paymentsRaw);
        return dealRows.map((deal) => {
            const row = deal && typeof deal === 'object' ? { ...deal } : {};
            const dealId = String(row.deal_id || '').trim();
            const didarDealId = String(row.didar_deal_id || '').trim();
            const byDealId = paymentMap.raw[`deal_id:${dealId}`] || [];
            const byDidarId = paymentMap.raw[`didar_deal_id:${didarDealId}`] || [];
            const merged = [];
            const seen = new Set();
            [...byDealId, ...byDidarId].forEach((p) => {
                const fingerprint = JSON.stringify(p || {});
                if (seen.has(fingerprint)) return;
                seen.add(fingerprint);
                merged.push(p);
            });
            const activePayments = merged.filter((p) => !this.isDeletedPayment(p));
            const attachedPaymentTotal = activePayments.reduce((sum, p) => sum + this.getPaymentNetAmount(p), 0);
            const payableAmount = Number(row.payable_amount || 0);
            const hasServerPaid = !this.isExportEmptyValue(row.total_paid) || !this.isExportEmptyValue(row.payment_amount);
            const serverPaid = Number(!this.isExportEmptyValue(row.total_paid) ? row.total_paid : row.payment_amount);
            const effectivePaid = hasServerPaid && Number.isFinite(serverPaid) ? serverPaid : attachedPaymentTotal;
            const hasServerOutstanding = !this.isExportEmptyValue(row.outstanding_balance) || !this.isExportEmptyValue(row.outstanding_amount);
            const serverOutstanding = Number(!this.isExportEmptyValue(row.outstanding_balance) ? row.outstanding_balance : row.outstanding_amount);
            row.payments_count = activePayments.length;
            row.payments_total_amount = effectivePaid;
            row.payment_amount = !this.isExportEmptyValue(row.payment_amount) ? row.payment_amount : effectivePaid;
            row.total_paid = !this.isExportEmptyValue(row.total_paid) ? row.total_paid : effectivePaid;
            row.outstanding_balance = hasServerOutstanding && Number.isFinite(serverOutstanding)
                ? Math.max(serverOutstanding, 0)
                : Math.max(payableAmount - effectivePaid, 0);
            row.outstanding_amount = !this.isExportEmptyValue(row.outstanding_amount) ? row.outstanding_amount : row.outstanding_balance;
            row.payment_types = paymentMap.summarizeTypes(merged);
            row.payments_summary = activePayments.map((payment, index) => {
                const amount = this.formatNumber(this.getPaymentNetAmount(payment));
                const date = payment?.payment_date || payment?.payment_record_payment_date || payment?.date || payment?.effective_date || '';
                const method = this.translateDrilldownPaymentMethod(payment?.payment_method || payment?.method || payment?.payment_type || '');
                const owner = String(payment?.payment_owner_name || payment?.registered_by_name || payment?.owner_name || '').trim();
                const desc = String(payment?.description || '').trim();
                return [
                    `${index + 1})`,
                    date ? (toPersianDate(date) || String(date)) : '',
                    amount ? `${amount} تومان` : '',
                    method,
                    owner ? `ثبت‌کننده/مالک: ${owner}` : '',
                    desc ? `توضیح: ${desc}` : ''
                ].filter(Boolean).join(' | ');
            }).join(' ؛ ');
            row.payments_details = merged;
            row.payments_details_json = JSON.stringify(merged);
            return row;
        });
    },

    buildExpandedDealPaymentColumnsForExport(dealRows) {
        const rows = Array.isArray(dealRows) ? dealRows : [];
        if (!rows.length) return [];
        const paymentFields = [
            'payment_sequence',
            'payment_date', 'effective_date', 'registered_at',
            'amount', 'gross_amount', 'refund_amount', 'refund_status',
            'payment_method', 'payment_status', 'commission_status', 'finance_status',
            'payment_owner_name', 'registered_by_name',
            'depositor_name', 'account_owner_name',
            'card_last4', 'tracking_code_last4',
            'destination_bank_title', 'destination_bank', 'destination_card_number', 'destination_account_number', 'destination_sheba', 'destination_tafsili_code',
            'cheque_status', 'cheque_collection_status', 'check_date', 'check_due_date', 'check_number', 'check_bank_name', 'check_owner_name',
            'receipt_image_path', 'check_image_path',
            'description',
            'payment_record_id', 'transaction_id', 'payment_uuid', 'source_hash'
        ];
        const maxPayments = rows.reduce((max, row) => {
            const count = Array.isArray(row?.payments_details) ? row.payments_details.length : 0;
            return Math.max(max, count);
        }, 0);
        return rows.map((row) => {
            const out = { ...(row || {}) };
            const details = Array.isArray(out.payments_details) ? out.payments_details : [];
            for (let index = 0; index < maxPayments; index++) {
                const p = details[index] && typeof details[index] === 'object' ? details[index] : {};
                paymentFields.forEach((field) => {
                    let value = (p[field] ?? '');
                    if (field === 'payment_method' || field === 'payment_type' || field === 'method') {
                        value = this.translateDrilldownPaymentMethod(value);
                    } else if (['commission_status', 'finance_status', 'payment_status', 'cheque_status', 'cheque_collection_status', 'refund_status', 'payment_owner_source'].includes(field)) {
                        value = this.translateDrilldownExportCode(field, value);
                    } else if (field.includes('date') || field.endsWith('_at')) {
                        value = value ? (toPersianDateTime(value) || value) : value;
                    }
                    out[`payment_${index + 1}_${field}`] = value;
                });
            }
            delete out.payments_details;
            delete out.payments_details_json;
            return out;
        });
    },

    getDrilldownFieldLabel(datasetKey, fieldKey) {
        const key = String(fieldKey || '');
        const maps = {
            common: {
                title: 'عنوان',
                table_id: 'شناسه جدول',
                metric_key: 'کلید شاخص',
                drilldown_kind: 'نوع Drill-down',
                generated_at: 'زمان تولید',
                amount: 'مبلغ',
                payment_date: 'تاریخ پرداخت (شمسی)',
                payment_date_persian: 'تاریخ پرداخت (شمسی)',
                payment_record_payment_date: 'تاریخ پرداخت در رکورد',
                deposit_date: 'تاریخ واریز',
                effective_date: 'تاریخ موثر',
                registered_date: 'تاریخ ثبت پرداخت',
                registered_at: 'زمان ثبت پرداخت',
                sepidar_registered_at: 'زمان ثبت در سپیدار',
                payment_status: 'وضعیت پرداخت',
                commission_status: 'وضعیت پورسانت پرداخت',
                finance_status: 'وضعیت مالی/سپیدار',
                payment_method: 'روش پرداخت',
                method: 'روش پرداخت',
                payment_type: 'نوع پرداخت',
                payment_record_id: 'شناسه رکورد پرداخت',
                payment_uuid: 'شناسه یکتای فنی پرداخت',
                source_hash: 'هش فنی رکورد پرداخت',
                payment_sequence: 'شماره پرداخت',
                gross_amount: 'مبلغ ناخالص پرداخت',
                refunded_amount: 'مبلغ عودت‌شده',
                refund_amount: 'مبلغ عودت وجه',
                refund_count: 'تعداد عودت',
                refund_status: 'وضعیت عودت',
                refund_description: 'توضیحات عودت وجه',
                payment_owner_name: 'کارشناس مالک پرداخت',
                payment_owner_didar_id: 'شناسه CRM خارجی مالک پرداخت',
                payment_owner_user_id: 'شناسه کاربر مالک پرداخت',
                payment_owner_source: 'منبع مالک پرداخت',
                registered_by_name: 'ثبت‌کننده پرداخت',
                registered_by_didar_id: 'شناسه ثبت‌کننده پرداخت',
                phone_number: 'شماره تماس',
                service_product: 'خدمت/محصول',
                products: 'محصولات معامله',
                card_last4: '۴ رقم آخر کارت',
                destination_bank: 'بانک مقصد',
                destination_bank_title: 'عنوان بانک مقصد',
                destination_card_number: 'شماره کارت مقصد',
                destination_card_full: 'شماره کامل کارت مقصد',
                destination_account_number: 'شماره حساب مقصد',
                destination_sheba: 'شبا مقصد',
                destination_tafsili_code: 'کد تفصیلی مقصد',
                destination_account_owner: 'صاحب حساب مقصد',
                account_owner_name: 'نام صاحب حساب',
                depositor_name: 'نام واریزکننده',
                tracking_code_last4: '۴ رقم آخر کد رهگیری',
                card_reader_device: 'دستگاه کارتخوان',
                currency: 'نوع ارز',
                currency_other: 'ارز (سایر)',
                currency_amount: 'مبلغ ارزی',
                cheque_status: 'وضعیت تایید چک',
                cheque_collection_status: 'وضعیت وصول چک',
                cheque_due_date: 'تاریخ سررسید چک',
                cheque_collection_date: 'تاریخ وصول چک',
                check_bank_name: 'نام بانک چک',
                check_date: 'تاریخ چک (شمسی)',
                check_due_date: 'تاریخ سررسید چک',
                check_number: 'شماره چک',
                check_owner_name: 'نام صاحب چک',
                sayadi_date: 'تاریخ صیادی',
                check_delivery_date: 'تاریخ تحویل چک',
                check_recipient_name: 'تحویل‌گیرنده چک',
                receipt_image_path: 'مسیر تصویر رسید',
                check_image_path: 'مسیر تصویر چک',
                description: 'توضیحات',
                created_at: 'زمان ثبت (شمسی)',
                invoice_code: 'کد فاکتور/حسابداری',
                owner_name: 'کارشناس',
                owner_didar_id: 'شناسه کارشناس',
                didar_deal_id: 'شناسه CRM خارجی معامله',
                deal_id: 'شناسه داخلی معامله',
                deal_title: 'عنوان معامله',
                deal_date: 'تاریخ معامله',
                status: 'وضعیت',
                deal_status_group: 'گروه وضعیت معامله',
                pipeline_stage: 'مرحله کاریز',
                failure_reason: 'دلیل ناموفق/عدم موفقیت',
                register_time_jalali: 'تاریخ ثبت',
                register_time: 'زمان ثبت (شمسی)',
                won_time: 'زمان موفق (شمسی)',
                lost_time: 'زمان ناموفق (شمسی)',
                payable_amount: 'مبلغ قرارداد',
                outstanding_balance: 'مانده حساب',
                total_paid: 'جمع پرداخت‌های معامله',
                has_payment: 'دارای پرداخت',
                contact_didar_id: 'شناسه لید',
                contact_id: 'شناسه لید',
                full_name: 'نام لید',
                lead_name: 'نام لید',
                contact_name: 'نام لید',
                lead_date: 'تاریخ لید',
                lead_register_time_jalali: 'تاریخ ثبت لید',
                lead_sale_status: 'وضعیت فروش',
                sale_status: 'وضعیت فروش',
                mobile_phone: 'موبایل',
                contact_mobile: 'موبایل لید',
                customer_code: 'کد مشتری',
                secondary_mobile_phone: 'موبایل دوم',
                activity_date: 'تاریخ فعالیت',
                activity_type: 'نوع فعالیت',
                activity_duration_minutes: 'مدت فعالیت (دقیقه)',
                payments_count: 'تعداد پرداخت‌های معامله',
                payments_total_amount: 'جمع مبلغ پرداخت‌های معامله',
                payment_types: 'روش‌های پرداخت معامله',
                payments_summary: 'خلاصه خوانای پرداخت‌ها',
                total_leads: 'تعداد لید',
                total_deals: 'تعداد معامله',
                acquaintance_source: 'شیوه آشنایی',
                acquaintance_detail: 'جزئیات شیوه آشنایی',
                lead_acquaintance_source: 'شیوه آشنایی لید',
                lead_acquaintance_detail: 'جزئیات شیوه آشنایی لید',
                deal_acquaintance_source: 'شیوه آشنایی معامله',
                deal_acquaintance_detail: 'جزئیات شیوه آشنایی معامله',
                content_topic: 'موضوع محتوا',
                created_by_name: 'ثبت شده توسط',
                created_via: 'روش ثبت',
                deal_count: 'تعداد معاملات',
                won_deal_count: 'تعداد معاملات موفق',
                lost_deal_count: 'تعداد معاملات ناموفق',
                current_deal_count: 'تعداد معاملات جاری',
                total_activities: 'تعداد فعالیت',
                total_payments: 'تعداد پرداخت'
            },
            leads: {},
            deals: {
                acquaintance_source: 'شیوه آشنایی معامله',
                acquaintance_detail: 'جزئیات شیوه آشنایی معامله',
                status: 'وضعیت معامله',
                deal_status_group: 'گروه وضعیت معامله',
                title: 'عنوان معامله',
                contact_name: 'نام لید',
                contact_mobile: 'موبایل لید',
            },
            payments: {
                status: 'وضعیت',
                payment_status: 'وضعیت پرداخت',
                outstanding_balance: 'مانده حساب معامله (زمینه)',
            },
            activities: {}
        };

        const paymentColMatch = key.match(/^payment_(\d+)_(.+)$/);
        if (paymentColMatch) {
            const idx = Number(paymentColMatch[1] || 0);
            const sub = paymentColMatch[2] || '';
            const subFa = maps.common[sub] || sub;
            return `پرداخت ${idx} - ${subFa}`;
        }

        const byDataset = maps[datasetKey] || {};
        return byDataset[key] || maps.common[key] || key;
    },

    buildDrilldownExportDatasets(data) {
        const meta = data?.meta || {};
        const conversion = data?.conversion_analysis || null;
        const datasets = {};
        const leadsRaw = Array.isArray(data?.leads) ? data.leads : [];
        const dealsRaw = Array.isArray(data?.deals) ? data.deals : [];
        const paymentsRaw = Array.isArray(data?.payments) ? data.payments : [];
        const activitiesRaw = Array.isArray(data?.activities) ? data.activities : [];
        const leadLookup = this.buildLeadLookupForExport(leadsRaw);
        const enrichedDealsRaw = this.enrichDealRowsWithPaymentDetails(dealsRaw, paymentsRaw);
        const preparedDealsRaw = this.prepareDealRowsForExport(enrichedDealsRaw, leadLookup);
        const expandedDealsRaw = this.buildExpandedDealPaymentColumnsForExport(preparedDealsRaw);
        const paymentDealLookup = {};
        preparedDealsRaw.forEach((row) => {
            const dealId = String(row?.deal_id || '').trim();
            const didarDealId = String(row?.didar_deal_id || '').trim();
            if (dealId) paymentDealLookup[`deal_id:${dealId}`] = row;
            if (didarDealId) paymentDealLookup[`didar_deal_id:${didarDealId}`] = row;
        });
        const enrichedPaymentsRaw = this.preparePaymentRowsForExport(paymentsRaw.map((payment) => {
            const row = payment && typeof payment === 'object' ? { ...payment } : {};
            const dealId = String(row.deal_id || '').trim();
            const didarDealId = String(row.didar_deal_id || '').trim();
            const dealRow = paymentDealLookup[`deal_id:${dealId}`] || paymentDealLookup[`didar_deal_id:${didarDealId}`] || null;
            if (dealRow) {
                const setFromDeal = (targetKey, dealKey = targetKey) => {
                    if (!this.isExportEmptyValue(row[targetKey])) return;
                    if (this.isExportEmptyValue(dealRow?.[dealKey])) return;
                    row[targetKey] = dealRow[dealKey];
                };
                const payableAmount = Number(dealRow.payable_amount || 0);
                const totalPaid = Number(dealRow.payments_total_amount || dealRow.total_paid || 0);
                row.outstanding_balance = Math.max(payableAmount - totalPaid, 0);
                row.contact_name = row.contact_name || dealRow.contact_name || dealRow.full_name || dealRow.lead_name || '';
                row.contact_mobile = row.contact_mobile || dealRow.contact_mobile || dealRow.mobile_phone || '';
                row.customer_code = row.customer_code || dealRow.customer_code || '';
                row.deal_title = row.deal_title || dealRow.deal_title || dealRow.title || '';
                row.products = row.products || dealRow.products || '';
                setFromDeal('payable_amount');
                setFromDeal('total_paid');
                setFromDeal('payments_count');
                setFromDeal('payments_total_amount');
                setFromDeal('payment_types');
                setFromDeal('payments_summary');
                setFromDeal('deal_date');
                setFromDeal('register_time_jalali');
                setFromDeal('register_time');
                setFromDeal('won_time');
                setFromDeal('lost_time');
                setFromDeal('deal_status_group');
                setFromDeal('status');
                setFromDeal('pipeline_stage');
                setFromDeal('failure_reason');
                setFromDeal('owner_didar_id');
                setFromDeal('lead_sale_status');
                setFromDeal('lead_register_time_jalali');
                setFromDeal('lead_acquaintance_source');
                setFromDeal('lead_acquaintance_detail');
                setFromDeal('acquaintance_source');
                setFromDeal('acquaintance_detail');
                setFromDeal('content_topic');
                setFromDeal('created_by_name');
                setFromDeal('created_via');
                const titleParts = this.splitDealTitleForDrilldownPaymentExport(row.deal_title);
                row.phone_number = row.phone_number || titleParts.phone_number || row.contact_mobile || '';
                row.service_product = row.service_product || titleParts.service_product || row.products || '';
            } else if (row.outstanding_balance == null) {
                row.outstanding_balance = '';
            }
            row.status = this.ddPaymentSepidarStatusLabel(row);
            return row;
        }));

        datasets.card_summary = {
            label: 'خلاصه کارت و متا',
            rows: this.normalizeDrilldownExportRows([{
                title: meta.title || '',
                table_id: meta.table_id || this.state.kpiDrilldownContext?.tableId || '',
                metric_key: meta.metric_key || this.state.kpiDrilldownContext?.metricKey || '',
                drilldown_kind: meta.drilldown_kind || '',
                total_leads: Number(meta.total_leads || 0),
                total_deals: Number(meta.total_deals || 0),
                total_activities: Number(meta.total_activities || 0),
                total_payments: Number(meta.total_payments || 0),
                generated_at: new Date().toISOString()
            }])
        };

        datasets.leads = { label: 'لیدها', rows: this.normalizeDrilldownExportRows(leadsRaw) };
        datasets.deals = { label: 'معاملات (با جزئیات پرداخت)', rows: this.normalizeDrilldownExportRows(expandedDealsRaw) };
        datasets.payments = { label: 'پرداخت‌ها', rows: this.normalizeDrilldownExportRows(enrichedPaymentsRaw) };
        datasets.activities = { label: 'فعالیت‌ها', rows: this.normalizeDrilldownExportRows(activitiesRaw) };

        if (conversion && typeof conversion === 'object') {
            const summaryRows = this.normalizeDrilldownExportRows([{
                analysis_type: conversion.type || '',
                formula_title: conversion.formula_title || '',
                formula_text: conversion.formula_text || '',
                ...(conversion.summary || {})
            }]);
            if (summaryRows.length > 0) {
                datasets.conversion_summary = { label: 'خلاصه تحلیل نرخ تبدیل', rows: summaryRows };
            }
            const conversionMap = [
                ['included_deals', 'معاملات محاسبه‌شده نرخ تبدیل'],
                ['excluded_deals', 'معاملات حذف‌شده نرخ تبدیل'],
                ['included_leads', 'لیدهای محاسبه‌شده نرخ تبدیل'],
                ['excluded_leads', 'لیدهای حذف‌شده نرخ تبدیل'],
                ['excluded_reasons_summary', 'دلایل حذف/عدم تبدیل']
            ];
            conversionMap.forEach(([key, label]) => {
                const rows = this.normalizeDrilldownExportRows(conversion[key]);
                if (rows.length > 0) {
                    datasets[key] = { label, rows };
                }
            });
        }

        return datasets;
    },

    getDrilldownExportDatasetEntries() {
        const datasets = this.state.kpiDrilldownExport?.datasets || {};
        return Object.entries(datasets).filter(([, item]) => item && Array.isArray(item.rows));
    },

    getPreferredDrilldownExportFieldOrder(datasetKey) {
        const common = [
            'register_time_jalali', 'register_time', 'payment_date_persian', 'payment_date', 'payment_record_payment_date', 'deposit_date', 'effective_date', 'registered_date', 'registered_at', 'check_date', 'check_due_date', 'cheque_due_date', 'cheque_collection_date', 'created_at',
            'contact_name', 'customer_code', 'full_name', 'lead_name', 'lead_sale_status', 'lead_register_time_jalali', 'lead_date', 'contact_mobile', 'mobile_phone', 'secondary_mobile_phone',
            'deal_id', 'didar_deal_id', 'deal_title', 'title', 'invoice_code',
            'owner_name', 'owner_didar_id',
            'status', 'deal_status_group', 'pipeline_stage', 'activity_type',
            'service_cost', 'payable_amount', 'payment_amount', 'outstanding_balance', 'amount', 'gross_amount', 'refunded_amount', 'refund_amount', 'total_paid',
            'payments_count', 'payments_total_amount', 'payment_types', 'payment_record_id', 'payment_uuid', 'source_hash', 'transaction_id', 'payment_sequence',
            'payment_status', 'commission_status', 'finance_status', 'payment_method', 'method', 'payment_type',
            'payment_owner_name', 'payment_owner_didar_id', 'payment_owner_user_id', 'payment_owner_source', 'registered_by_name', 'registered_by_didar_id',
            'card_last4', 'destination_bank', 'destination_bank_title', 'destination_card_number', 'destination_card_full', 'destination_account_number', 'destination_sheba', 'destination_tafsili_code', 'destination_account_owner',
            'account_owner_name', 'depositor_name', 'tracking_code_last4', 'card_reader_device', 'currency', 'currency_other', 'currency_amount',
            'cheque_status', 'cheque_collection_status', 'check_bank_name', 'check_number', 'check_owner_name', 'sayadi_date', 'check_delivery_date', 'check_recipient_name',
            'receipt_image_path', 'check_image_path', 'refund_count', 'refund_status', 'refund_description', 'description',
        ];
        const datasetSpecific = {
            deals: [
                'contact_name', 'customer_code', 'contact_mobile', 'lead_sale_status', 'lead_register_time_jalali',
                'title', 'deal_id', 'didar_deal_id', 'invoice_code', 'register_time_jalali', 'register_time',
                'owner_name', 'status', 'deal_status_group', 'pipeline_stage', 'failure_reason',
                'products', 'service_cost', 'payable_amount', 'payment_amount', 'total_paid', 'outstanding_balance',
                'payments_count', 'payments_total_amount', 'payment_types', 'payments_summary',
                'lead_acquaintance_source', 'lead_acquaintance_detail', 'acquaintance_source', 'acquaintance_detail',
                'content_topic', 'created_by_name', 'created_via', 'contact_didar_id', 'owner_didar_id'
            ],
            payments: [
                'payment_date_persian', 'payment_date', 'payment_sequence',
                'contact_name', 'phone_number', 'service_product', 'customer_code', 'contact_mobile',
                'lead_sale_status', 'lead_register_time_jalali', 'lead_acquaintance_source', 'lead_acquaintance_detail',
                'deal_id', 'didar_deal_id', 'deal_title', 'invoice_code', 'deal_date', 'register_time_jalali',
                'owner_name', 'owner_didar_id', 'payment_owner_name', 'registered_by_name',
                'status', 'deal_status_group', 'pipeline_stage', 'failure_reason',
                'payable_amount', 'total_paid', 'payments_count', 'payments_total_amount', 'payment_types', 'payments_summary',
                'amount', 'gross_amount', 'refunded_amount', 'refund_amount', 'outstanding_balance',
                'payment_status', 'commission_status', 'finance_status', 'payment_method', 'payment_type'
            ],
            activities: ['activity_date', 'contact_name', 'customer_code', 'contact_mobile', 'deal_id', 'deal_title', 'owner_name', 'activity_type', 'activity_duration_minutes'],
            leads: ['register_time_jalali', 'register_time', 'contact_name', 'customer_code', 'contact_mobile', 'owner_name', 'acquaintance_source', 'acquaintance_detail', 'content_topic', 'created_by_name', 'created_via', 'deal_count', 'won_deal_count', 'lost_deal_count', 'current_deal_count'],
        };
        return [...(datasetSpecific[datasetKey] || []), ...common];
    },

    sortDrilldownExportFields(datasetKey, fields) {
        const preferred = this.getPreferredDrilldownExportFieldOrder(datasetKey);
        const rank = new Map(preferred.map((key, index) => [key, index]));
        return [...fields].sort((left, right) => {
            const l = rank.has(left) ? rank.get(left) : 10000;
            const r = rank.has(right) ? rank.get(right) : 10000;
            if (l !== r) return l - r;
            const leftPayment = left.match(/^payment_(\d+)_(.+)$/);
            const rightPayment = right.match(/^payment_(\d+)_(.+)$/);
            if (leftPayment && rightPayment) {
                const idxDiff = Number(leftPayment[1]) - Number(rightPayment[1]);
                if (idxDiff !== 0) return idxDiff;
                const subRank = new Map(this.getPreferredDrilldownExportFieldOrder(datasetKey).map((key, index) => [key, index]));
                const sl = subRank.has(leftPayment[2]) ? subRank.get(leftPayment[2]) : 10000;
                const sr = subRank.has(rightPayment[2]) ? subRank.get(rightPayment[2]) : 10000;
                if (sl !== sr) return sl - sr;
            }
            return left.localeCompare(right, 'fa');
        });
    },

    buildDrilldownExportSummaryRow(datasetKey, fields, rows) {
        const src = Array.isArray(rows) ? rows : [];
        if (!src.length) return null;
        const numericFields = new Set(['service_cost', 'payable_amount', 'payment_amount', 'outstanding_balance', 'amount', 'gross_amount', 'refunded_amount', 'refund_amount', 'currency_amount', 'total_paid', 'payments_count', 'payments_total_amount', 'activity_duration_minutes']);
        const countLikeFields = new Set(['deal_id', 'didar_deal_id', 'transaction_id', 'contact_name', 'contact_mobile']);
        const summary = {};
        const totals = {};
        fields.forEach((field, index) => {
            if (index === 0) {
                summary[field] = 'جمع / تحلیل';
                return;
            }
            if (datasetKey === 'payments' && field === 'outstanding_balance') {
                totals[field] = this.sumUniqueDrilldownPaymentDealOutstanding(src);
                summary[field] = '';
                return;
            }
            if (numericFields.has(field) || /^payment_\d+_(amount|currency_amount)$/.test(field)) {
                const total = src.reduce((sum, row) => sum + Number(row?.[field] || 0), 0);
                summary[field] = total ? total : '';
                totals[field] = total;
                return;
            }
            if (countLikeFields.has(field)) {
                const count = src.reduce((sum, row) => sum + (this.isExportEmptyValue(row?.[field]) ? 0 : 1), 0);
                summary[field] = count ? `تعداد: ${count}` : '';
            }
        });

        const notes = [];
        if (datasetKey === 'deals') {
            const count = src.length;
            const payable = Number(totals.payable_amount || 0);
            const paid = Number(totals.payment_amount || totals.total_paid || 0);
            const outstanding = Number(totals.outstanding_balance || 0);
            notes.push(`تعداد معاملات: ${count}`);
            if (count > 0) notes.push(`میانگین مبلغ قرارداد: ${Math.round((payable / count) * 100) / 100}`);
            if (payable > 0) notes.push(`نسبت وصول: ${Math.round((paid / payable) * 10000) / 100}%`);
            notes.push(`جمع مانده حساب: ${Math.round(outstanding * 100) / 100}`);
        } else if (datasetKey === 'payments') {
            const count = src.length;
            const amount = Number(totals.amount || 0);
            const outstanding = Number(totals.outstanding_balance || 0);
            notes.push(`تعداد پرداخت: ${count}`);
            if (count > 0) notes.push(`میانگین مبلغ پرداخت: ${Math.round((amount / count) * 100) / 100}`);
            notes.push(`جمع مبالغ پرداخت: ${Math.round(amount * 100) / 100}`);
            notes.push(`مانده حساب یکتای معاملات مرتبط: ${Math.round(outstanding * 100) / 100}`);
        } else if (datasetKey === 'activities') {
            const count = src.length;
            const duration = Number(totals.activity_duration_minutes || 0);
            const avgDuration = count > 0 ? (duration / count) : 0;
            const callCount = src.reduce((sum, row) => {
                const type = String(row?.activity_type || row?.activity_type_title || '').trim();
                return sum + (type.includes('تماس') ? 1 : 0);
            }, 0);
            const supportCount = src.reduce((sum, row) => {
                const type = String(row?.activity_type || row?.activity_type_title || '').trim();
                return sum + (type.includes('پشتیبانی') ? 1 : 0);
            }, 0);
            const courseCount = src.reduce((sum, row) => {
                const type = String(row?.activity_type || row?.activity_type_title || '').trim();
                return sum + ((type.includes('دوره') || type.includes('برگزاری')) ? 1 : 0);
            }, 0);
            notes.push(`تعداد فعالیت: ${count}`);
            notes.push(`مجموع مدت: ${Math.round(duration * 100) / 100} دقیقه`);
            notes.push(`میانگین مدت: ${Math.round(avgDuration * 100) / 100} دقیقه`);
            notes.push(`تعداد تماس: ${callCount}`);
            notes.push(`تعداد پشتیبانی: ${supportCount}`);
            notes.push(`تعداد برگزاری دوره: ${courseCount}`);
        }
        if (notes.length) {
            let noteIndex = 1;
            for (const note of notes) {
                while (noteIndex < fields.length && !this.isExportEmptyValue(summary[fields[noteIndex]])) {
                    noteIndex += 1;
                }
                if (noteIndex >= fields.length) break;
                summary[fields[noteIndex]] = note;
                noteIndex += 1;
            }
        }
        return summary;
    },

    sumUniqueDrilldownPaymentDealOutstanding(rows) {
        const src = Array.isArray(rows) ? rows : [];
        const seen = new Map();
        src.forEach((row, index) => {
            if (!row || typeof row !== 'object') return;
            if (this.isExportEmptyValue(row.outstanding_balance)) return;
            const dealKey = String(row.deal_id || row.didar_deal_id || `row:${index}`).trim();
            if (!dealKey || seen.has(dealKey)) return;
            const value = Number(row.outstanding_balance || 0);
            if (Number.isFinite(value)) {
                seen.set(dealKey, Math.max(value, 0));
            }
        });
        return Array.from(seen.values()).reduce((sum, value) => sum + value, 0);
    },

    isDrilldownNumericExportField(field) {
        return [
            'service_cost', 'payable_amount', 'payment_amount', 'outstanding_balance',
            'amount', 'gross_amount', 'refunded_amount', 'refund_amount', 'total_paid',
            'payments_count', 'payments_total_amount', 'currency_amount'
        ].includes(String(field || '')) || /^payment_\d+_(amount|gross_amount|refunded_amount|refund_amount|currency_amount)$/.test(String(field || ''));
    },

    formatDrilldownExportNumber(num) {
        if (num === null || num === undefined || num === '') return '0';
        return new Intl.NumberFormat('en-US').format(num);
    },

    normalizeCheckDateForExport(value) {
        const raw = this.toEnglishDigits(String(value || '').trim());
        if (!raw) return '';
        const normalized = raw.replace(/[.\-]/g, '/');
        const match = normalized.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/);
        if (match) {
            const year = Number(match[1]);
            const month = Number(match[2]);
            const day = Number(match[3]);
            if (year >= 1200 && year <= 1600 && month >= 1 && month <= 12 && day >= 1 && day <= 31) {
                return `${String(year).padStart(4, '0')}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')}`;
            }
        }
        return this.toEnglishDigits(toPersianDate(raw) || raw);
    },

    splitDealTitleForDrilldownPaymentExport(title) {
        const normalized = this.toEnglishDigits(String(title || '').trim()).replace(/\s+/g, ' ');
        let baseTitle = normalized;
        let serviceProduct = '';
        if (normalized.includes('|')) {
            const parts = normalized.split('|');
            baseTitle = (parts.shift() || '').trim();
            serviceProduct = parts.join('|').trim();
        }

        let phoneNumber = '';
        const match = baseTitle.match(/(?:\+?98|0)?9\d{9}/);
        if (match) {
            phoneNumber = match[0];
            if (phoneNumber.startsWith('+98') && phoneNumber.length === 13) {
                phoneNumber = `0${phoneNumber.slice(3)}`;
            } else if (phoneNumber.startsWith('98') && phoneNumber.length === 12) {
                phoneNumber = `0${phoneNumber.slice(2)}`;
            } else if (phoneNumber.length === 10 && phoneNumber.startsWith('9')) {
                phoneNumber = `0${phoneNumber}`;
            }
        }

        return { phone_number: phoneNumber, service_product: serviceProduct };
    },

    normalizeDrilldownExportCell(value) {
        if (value === null || typeof value === 'undefined') return '';
        if (typeof value === 'number') return value;
        if (typeof value === 'boolean') return value ? '1' : '0';
        return this.toEnglishDigits(String(value));
    },

    formatDrilldownExportValue(field, value) {
        if (this.isExportEmptyValue(value)) return '';
        if (this.isDrilldownExportFieldHidden(field)) return '';
        const translated = this.translateDrilldownExportUiValue(field, value);
        if (!this.isDrilldownNumericExportField(field)) return translated;
        const num = Number(value || 0);
        if (!Number.isFinite(num)) return translated;
        return this.formatDrilldownExportNumber(num);
    },

    getDrilldownExportFieldKeys(datasetKey) {
        const datasets = this.state.kpiDrilldownExport?.datasets || {};
        const rows = Array.isArray(datasets?.[datasetKey]?.rows) ? datasets[datasetKey].rows : [];
        const keys = [];
        const seen = new Set();
        rows.forEach((row) => {
            Object.keys(row || {}).forEach((key) => {
                if (seen.has(key) || this.isDrilldownExportFieldHidden(key)) return;
                seen.add(key);
                keys.push(key);
            });
        });
        return this.sortDrilldownExportFields(
            datasetKey,
            keys.filter((key) => rows.some((row) => !this.isExportEmptyValue(row?.[key])))
        );
    },

    isExportEmptyValue(value) {
        if (value === null || typeof value === 'undefined') return true;
        if (typeof value === 'number') return false;
        if (typeof value === 'boolean') return false;
        if (Array.isArray(value)) return value.length === 0;
        if (typeof value === 'object') return Object.keys(value).length === 0;
        const str = String(value).trim();
        return str === '';
    },

    renderDrilldownExportControls(data) {
        if (!this.isAdmin()) return '';
        const datasets = this.buildDrilldownExportDatasets(data);
        const entries = Object.entries(datasets);
        if (entries.length === 0) return '';

        this.state.kpiDrilldownExport = this.state.kpiDrilldownExport || { datasets: {}, selectedDataset: '' };
        this.state.kpiDrilldownExport.datasets = datasets;
        const ctx = this.state.kpiDrilldownContext || {};
        const signature = JSON.stringify([
            data?.meta?.table_id || ctx.tableId || '',
            data?.meta?.metric_key || ctx.metricKey || '',
            data?.meta?.drilldown_kind || '',
            data?.meta?.row_type || '',
            entries.map(([key, item]) => `${key}:${(item.rows || []).length}`).join('|')
        ]);
        const current = this.state.kpiDrilldownExport.selectedDataset;
        if (this.state.kpiDrilldownExport.signature !== signature || !current || !datasets[current]) {
            this.state.kpiDrilldownExport.selectedDataset = datasets.deals ? 'deals' : entries[0][0];
            this.state.kpiDrilldownExport.signature = signature;
        }
        const selectedKey = this.state.kpiDrilldownExport.selectedDataset;
        const fieldKeys = this.getDrilldownExportFieldKeys(selectedKey);

        return `
            <details class="dd-export">
                <summary>
                    <span class="dd-export-label"><i class="fa-solid fa-file-export"></i> خروجی داده‌ها <span class="dd-tag">فقط ادمین</span></span>
                    <span class="dd-export-quick">
                        <button type="button" class="dd-btn is-primary dd-btn-sm" onclick="event.preventDefault();event.stopPropagation();app.exportCurrentDrilldownCsv()"><i class="fa-solid fa-file-csv"></i> CSV</button>
                        <button type="button" class="dd-btn dd-btn-sm" onclick="event.preventDefault();event.stopPropagation();app.exportCurrentDrilldownXlsx()"><i class="fa-solid fa-file-excel"></i> Excel</button>
                        <span class="dd-export-toggle" title="انتخاب فیلدها"><span class="dd-tag">فیلدها</span><i class="fa-solid fa-chevron-down dd-chev" aria-hidden="true"></i></span>
                    </span>
                </summary>
                <div class="dd-export-body">
                    <div class="dd-field-toolbar">
                        <select id="drill-export-dataset" class="dd-btn" onchange="app.refreshDrilldownExportFieldOptions()" aria-label="نوع داده خروجی">
                            ${entries.map(([key, item]) => `<option value="${this.escapeHtml(key)}" ${key === selectedKey ? 'selected' : ''}>${this.escapeHtml(item.label || key)} (${this.formatNumber((item.rows || []).length)})</option>`).join('')}
                        </select>
                        <input id="drill-export-search" class="dd-search" type="search" placeholder="جستجوی فیلد..." oninput="app.filterDrilldownExportFields(this.value)" aria-label="جستجوی فیلد">
                        <button type="button" class="dd-btn dd-preset is-active" data-preset="all" onclick="app.applyDrilldownExportPreset('all')">همه</button>
                        <button type="button" class="dd-btn dd-preset" data-preset="min" onclick="app.applyDrilldownExportPreset('min')">حداقل</button>
                        <button type="button" class="dd-btn dd-preset" data-preset="finance" onclick="app.applyDrilldownExportPreset('finance')">مالی</button>
                        <button type="button" class="dd-btn dd-preset" data-preset="cheque" onclick="app.applyDrilldownExportPreset('cheque')">چک</button>
                    </div>
                    <div id="drill-export-fields">
                        ${this.renderDrilldownExportFieldGroups(selectedKey, fieldKeys)}
                    </div>
                    <div class="dd-export-actions">
                        <span class="dd-tag" id="drill-export-count">${this.formatNumber(fieldKeys.length)} فیلد انتخاب شده</span>
                        <span class="dd-tag">خروجی با دکمه‌های CSV / Excel در بالا</span>
                    </div>
                </div>
            </details>
        `;
    },

    getDrilldownExportFieldGroupDef() {
        return [
            { key: 'lead', label: 'لید', icon: 'fa-user-tag' },
            { key: 'deal', label: 'معامله', icon: 'fa-handshake' },
            { key: 'payment', label: 'پرداخت', icon: 'fa-money-bill-wave' },
            { key: 'cheque', label: 'چک', icon: 'fa-money-check-dollar' },
            { key: 'finance', label: 'مالی', icon: 'fa-coins' },
            { key: 'other', label: 'سایر', icon: 'fa-ellipsis' }
        ];
    },

    getDrilldownFieldGroup(field) {
        const f = String(field || '');
        const sub = f.match(/^payment_\d+_(.+)$/);
        const base = sub ? sub[1] : f;
        if (/^(check_|cheque_)/.test(base) || base === 'sayadi_date') return 'cheque';
        if (/(finance_status|commission_status|invoice_code|sepidar)/.test(base)) return 'finance';
        if (sub || /^(amount|gross_amount|refund|method|payment_|destination_|card_|tracking_|depositor|account_owner|currency|receipt_image_path|registered_|deposit_date|effective_date|source_hash|transaction_id)/.test(base)) return 'payment';
        if (/^(contact_|lead_|full_name|mobile_phone|secondary_mobile_phone|customer_code|acquaintance|content_topic|created_by_name|created_via|sale_status)/.test(base)) return 'lead';
        if (/^(deal_|title|payable_amount|total_paid|outstanding_balance|pipeline_stage|status|failure_reason|register_time|won_time|lost_time|products|payments_count|payments_total_amount|payment_types|payments_summary|has_payment|deal_count|won_deal_count|lost_deal_count|current_deal_count|owner_name|owner_didar_id|service_product|service_cost|phone_number)/.test(base)) return 'deal';
        return 'other';
    },

    renderDrilldownExportFieldGroups(datasetKey, fieldKeys) {
        const keys = Array.isArray(fieldKeys) ? fieldKeys : [];
        if (!keys.length) return '<div class="dd-empty" style="padding:14px">فیلدی برای خروجی وجود ندارد.</div>';
        const groups = this.getDrilldownExportFieldGroupDef();
        const byGroup = {};
        keys.forEach((field) => {
            const g = this.getDrilldownFieldGroup(field);
            (byGroup[g] = byGroup[g] || []).push(field);
        });
        return groups.filter((g) => (byGroup[g.key] || []).length).map((g) => {
            const fields = byGroup[g.key];
            return `
                <fieldset class="dd-fieldset" data-group="${g.key}">
                    <legend>
                        <div class="dd-fieldset-head" onclick="app.toggleDrilldownFieldGroup(this)">
                            <span><i class="fa-solid ${g.icon}"></i> ${g.label}</span>
                            <span class="dd-count">${this.formatNumber(fields.length)}</span>
                        </div>
                    </legend>
                    <div class="dd-fieldset-grid">
                        ${fields.map((field) => `
                            <label class="dd-field">
                                <input type="checkbox" class="drill-export-field" value="${this.escapeHtml(field)}" checked onchange="app.updateDrilldownExportCount()">
                                <span>${this.escapeHtml(this.getDrilldownFieldLabel(datasetKey, field))}</span>
                            </label>
                        `).join('')}
                    </div>
                </fieldset>`;
        }).join('');
    },

    toggleDrilldownFieldGroup(headEl) {
        const fieldset = headEl && headEl.closest('.dd-fieldset');
        if (fieldset) fieldset.classList.toggle('is-collapsed');
    },

    filterDrilldownExportFields(query) {
        const q = this.toEnglishDigits(String(query || '').trim().toLowerCase());
        const labels = document.querySelectorAll('#drill-export-fields .dd-field');
        labels.forEach((label) => {
            const text = (label.textContent || '').toLowerCase();
            label.classList.toggle('is-hidden', q !== '' && !text.includes(q));
        });
        document.querySelectorAll('#drill-export-fields .dd-fieldset').forEach((fs) => {
            const anyVisible = Array.from(fs.querySelectorAll('.dd-field')).some((l) => !l.classList.contains('is-hidden'));
            fs.style.display = anyVisible ? '' : 'none';
        });
    },

    applyDrilldownExportPreset(name) {
        const minBase = new Set(['register_time_jalali', 'register_time', 'deal_date', 'payment_date', 'payment_date_persian', 'contact_name', 'full_name', 'customer_code', 'title', 'deal_title', 'owner_name', 'amount', 'payable_amount', 'total_paid', 'outstanding_balance']);
        const boxes = document.querySelectorAll('#drill-export-fields .drill-export-field');
        boxes.forEach((box) => {
            const field = String(box.value || '');
            const group = this.getDrilldownFieldGroup(field);
            const base = (field.match(/^payment_\d+_(.+)$/) || [null, field])[1];
            let checked = true;
            if (name === 'min') checked = minBase.has(base);
            else if (name === 'finance') checked = ['payment', 'finance', 'deal'].includes(group);
            else if (name === 'cheque') checked = ['cheque', 'deal'].includes(group) || base === 'amount';
            box.checked = checked;
        });
        document.querySelectorAll('#drill-export-fields .dd-preset, .dd-field-toolbar .dd-preset').forEach((btn) => {
            btn.classList.toggle('is-active', btn.dataset.preset === name);
        });
        this.updateDrilldownExportCount();
    },

    updateDrilldownExportCount() {
        const count = document.querySelectorAll('#drill-export-fields .drill-export-field:checked').length;
        const el = document.getElementById('drill-export-count');
        if (el) el.textContent = `${this.formatNumber(count)} فیلد انتخاب شده`;
    },

    renderPerformanceActivityPerformanceSection(activityPerformance) {
        const pastCards = Array.isArray(activityPerformance?.past_deals) ? activityPerformance.past_deals : [];
        const dailyCards = Array.isArray(activityPerformance?.daily) ? activityPerformance.daily : [];
        if (!pastCards.length && !dailyCards.length) {
            return '';
        }

        const renderCard = (card, borderClass) => {
            const countValue = Number(card?.count || 0);
            const duration = Number(card?.duration_minutes || 0);
            const amount = Number(card?.amount || 0);
            const subCount = Number(card?.sub_count || 0);
            const subAmount = Number(card?.sub_amount || 0);
            const stageCount = Number(card?.stage_count || 0);
            const performedActivityCount = Number(card?.performed_activity_count ?? -1);
            const stageMetricKey = String(card?.stage_metric_key || '').trim();
            const primaryCountLabel = String(card?.primary_count_label || '').trim();
            const primaryValueType = String(card?.primary_value_type || 'count').trim().toLowerCase() === 'amount'
                ? 'amount'
                : 'count';
            const primaryValueHtml = primaryValueType === 'amount'
                ? `${this.formatNumber(amount)} <span class="text-sm font-medium text-gray-300">تومان</span>`
                : this.formatNumber(countValue);
            const lines = [];
            if (duration > 0) {
                lines.push(`مدت: ${this.formatNumber(duration)} دقیقه`);
            }
            if (primaryValueType !== 'amount' && amount > 0) {
                lines.push(`مبلغ: ${this.formatNumber(amount)} تومان`);
            }
            if (primaryCountLabel && primaryValueType !== 'amount') {
                lines.push(this.escapeHtml(primaryCountLabel));
            }
            if (performedActivityCount >= 0) {
                lines.push(`فعالیت ثبت‌شده در بازه: ${this.formatNumber(performedActivityCount)}`);
            }
            if (subCount > 0) {
                lines.push(`${this.escapeHtml(card?.sub_count_label || 'زیرمقدار')}: ${this.formatNumber(subCount)}`);
            }
            if (subAmount > 0) {
                lines.push(`${this.escapeHtml(card?.sub_amount_label || 'زیرمبلغ')}: ${this.formatNumber(subAmount)} تومان`);
            }
            if (stageMetricKey) {
                lines.push(`
                    <button type="button" class="text-[11px] text-cyan-300 hover:text-cyan-200" onclick="event.stopPropagation(); app.openPerformanceChartDrilldown('performance_cards','${stageMetricKey}', {})">
                        مشاهده مرحله کاریز: ${this.formatNumber(stageCount)}
                    </button>
                `);
            }
            const metricKey = card?.key || '';
            const isClickable = metricKey !== '';
            const clickHandler = isClickable
                ? `onclick="app.openPerformanceChartDrilldown('performance_cards','${metricKey}', {})"`
                : '';
            return `
                <div class="glass p-3 border-r-4 ${borderClass} ${isClickable ? 'cursor-pointer hover:border-cyan-400 transition-colors' : ''}" ${clickHandler}>
                    <div class="text-[11px] text-gray-400 mb-1">${this.escapeHtml(card?.title || '-')}</div>
                    <div class="text-lg font-bold text-white leading-7 mb-1">${primaryValueHtml}</div>
                    ${lines.map(line => `<div class="text-[11px] text-gray-500">${line}</div>`).join('')}
                    ${isClickable ? '<div class="text-[10px] text-cyan-300 mt-2">برای مشاهده جزئیات کلیک کنید</div>' : ''}
                </div>
            `;
        };

        return `
            <div class="glass p-4 mb-6 border border-gray-700/70">
                <div class="text-sm text-gray-200 font-bold mb-4">عملکرد فعالیت‌ها</div>
                <div class="mb-5">
                    <div class="text-xs text-gray-400 mb-3">فعالیت‌های معاملات گذشته</div>
                    <div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-3">
                        ${pastCards.map(card => renderCard(card, 'border-indigo-500')).join('')}
                    </div>
                </div>
                <div>
                    <div class="text-xs text-gray-400 mb-3">فعالیت‌های معاملات بازه</div>
                    <div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-3">
                        ${dailyCards.map(card => renderCard(card, 'border-sky-500')).join('')}
                    </div>
                </div>
            </div>
        `;
    },

    refreshDrilldownExportFieldOptions() {
        if (!this.isAdmin()) return;
        const select = document.getElementById('drill-export-dataset');
        const container = document.getElementById('drill-export-fields');
        if (!select || !container) return;
        const selectedKey = String(select.value || '');
        if (!selectedKey) return;
        this.state.kpiDrilldownExport = this.state.kpiDrilldownExport || { datasets: {}, selectedDataset: '' };
        this.state.kpiDrilldownExport.selectedDataset = selectedKey;
        const fields = this.getDrilldownExportFieldKeys(selectedKey);
        container.innerHTML = this.renderDrilldownExportFieldGroups(selectedKey, fields);
        const search = document.getElementById('drill-export-search');
        if (search) search.value = '';
        document.querySelectorAll('.dd-field-toolbar .dd-preset').forEach((btn) => {
            btn.classList.toggle('is-active', btn.dataset.preset === 'all');
        });
        this.updateDrilldownExportCount();
    },

    setDrilldownExportAllFields(checked) {
        const boxes = document.querySelectorAll('#drill-export-fields .drill-export-field');
        boxes.forEach((box) => { box.checked = !!checked; });
        this.updateDrilldownExportCount();
    },

    buildCurrentDrilldownExportTable() {
        if (!this.isAdmin()) {
            Swal.fire('خطا', 'این قابلیت فقط برای ادمین فعال است', 'error');
            return null;
        }
        const datasets = this.state.kpiDrilldownExport?.datasets || {};
        const select = document.getElementById('drill-export-dataset');
        const datasetKey = String(select?.value || this.state.kpiDrilldownExport?.selectedDataset || '');
        const dataset = datasets?.[datasetKey];
        if (!dataset || !Array.isArray(dataset.rows) || dataset.rows.length === 0) {
            Swal.fire('خطا', 'برای این بخش داده‌ای جهت خروجی وجود ندارد', 'error');
            return null;
        }
        const selectedFields = Array.from(document.querySelectorAll('#drill-export-fields .drill-export-field:checked'))
            .map((input) => String(input.value || '').trim())
            .filter(Boolean);
        if (selectedFields.length === 0) {
            Swal.fire('خطا', 'حداقل یک فیلد برای خروجی انتخاب کنید', 'error');
            return null;
        }
        const effectiveFields = selectedFields.filter((field) => dataset.rows.some((row) => !this.isExportEmptyValue(row?.[field])));
        const enforceCustomerCodeDatasets = new Set(['leads', 'deals', 'payments', 'activities']);
        if (enforceCustomerCodeDatasets.has(datasetKey)) {
            const hasAnyCustomerCode = dataset.rows.some((row) => !this.isExportEmptyValue(row?.customer_code));
            if (hasAnyCustomerCode && !effectiveFields.includes('customer_code')) {
                effectiveFields.push('customer_code');
            }
        }
        if (effectiveFields.length === 0) {
            Swal.fire('خطا', 'فیلد انتخاب‌شده با داده غیرخالی وجود ندارد', 'error');
            return null;
        }
        const orderedFields = this.sortDrilldownExportFields(datasetKey, effectiveFields);
        const headerRow = orderedFields.map((field) => this.normalizeDrilldownExportCell(this.getDrilldownFieldLabel(datasetKey, field)));
        const bodyRows = [];
        dataset.rows.forEach((row) => {
            bodyRows.push(orderedFields.map((field) => {
                const value = this.formatDrilldownExportValue(field, row?.[field]);
                if (Array.isArray(value) || (value && typeof value === 'object')) {
                    return this.normalizeDrilldownExportCell(JSON.stringify(value));
                }
                return this.normalizeDrilldownExportCell(value ?? '');
            }));
        });
        const summaryRow = this.buildDrilldownExportSummaryRow(datasetKey, orderedFields, dataset.rows);
        if (summaryRow) {
            bodyRows.push(orderedFields.map((field) => this.normalizeDrilldownExportCell(this.formatDrilldownExportValue(field, summaryRow[field] ?? ''))));
        }
        const ctx = this.state.kpiDrilldownContext || {};
        const ts = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
        const base = `drill_${ctx.mode || 'kpi'}_${ctx.tableId || 'table'}_${ctx.metricKey || 'metric'}_${datasetKey}_${ts}`.replace(/[^\w\-]+/g, '_');
        return { datasetKey, fields: orderedFields, headerRow, bodyRows, base };
    },

    exportCurrentDrilldownCsv() {
        const table = this.buildCurrentDrilldownExportTable();
        if (!table) return;
        const lines = [
            table.headerRow.map((value) => this.csvEscape(value)).join(','),
            ...table.bodyRows.map((row) => row.map((value) => this.csvEscape(value ?? '')).join(','))
        ];
        const csvText = "\uFEFF" + lines.join('\n');
        const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `${table.base}.csv`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    },

    async exportCurrentDrilldownXlsx() {
        const table = this.buildCurrentDrilldownExportTable();
        if (!table) return;
        try { await this.ensureVendor('xlsx'); } catch (e) {}
        if (typeof XLSX === 'undefined') {
            Swal.fire('خطا', 'کتابخانه خروجی Excel بارگذاری نشده است', 'error');
            return;
        }
        const aoa = [table.headerRow, ...table.bodyRows];
        const worksheet = XLSX.utils.aoa_to_sheet(aoa);
        worksheet['!autofilter'] = { ref: XLSX.utils.encode_range({ s: { r: 0, c: 0 }, e: { r: Math.max(0, aoa.length - 1), c: Math.max(0, table.headerRow.length - 1) } }) };
        worksheet['!cols'] = table.headerRow.map((header, index) => {
            const maxLen = aoa.reduce((max, row) => Math.max(max, String(row[index] ?? '').length), String(header || '').length);
            return { wch: Math.min(Math.max(maxLen + 2, 10), 45) };
        });
        const workbook = XLSX.utils.book_new();
        workbook.Workbook = { Views: [{ RTL: true }] };
        XLSX.utils.book_append_sheet(workbook, worksheet, 'Drilldown');
        XLSX.writeFile(workbook, `${table.base}.xlsx`, { bookType: 'xlsx', compression: true });
    },

    renderKpiDrilldownTableFooter(rows = [], columns = []) {
        const src = Array.isArray(rows) ? rows : [];
        if (!src.length || !Array.isArray(columns) || !columns.length) return '';
        const cells = columns.map((col, index) => {
            const align = col.align === 'center' ? ' style="text-align:center"' : '';
            const cls = ' class="p-2 border border-gray-700 font-bold bg-cyan-950/20"';
            if (index === 0) {
                return `<td${cls}${align}>جمع کل: ${this.formatNumber(src.length)} ردیف</td>`;
            }
            if (col.total === 'sum') {
                const total = src.reduce((sum, row) => {
                    const value = typeof col.value === 'function' ? col.value(row) : row?.[col.key];
                    return sum + Number(value || 0);
                }, 0);
                const valueHtml = col.pill
                    ? `<span class="perf-pill">${this.formatNumber(total)}</span>`
                    : this.formatNumber(total);
                return `<td${cls}${align}>${valueHtml}</td>`;
            }
            if (col.total === 'percent') {
                return `<td${cls}${align}>100٪</td>`;
            }
            return `<td${cls}${align}>-</td>`;
        }).join('');
        return `<tfoot><tr>${cells}</tr></tfoot>`;
    },

    renderKpiDrilldownContent(data) {
        const meta = data?.meta || {};
        const kind = String(meta.drilldown_kind || '');

        // Dynamic dialog title + initial focus (a11y).
        const titleEl = document.getElementById('kpi-drilldown-title');
        if (titleEl) titleEl.textContent = meta.title || 'جزئیات';
        this.focusDrilldownClose();

        // Ensure every view can show the lead (name + link) on deal/payment rows.
        this.ddEnrichDrilldownLeads(data);

        if (kind === 'conversion_analysis' && data?.conversion_analysis) {
            return this.renderConversionAnalysisDrilldownContent(data);
        }
        if (kind === 'cheque') return this.renderChequeDrilldownContent(data);
        if (kind === 'outstanding_balance') return this.renderOutstandingDrilldownContent(data);
        if (kind === 'deals_without_payment') return this.renderDealsWithoutPaymentDrilldownContent(data);
        if (kind === 'conversion_time') return this.renderConversionTimeDrilldownContent(data);

        return this.renderGenericDrilldownContent(data);
    },

    // One adaptive table whose row-type matches the card that opened it.
    renderGenericDrilldownContent(data) {
        const meta = data?.meta || {};
        const leads = Array.isArray(data?.leads) ? data.leads : [];
        const deals = Array.isArray(data?.deals) ? data.deals : [];
        const activities = Array.isArray(data?.activities) ? data.activities : [];
        const payments = Array.isArray(data?.payments) ? data.payments : [];

        const rowType = meta.row_type
            || (payments.length ? 'payment'
                : deals.length ? 'deal'
                    : activities.length ? 'activity'
                        : 'lead');

        const sections = {
            payment: { icon: 'fa-money-bill-wave', cls: 'is-money', label: 'پرداخت‌ها', table: () => this.renderDrilldownPaymentTable(payments) },
            deal: { icon: 'fa-handshake', cls: 'is-source', label: 'معاملات', table: () => this.renderDrilldownDealTable(deals) },
            activity: { icon: 'fa-list-check', cls: 'is-balance', label: 'فعالیت‌ها', table: () => this.renderDrilldownActivityTable(activities) },
            lead: { icon: 'fa-user-tag', cls: 'is-source', label: 'لیدها', table: () => this.renderDrilldownLeadTable(leads) }
        };
        const section = sections[rowType] || sections.lead;

        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            ${this.renderDrilldownMetricStrip(meta, { leads, deals, activities, payments })}
            <div class="dd-section ${section.cls}">
                <div class="dd-section-head"><i class="fa-solid ${section.icon}"></i> ${section.label}</div>
                ${section.table()}
            </div>
        `;
    },

    // ---- shared drill-down primitives (.dd-*) ----
    renderDrilldownMetricStrip(meta, counts = {}) {
        const items = [
            { n: meta.total_leads ?? (counts.leads || []).length, l: 'لید', role: 'source' },
            { n: meta.total_deals ?? (counts.deals || []).length, l: 'معامله', role: 'vol' },
            { n: meta.total_activities ?? (counts.activities || []).length, l: 'فعالیت', role: 'balance' },
            { n: meta.total_payments ?? (counts.payments || []).length, l: 'پرداخت', role: 'money' }
        ].filter((x) => Number(x.n) > 0);
        if (!items.length) return '';
        return `<div class="dd-metrics">${items.map((x) => `
            <span class="dd-metric is-${x.role}">
                <span class="dd-metric-val">${this.formatNumber(Number(x.n) || 0)}</span>
                <span class="dd-metric-lbl">${x.l}</span>
            </span>`).join('')}</div>`;
    },

    ddEmpty(message, icon = 'fa-inbox') {
        return `<div class="dd-empty"><i class="fa-solid ${icon}"></i>${this.escapeHtml(message)}</div>`;
    },

    // columns: { label, num?, cell(row), total?: 'sum', value?(row), pill?, pillCls? }
    ddTable(columns, rows) {
        const src = Array.isArray(rows) ? rows : [];
        if (!src.length) return this.ddEmpty('رکوردی برای نمایش یافت نشد.');
        const head = columns.map((c) => `<th class="${c.num ? 'is-num' : ''}">${c.label}</th>`).join('');
        const body = src.map((row) => `<tr>${columns.map((c) => `<td class="${c.num ? 'is-num' : ''}">${c.cell(row)}</td>`).join('')}</tr>`).join('');
        const foot = this.ddTableFooter(columns, src);
        return `<div class="dd-tablewrap"><table class="dd-table"><thead><tr>${head}</tr></thead><tbody>${body}</tbody>${foot}</table></div>`;
    },

    ddTableFooter(columns, rows) {
        const cells = columns.map((c, i) => {
            if (i === 0) return `<td>جمع کل: ${this.formatNumber(rows.length)} ردیف</td>`;
            if (c.total === 'sum') {
                const total = rows.reduce((sum, row) => sum + Number((typeof c.value === 'function' ? c.value(row) : row?.[c.key]) || 0), 0);
                const html = c.pill
                    ? `<span class="dd-pill ${c.pillCls || ''}">${this.formatNumber(total)}</span>`
                    : this.formatNumber(total);
                return `<td class="${c.num ? 'is-num' : ''}">${html}</td>`;
            }
            return `<td class="${c.num ? 'is-num' : ''}">-</td>`;
        }).join('');
        return `<tfoot><tr>${cells}</tr></tfoot>`;
    },

    ddDealLink(deal, source = '') {
        const id = this.escapeHtml(deal.didar_deal_id || deal.deal_id || '');
        const title = this.escapeHtml(deal.deal_title || deal.title || ('#' + (deal.deal_id || '')));
        if (!id) return title;
        const arg = source ? `'${id}', '${this.escapeHtml(source)}'` : `'${id}'`;
        return `<button type="button" class="dd-link" onclick="app.openDealFromKpiDrilldown(${arg})">${title}<i class="fa-solid fa-arrow-up-right-from-square" style="font-size:9px"></i></button>`;
    },

    ddLeadCell(row) {
        const name = this.escapeHtml(row.full_name || row.lead_name || row.contact_name || '-');
        const id = this.escapeHtml(row.contact_id || row.contact_didar_id || '');
        if (!id) return name;
        return `<button type="button" class="dd-link is-faint" onclick="app.openLeadFromKpiDrilldown('${id}')">${name}<i class="fa-solid fa-arrow-up-right-from-square" style="font-size:9px"></i></button>`;
    },

    // Cross-reference the lead (contact) onto deal + payment rows so every view can
    // show the lead name + link in one column. Uses the `leads` array already in the
    // payload; purely additive, never overwrites values the backend already set.
    ddEnrichDrilldownLeads(data) {
        if (!data || typeof data !== 'object') return;
        const leads = Array.isArray(data.leads) ? data.leads : [];
        const deals = Array.isArray(data.deals) ? data.deals : [];
        const payments = Array.isArray(data.payments) ? data.payments : [];
        const leadById = {};
        leads.forEach((l) => {
            const id = String(l.contact_id || l.contact_didar_id || '').trim();
            if (id && !leadById[id]) leadById[id] = l;
        });
        const dealContactById = {};
        deals.forEach((d) => {
            const cid = String(d.contact_didar_id || d.contact_id || '').trim();
            [d.deal_id, d.didar_deal_id].forEach((k) => {
                if (k !== undefined && k !== null && k !== '') dealContactById[String(k)] = cid;
            });
            const lead = cid ? leadById[cid] : null;
            if (lead) {
                if (!d.contact_name) d.contact_name = lead.full_name || lead.lead_name || '';
                if (!d.mobile_phone) d.mobile_phone = lead.mobile_phone || lead.secondary_mobile_phone || '';
            }
        });
        payments.forEach((p) => {
            let cid = String(p.contact_didar_id || p.contact_id || '').trim();
            if (!cid) {
                cid = dealContactById[String(p.deal_id)] || dealContactById[String(p.didar_deal_id)] || '';
                if (cid) p.contact_didar_id = cid;
            }
            if (cid && !p.contact_name) {
                const lead = leadById[cid];
                if (lead) p.contact_name = lead.full_name || lead.lead_name || '';
            }
        });
    },

    // Lead name + link rendered as a sub-line beneath a deal title cell.
    ddLeadSub(row) {
        const name = String(row.contact_name || row.lead_name || '').trim();
        const id = String(row.contact_didar_id || row.contact_id || '').trim();
        if (name && id) {
            return `<span class="dd-cell-sub"><button type="button" class="dd-link is-faint" onclick="app.openLeadFromKpiDrilldown('${this.escapeHtml(id)}')">${this.escapeHtml(name)}<i class="fa-solid fa-arrow-up-right-from-square" style="font-size:8px"></i></button></span>`;
        }
        const txt = this.escapeHtml(name || row.customer_code || '');
        return txt ? `<span class="dd-cell-sub">${txt}</span>` : '';
    },

    ddPaymentMethodLabel(value) {
        const k = String(value || '').toLowerCase().trim();
        const map = {
            card: 'کارت‌خوان', pos: 'کارت‌خوان', cash: 'نقدی', check: 'چک', cheque: 'چک',
            transfer: 'انتقال بانکی', card_to_card: 'کارت به کارت', online: 'آنلاین',
            wallet: 'کیف پول', deposit: 'واریز', sheba: 'شبا'
        };
        if (map[k]) return map[k];
        return value ? this.escapeHtml(String(value)) : '-';
    },

    ddPaymentStatusBadge(value) {
        const k = String(value || '').toLowerCase().trim();
        if (!k) return '<span class="dd-badge is-neutral">-</span>';
        const map = {
            confirmed: ['تأیید‌شده', 'is-collected'], paid: ['پرداخت‌شده', 'is-collected'],
            success: ['موفق', 'is-collected'], collected: ['وصول‌شده', 'is-collected'],
            pending: ['در انتظار', 'is-pending'], processing: ['در حال بررسی', 'is-pending'],
            cancelled: ['لغو‌شده', 'is-returned'], canceled: ['لغو‌شده', 'is-returned'],
            deleted: ['حذف‌شده', 'is-returned'], failed: ['ناموفق', 'is-returned'],
            void: ['باطل', 'is-returned'], returned: ['برگشتی', 'is-returned']
        };
        const hit = map[k];
        if (hit) return `<span class="dd-badge ${hit[1]}">${hit[0]}</span>`;
        return `<span class="dd-badge is-neutral">${this.escapeHtml(String(value))}</span>`;
    },

    ddPaymentSepidarStatusLabel(row) {
        const payment = row && typeof row === 'object' ? row : {};
        const financeStatus = String(payment.finance_status || '').trim().toLowerCase();
        if (payment.sepidar_registered_at || financeStatus === 'sepidar_registered') {
            return 'ثبت شده در سپیدار';
        }
        return 'در انتظار ثبت سپیدار';
    },

    ddPaymentSepidarStatusBadge(row) {
        const label = this.ddPaymentSepidarStatusLabel(row);
        const registered = label === 'ثبت شده در سپیدار';
        return `<span class="dd-badge ${registered ? 'is-collected' : 'is-pending'}">${label}</span>`;
    },

    renderDrilldownPaymentTable(payments) {
        return this.ddTable([
            { label: 'تاریخ پرداخت', cell: (p) => this.formatKpiDate(p.payment_date || '') },
            { label: 'مبلغ', num: true, total: 'sum', key: 'amount', pill: true, cell: (p) => `<span class="dd-pill">${this.formatNumber(Number(p.amount || 0))}</span>` },
            { label: 'لید', cell: (p) => this.ddLeadCell(p) },
            { label: 'معامله', cell: (p) => this.ddDealLink(p, 'payments') },
            { label: 'روش پرداخت', cell: (p) => this.ddPaymentMethodLabel(p.payment_method || p.payment_type || p.method) },
            { label: 'وضعیت', cell: (p) => this.ddPaymentSepidarStatusBadge(p) },
            { label: 'مسئول', cell: (p) => this.escapeHtml(p.owner_name || p.payment_owner_name || '-') }
        ], payments);
    },

    renderDrilldownDealTable(deals) {
        return this.ddTable([
            { label: 'عنوان معامله', cell: (d) => this.ddDealLink(d) + this.ddLeadSub(d) },
            { label: 'تاریخ معامله', cell: (d) => this.formatKpiDate(d.deal_date || '') },
            { label: 'وضعیت', cell: (d) => this.ddDealStatusBadge(d.deal_status_group || d.status || '') },
            { label: 'مبلغ قرارداد', num: true, total: 'sum', key: 'payable_amount', cell: (d) => this.formatNumber(Number(d.payable_amount || 0)) },
            { label: 'جمع پرداخت', num: true, total: 'sum', key: 'total_paid', pill: true, cell: (d) => `<span class="dd-pill">${this.formatNumber(Number(d.total_paid || 0))}</span>` },
            { label: 'مانده', num: true, total: 'sum', value: (d) => Math.max(Number(d.payable_amount || 0) - Number(d.total_paid || 0), 0), pill: true, pillCls: 'is-balance', cell: (d) => {
                const bal = Math.max(Number(d.payable_amount || 0) - Number(d.total_paid || 0), 0);
                return `<span class="dd-pill ${bal > 0 ? 'is-balance' : 'is-muted'}">${this.formatNumber(bal)}</span>`;
            } }
        ], deals);
    },

    renderDrilldownLeadTable(leads) {
        return this.ddTable([
            { label: 'نام لید', cell: (l) => this.ddLeadCell(l) },
            { label: 'شماره', cell: (l) => `<span class="dd-ltr">${this.escapeHtml(l.mobile_phone || l.secondary_mobile_phone || '-')}</span>` },
            { label: 'تاریخ ثبت', cell: (l) => this.formatKpiDate(l.lead_date || l.lead_register_time || '') },
            { label: 'مسئول', cell: (l) => this.escapeHtml(l.owner_name || '-') }
        ], leads);
    },

    renderDrilldownActivityTable(activities) {
        return this.ddTable([
            { label: 'تاریخ', cell: (a) => this.formatKpiDate(a.activity_date || '') },
            { label: 'نوع فعالیت', cell: (a) => this.escapeHtml(a.activity_type || '-') },
            { label: 'مدت (دقیقه)', num: true, total: 'sum', key: 'activity_duration_minutes', cell: (a) => this.formatNumber(Number(a.activity_duration_minutes || 0)) },
            { label: 'لید', cell: (a) => this.ddLeadCell(a) },
            { label: 'شماره', cell: (a) => `<span class="dd-ltr">${this.escapeHtml(a.mobile_phone || '-')}</span>` },
            { label: 'مسئول', cell: (a) => this.escapeHtml(a.owner_name || '-') }
        ], activities);
    },

    ddDealStatusBadge(group) {
        const key = String(group || '').toLowerCase();
        const map = {
            won: ['موفق', 'is-collected'],
            lost: ['ناموفق', 'is-returned'],
            pending: ['جاری', 'is-pending'],
            open: ['جاری', 'is-pending']
        };
        const [label, cls] = map[key] || [this.escapeHtml(group || '-'), 'is-neutral'];
        return `<span class="dd-badge ${cls}">${this.escapeHtml(label)}</span>`;
    },

    ddChequeCollectionBadge(status) {
        const key = String(status || '').toLowerCase();
        const map = {
            collected: ['وصول‌شده', 'is-collected'],
            returned: ['برگشتی', 'is-returned'],
            bounced: ['برگشتی', 'is-returned'],
            rejected: ['رد‌شده', 'is-returned'],
            pending: ['در انتظار', 'is-pending'],
            blataklif: ['بلاتکلیف', 'is-pending']
        };
        const [label, cls] = map[key] || ['نامشخص', 'is-neutral'];
        return `<span class="dd-badge ${cls}">${label}</span>`;
    },

    ddIsReturnedCheque(row) {
        const c = String(row.cheque_collection_status || '').toLowerCase();
        const s = String(row.cheque_status || '').toLowerCase();
        return ['returned', 'bounced', 'rejected'].includes(c) || ['returned', 'bounced', 'rejected'].includes(s);
    },

    ddDaysBetween(later, earlier) {
        const a = this.ddParseDate(later);
        const b = this.ddParseDate(earlier);
        if (!a || !b) return null;
        return Math.round((a - b) / 86400000);
    },

    ddParseDate(value) {
        const raw = this.toEnglishDigits(String(value || '').trim());
        if (!raw) return null;
        const m = raw.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
        if (!m) return null;
        const t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
        return Number.isFinite(t) ? t : null;
    },

    // ---- dedicated drill-down views ----
    renderChequeDrilldownContent(data) {
        const meta = data?.meta || {};
        const rows = Array.isArray(data?.payments) ? data.payments : [];
        const totalAmount = rows.reduce((s, r) => s + Number(r.amount || 0), 0);
        const collected = rows.filter((r) => String(r.cheque_collection_status || '').toLowerCase() === 'collected');
        const returned = rows.filter((r) => this.ddIsReturnedCheque(r));
        const pending = rows.filter((r) => {
            const c = String(r.cheque_collection_status || '').toLowerCase();
            return c !== 'collected' && !this.ddIsReturnedCheque(r);
        });
        const metrics = `<div class="dd-metrics">
            <span class="dd-metric is-cheque"><span class="dd-metric-val">${this.formatNumber(rows.length)}</span><span class="dd-metric-lbl">کل چک‌ها</span></span>
            <span class="dd-metric is-money"><span class="dd-metric-val">${this.formatNumber(totalAmount)}</span><span class="dd-metric-lbl">مبلغ کل</span></span>
            <span class="dd-metric is-money"><span class="dd-metric-val">${this.formatNumber(collected.length)}</span><span class="dd-metric-lbl">وصول‌شده</span></span>
            <span class="dd-metric is-cheque"><span class="dd-metric-val">${this.formatNumber(pending.length)}</span><span class="dd-metric-lbl">در انتظار</span></span>
            <span class="dd-metric" style="--role:var(--dd-danger)"><span class="dd-metric-val">${this.formatNumber(returned.length)}</span><span class="dd-metric-lbl">برگشتی</span></span>
        </div>`;
        const table = this.ddTable([
            { label: 'تاریخ ثبت', cell: (r) => this.formatKpiDate(r.registered_date || r.payment_date || '') },
            { label: 'سررسید', cell: (r) => this.formatKpiDate(r.cheque_due_date || r.check_due_date || '') },
            { label: 'وضعیت وصول', cell: (r) => this.ddChequeCollectionBadge(r.cheque_collection_status) },
            { label: 'برگشتی', cell: (r) => this.ddIsReturnedCheque(r) ? '<span class="dd-badge is-returned">برگشتی</span>' : '<span class="dd-badge is-neutral">خیر</span>' },
            { label: 'شماره چک', cell: (r) => `<span class="dd-ltr">${this.escapeHtml(r.check_number || '-')}</span>` },
            { label: 'بانک', cell: (r) => this.escapeHtml(r.check_bank_name || '-') },
            { label: 'صاحب چک', cell: (r) => this.escapeHtml(r.check_owner_name || '-') },
            { label: 'لید', cell: (r) => this.ddLeadCell(r) },
            { label: 'معامله', cell: (r) => this.ddDealLink(r, 'payments') },
            { label: 'مبلغ', num: true, total: 'sum', key: 'amount', pill: true, cell: (r) => `<span class="dd-pill">${this.formatNumber(Number(r.amount || 0))}</span>` }
        ], rows);
        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            <div class="dd-note is-cheque" style="--role:var(--dd-cheque)">
                <div class="dd-note-title">وضعیت چک‌ها</div>
                <div class="dd-note-body">تاریخ ثبت، سررسید، وضعیت وصول و برگشتی هر چک به همراه شماره، بانک و صاحب چک.</div>
            </div>
            ${metrics}
            <div class="dd-section is-cheque">
                <div class="dd-section-head"><i class="fa-solid fa-money-check-dollar"></i> فهرست چک‌ها</div>
                ${table}
            </div>
        `;
    },

    renderOutstandingDrilldownContent(data) {
        const deals = Array.isArray(data?.deals) ? data.deals : [];
        const rows = deals.map((d) => {
            const payable = Number(d.payable_amount || 0);
            const paid = Number(d.total_paid || d.payment_amount || d.payments_total_amount || 0);
            const explicitOutstanding = d.outstanding_balance ?? d.outstanding_amount;
            const outstanding = explicitOutstanding === null || typeof explicitOutstanding === 'undefined' || explicitOutstanding === ''
                ? Math.max(payable - paid, 0)
                : Math.max(Number(explicitOutstanding || 0), 0);
            const age = this.ddDaysBetween(this.getTodayGregorianDateOnly(), d.deal_date) ?? 0;
            return { ...d, total_paid: paid, payment_amount: paid, outstanding_balance: outstanding, _outstanding: outstanding, _age: age };
        }).filter((d) => d._outstanding > 0)
            .sort((a, b) => b._outstanding - a._outstanding);
        const totalOut = rows.reduce((s, r) => s + r._outstanding, 0);
        const totalContract = rows.reduce((s, r) => s + Number(r.payable_amount || 0), 0);
        const totalPaid = rows.reduce((s, r) => s + Number(r.total_paid || 0), 0);
        const ratio = totalContract > 0 ? Math.round((totalPaid / totalContract) * 1000) / 10 : 0;
        const metrics = `<div class="dd-metrics">
            <span class="dd-metric is-vol"><span class="dd-metric-val">${this.formatNumber(rows.length)}</span><span class="dd-metric-lbl">معامله مانده‌دار</span></span>
            <span class="dd-metric is-balance"><span class="dd-metric-val">${this.formatNumber(totalOut)}</span><span class="dd-metric-lbl">جمع مانده</span></span>
            <span class="dd-metric is-source"><span class="dd-metric-val">${this.formatNumber(totalContract)}</span><span class="dd-metric-lbl">جمع قرارداد</span></span>
            <span class="dd-metric is-money"><span class="dd-metric-val">${this.formatNumber(ratio)}٪</span><span class="dd-metric-lbl">نرخ وصول</span></span>
        </div>`;
        const ageChip = (age) => {
            const cls = age >= 90 ? 'is-stale' : age >= 30 ? 'is-warn' : 'is-fresh';
            return `<span class="dd-age ${cls}">${this.formatNumber(age)} روز</span>`;
        };
        const table = this.ddTable([
            { label: 'عنوان معامله', cell: (d) => this.ddDealLink(d) + this.ddLeadSub(d) },
            { label: 'تاریخ معامله', cell: (d) => this.formatKpiDate(d.deal_date || '') },
            { label: 'مبلغ قرارداد', num: true, total: 'sum', key: 'payable_amount', cell: (d) => this.formatNumber(Number(d.payable_amount || 0)) },
            { label: 'پرداخت‌شده', num: true, total: 'sum', key: 'total_paid', pill: true, cell: (d) => `<span class="dd-pill">${this.formatNumber(Number(d.total_paid || 0))}</span>` },
            { label: 'مانده', num: true, total: 'sum', value: (d) => d._outstanding, pill: true, pillCls: 'is-balance', cell: (d) => `<span class="dd-pill is-balance">${this.formatNumber(d._outstanding)}</span>` },
            { label: 'سن مانده', num: true, cell: (d) => ageChip(d._age) },
            { label: 'مسئول', cell: (d) => this.escapeHtml(d.owner_name || '-') }
        ], rows);
        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            <div class="dd-note is-balance" style="--role:var(--dd-balance)">
                <div class="dd-note-title">مانده حساب معاملات</div>
                <div class="dd-note-body">معاملاتی که مبلغ قرارداد آن‌ها به‌طور کامل وصول نشده است. «سن مانده» تعداد روز از تاریخ معامله تا امروز است.</div>
            </div>
            ${metrics}
            <div class="dd-section is-balance">
                <div class="dd-section-head"><i class="fa-solid fa-scale-balanced"></i> معاملات دارای مانده</div>
                ${table}
            </div>
        `;
    },

    renderDealsWithoutPaymentDrilldownContent(data) {
        const deals = Array.isArray(data?.deals) ? data.deals : [];
        const totalPotential = deals.reduce((s, d) => s + Number(d.payable_amount || 0), 0);
        const metrics = `<div class="dd-metrics">
            <span class="dd-metric is-vol"><span class="dd-metric-val">${this.formatNumber(deals.length)}</span><span class="dd-metric-lbl">معامله بدون پرداخت</span></span>
            <span class="dd-metric is-source"><span class="dd-metric-val">${this.formatNumber(totalPotential)}</span><span class="dd-metric-lbl">جمع مبلغ بالقوه</span></span>
        </div>`;
        const ageChip = (age) => {
            if (age === null) return '-';
            const cls = age >= 90 ? 'is-stale' : age >= 30 ? 'is-warn' : 'is-fresh';
            return `<span class="dd-age ${cls}">${this.formatNumber(age)} روز</span>`;
        };
        const table = this.ddTable([
            { label: 'عنوان معامله', cell: (d) => this.ddDealLink(d) + this.ddLeadSub(d) },
            { label: 'تاریخ معامله', cell: (d) => this.formatKpiDate(d.deal_date || '') },
            { label: 'وضعیت', cell: (d) => this.ddDealStatusBadge(d.deal_status_group || d.status || '') },
            { label: 'مبلغ قرارداد', num: true, total: 'sum', key: 'payable_amount', cell: (d) => this.formatNumber(Number(d.payable_amount || 0)) },
            { label: 'سن معامله', num: true, cell: (d) => ageChip(this.ddDaysBetween(this.getTodayGregorianDateOnly(), d.deal_date)) },
            { label: 'مسئول', cell: (d) => this.escapeHtml(d.owner_name || '-') }
        ], deals);
        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            <div class="dd-note is-source" style="--role:var(--dd-source)">
                <div class="dd-note-title">معاملات بدون پرداخت</div>
                <div class="dd-note-body">معاملاتی که در بازه ثبت شده‌اند اما هیچ پرداختی برای آن‌ها ثبت نشده است.</div>
            </div>
            ${metrics}
            <div class="dd-section is-source">
                <div class="dd-section-head"><i class="fa-solid fa-file-circle-xmark"></i> فهرست معاملات</div>
                ${table}
            </div>
        `;
    },

    renderConversionTimeDrilldownContent(data) {
        const dealsRaw = Array.isArray(data?.deals) ? data.deals : [];
        const rows = dealsRaw.map((d) => {
            const days = this.ddDaysBetween(d.first_payment_date, d.deal_date);
            return { ...d, _days: days };
        }).filter((d) => d._days !== null && d._days >= 0)
            .sort((a, b) => a._days - b._days);
        const count = rows.length;
        const avg = count ? Math.round((rows.reduce((s, r) => s + r._days, 0) / count) * 10) / 10 : 0;
        const buckets = [
            { label: '۰ تا ۳ روز', test: (n) => n <= 3 },
            { label: '۴ تا ۷ روز', test: (n) => n >= 4 && n <= 7 },
            { label: '۸ تا ۱۴ روز', test: (n) => n >= 8 && n <= 14 },
            { label: '۱۵ تا ۳۰ روز', test: (n) => n >= 15 && n <= 30 },
            { label: 'بیش از ۳۰ روز', test: (n) => n > 30 }
        ].map((b) => ({ ...b, n: rows.filter((r) => b.test(r._days)).length }));
        const maxBucket = Math.max(1, ...buckets.map((b) => b.n));
        const dist = `<div class="dd-dist">${buckets.map((b) => `
            <div class="dd-dist-row">
                <span class="dd-dist-lbl">${b.label}</span>
                <span class="dd-dist-track"><span class="dd-dist-fill" style="width:${Math.round((b.n / maxBucket) * 100)}%"></span></span>
                <span class="dd-dist-val">${this.formatNumber(b.n)}</span>
            </div>`).join('')}</div>`;
        const metrics = `<div class="dd-metrics">
            <span class="dd-metric is-vol"><span class="dd-metric-val">${this.formatNumber(count)}</span><span class="dd-metric-lbl">معامله تبدیل‌شده</span></span>
            <span class="dd-metric is-money"><span class="dd-metric-val">${this.formatNumber(avg)}</span><span class="dd-metric-lbl">میانگین روز تا تبدیل</span></span>
        </div>`;
        const table = this.ddTable([
            { label: 'عنوان معامله', cell: (d) => this.ddDealLink(d) + this.ddLeadSub(d) },
            { label: 'تاریخ معامله', cell: (d) => this.formatKpiDate(d.deal_date || '') },
            { label: 'اولین پرداخت', cell: (d) => this.formatKpiDate(d.first_payment_date || '') },
            { label: 'روز تا تبدیل', num: true, cell: (d) => `<span class="dd-pill is-muted">${this.formatNumber(d._days)}</span>` },
            { label: 'مبلغ قرارداد', num: true, total: 'sum', key: 'payable_amount', cell: (d) => this.formatNumber(Number(d.payable_amount || 0)) },
            { label: 'مسئول', cell: (d) => this.escapeHtml(d.owner_name || '-') }
        ], rows);
        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            <div class="dd-note is-money" style="--role:var(--dd-money)">
                <div class="dd-note-title">میانگین زمان تبدیل</div>
                <div class="dd-note-body">فاصله روز از تاریخ ثبت معامله تا اولین پرداخت، برای هر معامله تبدیل‌شده.</div>
            </div>
            ${metrics}
            <div class="dd-section is-money">
                <div class="dd-section-head"><i class="fa-solid fa-chart-simple"></i> توزیع زمان تبدیل</div>
                ${dist}
            </div>
            <div class="dd-section is-money">
                <div class="dd-section-head"><i class="fa-solid fa-stopwatch"></i> فهرست معاملات</div>
                ${table}
            </div>
        `;
    },

    renderConversionAnalysisDrilldownContent(data) {
        const meta = data?.meta || {};
        const analysis = data?.conversion_analysis || {};
        const summary = analysis.summary || {};
        const reasons = Array.isArray(analysis.excluded_reasons_summary) ? analysis.excluded_reasons_summary : [];
        const includedDeals = Array.isArray(analysis.included_deals) ? analysis.included_deals : [];
        const excludedDeals = Array.isArray(analysis.excluded_deals) ? analysis.excluded_deals : [];
        const includedLeads = Array.isArray(analysis.included_leads) ? analysis.included_leads : [];
        const excludedLeads = Array.isArray(analysis.excluded_leads) ? analysis.excluded_leads : [];
        const criteria = analysis.criteria || {};

        const metricCards = `
            <div class="grid grid-cols-2 md:grid-cols-3 gap-3">
                ${this.renderConversionMetricCard('کل جامعه اولیه', summary.raw_cohort_count)}
                ${this.renderConversionMetricCard('حذف‌شده از محاسبه', summary.excluded_count)}
                ${this.renderConversionMetricCard('جامعه معتبر', summary.valid_cohort_count)}
                ${this.renderConversionMetricCard('تبدیل‌شده', summary.converted_count)}
                ${this.renderConversionMetricCard('تبدیل‌نشده', summary.not_converted_count)}
                ${this.renderConversionMetricCard('نرخ تبدیل', `${this.formatNumber(summary.conversion_rate || 0)}%`)}
            </div>
        `;

        const formulaHtml = `
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
                <div class="bg-gray-800 border border-gray-700 rounded p-3">
                    <div class="text-sm font-bold text-yellow-300 mb-2">${this.escapeHtml(analysis.formula_title || 'فرمول محاسبه')}</div>
                    <div class="text-sm text-gray-200 leading-7">${this.escapeHtml(analysis.formula_text || '-')}</div>
                </div>
                <div class="bg-gray-800 border border-gray-700 rounded p-3">
                    <div class="text-sm font-bold text-cyan-300 mb-2">معیارها و فیلترهای فعال</div>
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-2 text-xs">
                        ${this.renderConversionCriteriaItem('از تاریخ', this.formatKpiDate(criteria.date_from || ''))}
                        ${this.renderConversionCriteriaItem('تا تاریخ', this.formatKpiDate(criteria.date_to || ''))}
                        ${this.renderConversionCriteriaItem('مبنای تاریخ فروش', this.translateKpiCriteriaValue('sales_date_basis', criteria.sales_date_basis))}
                        ${this.renderConversionCriteriaItem('قاعده احتساب فروش', this.translateKpiCriteriaValue('sales_count_rule', criteria.sales_count_rule))}
                        ${this.renderConversionCriteriaItem('مبنای تاریخ مالی', this.translateKpiCriteriaValue('payment_date_basis', criteria.payment_date_basis))}
                        ${this.renderConversionCriteriaItem('قاعده احتساب مالی', this.translateKpiCriteriaValue('payment_count_rule', criteria.payment_count_rule))}
                        ${this.renderConversionCriteriaItem('وضعیت پرداخت', this.translateKpiCriteriaValue('payment_status_scope', criteria.payment_status_scope))}
                        ${this.renderConversionCriteriaItem('Legacy', this.translateKpiCriteriaValue('legacy_deal_scope', criteria.legacy_deal_scope))}
                    </div>
                </div>
            </div>
        `;

        const reasonsHtml = `
            <div class="space-y-2">
                <div class="text-sm font-bold text-orange-300">دلایل خروج از محاسبه / عدم تبدیل</div>
                ${reasons.length === 0 ? '<div class="text-xs text-gray-500">دلیلی ثبت نشده است.</div>' : `
                    <div class="overflow-x-auto border border-gray-700 rounded">
                        <table class="w-full text-xs">
                            <thead class="bg-gray-800">
                                <tr>
                                    <th class="p-2 border border-gray-700">عنوان دلیل</th>
                                    <th class="p-2 border border-gray-700">تعداد</th>
                                </tr>
                            </thead>
                            <tbody>
                                ${reasons.map(r => `
                                    <tr class="hover:bg-gray-800/60">
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(r.label || r.key || 'نامشخص')}</td>
                                        <td class="p-2 border border-gray-700">${this.formatNumber(Number(r.count || 0))}</td>
                                    </tr>
                                `).join('')}
                            </tbody>
                            ${this.renderKpiDrilldownTableFooter(reasons, [
                                { key: 'label' },
                                { key: 'count', total: 'sum' }
                            ])}
                        </table>
                    </div>
                `}
            </div>
        `;

        const includedSection = analysis.type === 'lead'
            ? this.renderConversionLeadTable('لیدهای محاسبه‌شده (تبدیل‌شده)', includedLeads, false)
            : this.renderConversionDealTable('معاملات محاسبه‌شده', includedDeals, false);

        const excludedSection = analysis.type === 'lead'
            ? this.renderConversionLeadTable('لیدهای خارج از تبدیل / محاسبه‌نشده', excludedLeads, true)
            : this.renderConversionDealTable('معاملات خارج از محاسبه / تبدیل‌نشده', excludedDeals, true);

        const compatibilityBlocks = `
            ${Array.isArray(data?.deals) && data.deals.length ? `
                <details class="bg-gray-800 border border-gray-700 rounded p-3">
                    <summary class="cursor-pointer text-sm text-indigo-300">لیست معاملات مرتبط برای بررسی سریع</summary>
                    <div class="mt-3">${this.renderConversionCompactDeals(data.deals)}</div>
                </details>
            ` : ''}
            ${Array.isArray(data?.leads) && data.leads.length ? `
                <details class="bg-gray-800 border border-gray-700 rounded p-3">
                    <summary class="cursor-pointer text-sm text-indigo-300">لیست لیدهای مرتبط برای بررسی سریع</summary>
                    <div class="mt-3">${this.renderConversionCompactLeads(data.leads)}</div>
                </details>
            ` : ''}
        `;

        return `
            ${this.renderDrilldownCriteriaControls()}
            ${this.renderDrilldownExportControls(data)}
            <div class="space-y-4">
                <div class="bg-gray-800 border border-gray-700 rounded p-3">
                    <div class="text-sm font-bold text-cyan-300 mb-1">${this.escapeHtml(meta.title || 'جزئیات نرخ تبدیل')}</div>
                    <div class="text-xs text-gray-400">در این بخش دقیقاً مشخص می‌شود چه رکوردهایی در محاسبه وارد شده‌اند، چه رکوردهایی حذف شده‌اند و علت هر مورد چیست.</div>
                </div>
                ${metricCards}
                ${formulaHtml}
                ${reasonsHtml}
                ${includedSection}
                ${excludedSection}
                ${compatibilityBlocks}
            </div>
        `;
    },

    renderConversionMetricCard(label, value) {
        return `
            <div class="bg-gray-800 border border-gray-700 rounded p-3">
                <div class="text-xs text-gray-400 mb-1">${this.escapeHtml(label || '-')}</div>
                <div class="text-lg font-bold text-cyan-300">${typeof value === 'number' ? this.formatNumber(value) : this.escapeHtml(String(value ?? '-'))}</div>
            </div>
        `;
    },

    renderConversionCriteriaItem(label, value) {
        return `
            <div class="bg-gray-900/50 border border-gray-700 rounded p-2">
                <div class="text-gray-400 mb-1">${this.escapeHtml(label || '-')}</div>
                <div class="text-gray-200">${this.escapeHtml(String(value || '-'))}</div>
            </div>
        `;
    },

    translateKpiCriteriaValue(kind, value) {
        const maps = {
            sales_date_basis: {
                ui_payment_date: 'فروش: تاریخ واریزی UI',
                db_payment_date: 'فروش: تاریخ پرداخت DB',
                system_created_at: 'فروش: تاریخ ثبت سیستمی'
            },
            payment_date_basis: {
                ui_payment_date: 'مالی: تاریخ واریزی UI',
                db_payment_date: 'مالی: تاریخ پرداخت DB',
                system_created_at: 'مالی: تاریخ ثبت سیستمی'
            },
            sales_count_rule: {
                first_payment_only: 'فروش: فقط اولین واریزی',
                all_payments: 'فروش: همه پرداخت‌ها'
            },
            payment_count_rule: {
                first_payment_only: 'مالی: فقط اولین واریزی',
                all_payments: 'مالی: همه پرداخت‌ها'
            },
            payment_status_scope: {
                confirmed_only: 'فقط تاییدشده',
                all_statuses: 'همه وضعیت‌ها'
            },
            legacy_deal_scope: {
                exclude: 'معاملات legacy حذف شود',
                all: 'همه معاملات',
                only: 'فقط معاملات legacy'
            }
        };
        return (maps[kind] && maps[kind][value]) ? maps[kind][value] : (value || '-');
    },

    renderConversionDealTable(title, rows, showReason = false) {
        return `
            <div class="space-y-2">
                <div class="text-sm font-bold text-indigo-300">${this.escapeHtml(title || 'معاملات')}</div>
                ${rows.length === 0 ? '<div class="text-xs text-gray-500">رکوردی وجود ندارد.</div>' : `
                    <div class="overflow-x-auto border border-gray-700 rounded">
                        <table class="w-full text-xs">
                            <thead class="bg-gray-800">
                                <tr>
                                    <th class="p-2 border border-gray-700">عنوان معامله</th>
                                    <th class="p-2 border border-gray-700">تاریخ ثبت معامله</th>
                                    <th class="p-2 border border-gray-700">اولین واریزی</th>
                                    <th class="p-2 border border-gray-700">نوع لید</th>
                                    <th class="p-2 border border-gray-700">وضعیت</th>
                                    <th class="p-2 border border-gray-700">مبلغ قرارداد</th>
                                    <th class="p-2 border border-gray-700">جمع پرداخت (طبق معیار)</th>
                                    ${showReason ? '<th class="p-2 border border-gray-700">علت</th>' : ''}
                                    <th class="p-2 border border-gray-700">عملیات</th>
                                </tr>
                            </thead>
                            <tbody>
                                ${rows.map(row => `
                                    <tr class="hover:bg-gray-800/60">
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.title || ('#' + (row.deal_id || '')))}</td>
                                        <td class="p-2 border border-gray-700">${this.formatKpiDate(row.deal_date || '')}</td>
                                        <td class="p-2 border border-gray-700">${this.formatKpiDate(row.first_payment_date || '') || '-'}</td>
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.lead_type || '-')}</td>
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.deal_status_group || '-')}</td>
                                        <td class="p-2 border border-gray-700">${this.formatNumber(Number(row.payable_amount || 0))}</td>
                                        <td class="p-2 border border-gray-700">${this.formatNumber(Number(row.total_paid || 0))}</td>
                                        ${showReason ? `<td class="p-2 border border-gray-700">${this.escapeHtml(row.exclude_reason_label || row.conversion_reason_label || '-')}</td>` : ''}
                                        <td class="p-2 border border-gray-700 text-center">
                                            <button class="text-blue-300 hover:text-blue-200 underline" onclick="app.openDealFromKpiDrilldown('${this.escapeHtml(row.didar_deal_id || row.deal_id || '')}')">باز کردن معامله</button>
                                        </td>
                                    </tr>
                                `).join('')}
                            </tbody>
                            ${this.renderKpiDrilldownTableFooter(rows, [
                                { key: 'title' },
                                { key: 'deal_date' },
                                { key: 'first_payment_date' },
                                { key: 'lead_type' },
                                { key: 'deal_status_group' },
                                { key: 'payable_amount', total: 'sum' },
                                { key: 'total_paid', total: 'sum' },
                                ...(showReason ? [{ key: 'exclude_reason_label' }] : []),
                                { key: 'actions', align: 'center' }
                            ])}
                        </table>
                    </div>
                `}
            </div>
        `;
    },

    renderConversionLeadTable(title, rows, showReason = false) {
        return `
            <div class="space-y-2">
                <div class="text-sm font-bold text-indigo-300">${this.escapeHtml(title || 'لیدها')}</div>
                ${rows.length === 0 ? '<div class="text-xs text-gray-500">رکوردی وجود ندارد.</div>' : `
                    <div class="overflow-x-auto border border-gray-700 rounded">
                        <table class="w-full text-xs">
                            <thead class="bg-gray-800">
                                <tr>
                                    <th class="p-2 border border-gray-700">نام لید</th>
                                    <th class="p-2 border border-gray-700">شماره</th>
                                    <th class="p-2 border border-gray-700">تاریخ ثبت لید</th>
                                    <th class="p-2 border border-gray-700">مسئول</th>
                                    <th class="p-2 border border-gray-700">تعداد معامله</th>
                                    <th class="p-2 border border-gray-700">معاملات دارای پرداخت</th>
                                    ${showReason ? '<th class="p-2 border border-gray-700">علت</th>' : ''}
                                    <th class="p-2 border border-gray-700">عملیات</th>
                                </tr>
                            </thead>
                            <tbody>
                                ${rows.map(row => `
                                    <tr class="hover:bg-gray-800/60">
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.full_name || '-')}</td>
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.mobile_phone || row.secondary_mobile_phone || '-')}</td>
                                        <td class="p-2 border border-gray-700">${this.formatKpiDate(row.lead_date || '')}</td>
                                        <td class="p-2 border border-gray-700">${this.escapeHtml(row.owner_name || '-')}</td>
                                        <td class="p-2 border border-gray-700">${this.formatNumber(Number(row.deal_count || 0))}</td>
                                        <td class="p-2 border border-gray-700">${this.formatNumber(Number(row.converted_deal_count || 0))}</td>
                                        ${showReason ? `<td class="p-2 border border-gray-700">${this.escapeHtml(row.exclude_reason_label || row.conversion_reason_label || '-')}</td>` : ''}
                                        <td class="p-2 border border-gray-700 text-center">
                                            <button class="text-blue-300 hover:text-blue-200 underline" onclick="app.openLeadFromKpiDrilldown('${this.escapeHtml(row.contact_id || '')}')">باز کردن لید</button>
                                        </td>
                                    </tr>
                                `).join('')}
                            </tbody>
                            ${this.renderKpiDrilldownTableFooter(rows, [
                                { key: 'full_name' },
                                { key: 'mobile_phone' },
                                { key: 'lead_date' },
                                { key: 'owner_name' },
                                { key: 'deal_count', total: 'sum' },
                                { key: 'converted_deal_count', total: 'sum' },
                                ...(showReason ? [{ key: 'exclude_reason_label' }] : []),
                                { key: 'actions', align: 'center' }
                            ])}
                        </table>
                    </div>
                `}
            </div>
        `;
    },

    renderConversionCompactDeals(deals) {
        return `
            <div class="overflow-x-auto border border-gray-700 rounded">
                <table class="w-full text-xs">
                    <thead class="bg-gray-800">
                        <tr>
                            <th class="p-2 border border-gray-700">عنوان</th>
                            <th class="p-2 border border-gray-700">تاریخ</th>
                            <th class="p-2 border border-gray-700">وضعیت</th>
                            <th class="p-2 border border-gray-700">قرارداد</th>
                            <th class="p-2 border border-gray-700">پرداخت</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${deals.slice(0, 100).map(deal => `
                            <tr>
                                <td class="p-2 border border-gray-700">${this.escapeHtml(deal.title || '-')}</td>
                                <td class="p-2 border border-gray-700">${this.formatKpiDate(deal.deal_date || '')}</td>
                                <td class="p-2 border border-gray-700">${this.escapeHtml(deal.deal_status_group || '-')}</td>
                                <td class="p-2 border border-gray-700">${this.formatNumber(Number(deal.payable_amount || 0))}</td>
                                <td class="p-2 border border-gray-700">${this.formatNumber(Number(deal.total_paid || 0))}</td>
                            </tr>
                        `).join('')}
                    </tbody>
                    ${this.renderKpiDrilldownTableFooter(deals.slice(0, 100), [
                        { key: 'title' },
                        { key: 'deal_date' },
                        { key: 'deal_status_group' },
                        { key: 'payable_amount', total: 'sum' },
                        { key: 'total_paid', total: 'sum' }
                    ])}
                </table>
            </div>
        `;
    },

    renderConversionCompactLeads(leads) {
        return `
            <div class="overflow-x-auto border border-gray-700 rounded">
                <table class="w-full text-xs">
                    <thead class="bg-gray-800">
                        <tr>
                            <th class="p-2 border border-gray-700">نام لید</th>
                            <th class="p-2 border border-gray-700">شماره</th>
                            <th class="p-2 border border-gray-700">تاریخ ثبت</th>
                            <th class="p-2 border border-gray-700">مسئول</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${leads.slice(0, 100).map(lead => `
                            <tr>
                                <td class="p-2 border border-gray-700">${this.escapeHtml(lead.full_name || '-')}</td>
                                <td class="p-2 border border-gray-700">${this.escapeHtml(lead.mobile_phone || lead.secondary_mobile_phone || '-')}</td>
                                <td class="p-2 border border-gray-700">${this.formatKpiDate(lead.lead_date || '')}</td>
                                <td class="p-2 border border-gray-700">${this.escapeHtml(lead.owner_name || '-')}</td>
                            </tr>
                        `).join('')}
                    </tbody>
                    ${this.renderKpiDrilldownTableFooter(leads.slice(0, 100), [
                        { key: 'full_name' },
                        { key: 'mobile_phone' },
                        { key: 'lead_date' },
                        { key: 'owner_name' }
                    ])}
                </table>
            </div>
        `;
    },

    isKpiProductPivotTable(tableId) {
        return [
            'product_sold_by_owner_day',
            'product_requests_by_owner_day',
            'product_failed_by_owner_day'
        ].includes(tableId);
    },

    buildKpiProductPivotModel(tableId) {
        const rows = Array.isArray(this.state.kpiAdvancedTables?.[tableId]) ? [...this.state.kpiAdvancedTables[tableId]] : [];
        const products = [...new Set(rows.map(r => String(r.product_name || 'نامشخص')))].sort((a, b) => a.localeCompare(b, 'fa'));

        const byKey = {};
        rows.forEach((row) => {
            const date = String(row.date || '');
            const ownerDidarId = String(row.owner_didar_id || '');
            const ownerName = String(row.owner_name || 'بدون مسئول');
            const productName = String(row.product_name || 'نامشخص');
            const key = `${date}__${ownerDidarId}__${ownerName}`;
            if (!byKey[key]) {
                byKey[key] = {
                    date,
                    owner_didar_id: ownerDidarId,
                    owner_name: ownerName,
                    productValues: {},
                    requested_total_count: 0,
                    sold_total_count: 0,
                    unsold_total_count: 0
                };
            }
            if (!byKey[key].productValues[productName]) {
                byKey[key].productValues[productName] = { n: 0, o: 0, t: 0 };
            }

            if (tableId === 'product_sold_by_owner_day') {
                byKey[key].productValues[productName].n += Number(row.sold_new_count || 0);
                byKey[key].productValues[productName].o += Number(row.sold_old_count || 0);
                byKey[key].productValues[productName].t += Number(row.sold_total_count || 0);
            } else if (tableId === 'product_requests_by_owner_day') {
                byKey[key].productValues[productName].n += Number(row.requested_new_count || 0);
                byKey[key].productValues[productName].o += Number(row.requested_old_count || 0);
                byKey[key].productValues[productName].t += Number(row.requested_total_count || 0);
                byKey[key].requested_total_count += Number(row.requested_total_count || 0);
                byKey[key].sold_total_count += Number(row.sold_total_count || 0);
                byKey[key].unsold_total_count += Number(row.unsold_total_count || 0);
            } else if (tableId === 'product_failed_by_owner_day') {
                byKey[key].productValues[productName].n += Number(row.failed_new_count || 0);
                byKey[key].productValues[productName].o += Number(row.failed_old_count || 0);
                byKey[key].productValues[productName].t += Number(row.failed_total_count || 0);
            }
        });

        let dataRows = Object.values(byKey);
        dataRows.sort((a, b) => {
            const d = String(a.date || '').localeCompare(String(b.date || ''));
            if (d !== 0) return d;
            return String(a.owner_name || '').localeCompare(String(b.owner_name || ''), 'fa');
        });

        const withOwnerTotals = [];
        const groups = {};
        dataRows.forEach((row) => {
            const ownerKey = `${row.owner_didar_id}__${row.owner_name}`;
            if (!groups[ownerKey]) groups[ownerKey] = [];
            groups[ownerKey].push(row);
        });

        dataRows.forEach((row) => withOwnerTotals.push(row));
        Object.keys(groups).forEach((ownerKey) => {
            const rowsByOwner = groups[ownerKey];
            const total = {
                date: 'جمع کارشناس',
                owner_didar_id: rowsByOwner[0].owner_didar_id,
                owner_name: `${rowsByOwner[0].owner_name} (جمع)`,
                productValues: {},
                requested_total_count: 0,
                sold_total_count: 0,
                unsold_total_count: 0,
                __owner_total__: true
            };
            products.forEach((productName) => {
                total.productValues[productName] = { n: 0, o: 0, t: 0 };
            });
            rowsByOwner.forEach((r) => {
                products.forEach((productName) => {
                    const pv = r.productValues[productName] || { n: 0, o: 0, t: 0 };
                    total.productValues[productName].n += Number(pv.n || 0);
                    total.productValues[productName].o += Number(pv.o || 0);
                    total.productValues[productName].t += Number(pv.t || 0);
                });
                total.requested_total_count += Number(r.requested_total_count || 0);
                total.sold_total_count += Number(r.sold_total_count || 0);
                total.unsold_total_count += Number(r.unsold_total_count || 0);
            });
            withOwnerTotals.push(total);
        });

        return { products, rows: withOwnerTotals };
    },

    renderKpiProductPivotGrid(tableId) {
        const model = this.buildKpiProductPivotModel(tableId);
        const products = model.products || [];
        const rows = model.rows || [];
        const isRequests = tableId === 'product_requests_by_owner_day';
        if (!this.state.kpiAdvancedRenderedRows) {
            this.state.kpiAdvancedRenderedRows = {};
        }
        this.state.kpiAdvancedRenderedRows[tableId] = rows;

        const headerProductCols = products.map((p) => `
            <th colspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">${this.escapeHtml(p)}</th>
        `).join('');
        const headerSubCols = products.map(() => `
            <th class="border border-gray-700 bg-gray-800 p-2 text-gray-300">ج</th>
            <th class="border border-gray-700 bg-gray-800 p-2 text-gray-300">ق</th>
        `).join('');

        const allTotals = {};
        products.forEach((p) => {
            allTotals[p] = { n: 0, o: 0, t: 0 };
        });
        let reqTotal = 0;
        let soldTotal = 0;
        let unsoldTotal = 0;

        const dataRows = rows.filter(r => !r.__owner_total__);
        dataRows.forEach((row) => {
            products.forEach((p) => {
                const pv = row.productValues?.[p] || { n: 0, o: 0, t: 0 };
                allTotals[p].n += Number(pv.n || 0);
                allTotals[p].o += Number(pv.o || 0);
                allTotals[p].t += Number(pv.t || 0);
            });
            reqTotal += Number(row.requested_total_count || 0);
            soldTotal += Number(row.sold_total_count || 0);
            unsoldTotal += Number(row.unsold_total_count || 0);
        });

        const bodyHtml = rows.map((row, idx) => {
            const isEven = idx % 2 === 0;
            const bgClass = row.__owner_total__ ? 'bg-indigo-900/20' : (isEven ? 'bg-gray-900/50' : 'bg-gray-900/30');
            const renderPivotMetricCell = (value, metricKey, productName = '') => {
                const formatted = this.formatNumber(Number(value || 0));
                if (row.__owner_total__) {
                    return `<span class="text-gray-300">${formatted}</span>`;
                }
                const encodedProduct = encodeURIComponent(String(productName || ''));
                return `<button type="button" class="text-cyan-300 hover:text-cyan-200 hover:underline transition-colors" onclick="app.openKpiAdvancedDrilldownWithProduct('${tableId}','${metricKey}',${idx},'${encodedProduct}')">${formatted}</button>`;
            };
            const productCells = products.map((p) => {
                const pv = row.productValues?.[p] || { n: 0, o: 0, t: 0 };
                const metricNew = tableId === 'product_sold_by_owner_day'
                    ? 'sold_new_count'
                    : (tableId === 'product_requests_by_owner_day' ? 'requested_new_count' : 'failed_new_count');
                const metricOld = tableId === 'product_sold_by_owner_day'
                    ? 'sold_old_count'
                    : (tableId === 'product_requests_by_owner_day' ? 'requested_old_count' : 'failed_old_count');
                return `
                    <td class="border border-gray-700/50 p-2 text-center text-gray-300">${renderPivotMetricCell(pv.n, metricNew, p)}</td>
                    <td class="border border-gray-700/50 p-2 text-center text-gray-300">${renderPivotMetricCell(pv.o, metricOld, p)}</td>
                `;
            }).join('');
            const rowSum = products.reduce((sum, p) => sum + Number(row.productValues?.[p]?.t || 0), 0);
            return `
                <tr class="${bgClass}">
                    <td class="border border-gray-700/50 p-2 text-center text-gray-300">${this.formatKpiDate(row.date || '')}</td>
                    <td class="border border-gray-700/50 p-2 text-center text-gray-300">${this.escapeHtml(row.owner_name || '-')}</td>
                    ${productCells}
                    ${isRequests ? `
                        <td class="border border-gray-700/50 p-2 text-center text-gray-300">${renderPivotMetricCell(row.requested_total_count, 'requested_total_count')}</td>
                        <td class="border border-gray-700/50 p-2 text-center text-gray-300">${renderPivotMetricCell(row.sold_total_count, 'sold_total_count')}</td>
                        <td class="border border-gray-700/50 p-2 text-center text-gray-300">${renderPivotMetricCell(row.unsold_total_count, 'unsold_total_count')}</td>
                    ` : ''}
                    <td class="border border-gray-700/50 p-2 text-center font-bold text-white">${renderPivotMetricCell(rowSum, (tableId === 'product_sold_by_owner_day' ? 'sold_total_count' : (tableId === 'product_requests_by_owner_day' ? 'requested_total_count' : 'failed_total_count')))}</td>
                </tr>
            `;
        }).join('');

        const footerProductCells = products.map((p) => `
            <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(allTotals[p].n || 0))}</td>
            <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(allTotals[p].o || 0))}</td>
        `).join('');
        const footerRowSum = products.reduce((sum, p) => sum + Number(allTotals[p].t || 0), 0);

        return `
            <div class="overflow-x-auto rounded-lg border border-gray-700 shadow-xl custom-scrollbar">
                <table class="w-full text-xs border-collapse min-w-[1100px]">
                    <thead>
                        <tr>
                            <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">تاریخ</th>
                            <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">کارشناس</th>
                            ${headerProductCols}
                            ${isRequests ? `
                                <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">کل درخواست</th>
                                <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">فروش‌رفته</th>
                                <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-gray-300">فروش‌نرفته</th>
                            ` : ''}
                            <th rowspan="2" class="border border-gray-700 bg-gray-800 p-2 text-white">جمع کل ردیف</th>
                        </tr>
                        <tr>${headerSubCols}</tr>
                    </thead>
                    <tbody>
                        ${bodyHtml || `<tr><td colspan="${2 + (products.length * 2) + (isRequests ? 4 : 1)}" class="p-6 text-center text-gray-500 italic">نتیجه‌ای برای این فیلتر وجود ندارد.</td></tr>`}
                    </tbody>
                    <tfoot>
                        <tr>
                            <td class="border border-gray-700 p-2 font-bold text-white bg-gray-800">جمع کل</td>
                            <td class="border border-gray-700 p-2 font-bold text-white bg-gray-800">-</td>
                            ${footerProductCells}
                            ${isRequests ? `
                                <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(reqTotal || 0))}</td>
                                <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(soldTotal || 0))}</td>
                                <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(unsoldTotal || 0))}</td>
                            ` : ''}
                            <td class="border border-gray-700 p-2 text-center font-bold text-white bg-gray-800">${this.formatNumber(Number(footerRowSum || 0))}</td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        `;
    },

    csvEscape(value) {
        const str = String(value ?? '');
        if (str.includes(',') || str.includes('"') || str.includes('\n')) {
            return `"${str.replace(/"/g, '""')}"`;
        }
        return str;
    },

    sumLostSales(lostSales) {
        if (!lostSales || lostSales.length === 0) return 0;
        return lostSales.reduce((sum, item) => sum + Number(item.total_lost || 0), 0);
    },

    sumLostSalesAmount(lostSales) {
        if (!lostSales || lostSales.length === 0) return 0;
        return lostSales.reduce((sum, item) => sum + (parseFloat(item.total_amount) || 0), 0);
    },

    getKpiOwnerOptions(selectedIds = []) {
        const selectedSet = new Set((selectedIds || []).map(v => String(v)));
        const allSelected = selectedSet.size === 0 || selectedSet.has('__all__');
        let options = `<option value="__all__" ${allSelected ? 'selected' : ''}>همه مسئول‌ها</option>`;
        if (this.state.kpiUsers && this.state.kpiUsers.length > 0) {
            const activeUsers = this.state.kpiUsers.filter(user => Number(user.is_active) === 1);
            activeUsers.forEach(user => {
                const name = user.display_name || user.email || (user.first_name + ' ' + user.last_name);
                const didarId = user.didar_user_id || user.owner_id || '';
                if (!didarId) return;
                const isSelected = selectedSet.has(String(didarId));
                options += `<option value="${didarId}" ${isSelected ? 'selected' : ''}>${name}</option>`;
            });
        }
        return options;
    },

    getKpiProductOptions(selectedIds = []) {
        const selectedSet = new Set((selectedIds || []).map(v => String(v)));
        const allSelected = selectedSet.size === 0 || selectedSet.has('__all__');
        let options = `<option value="__all__" ${allSelected ? 'selected' : ''}>همه محصولات</option>`;
        if (this.state.kpiProducts && this.state.kpiProducts.length > 0) {
            this.state.kpiProducts.forEach(product => {
                if (!product.id) return;
                const isSelected = selectedSet.has(String(product.id));
                options += `<option value="${product.id}" ${isSelected ? 'selected' : ''}>${product.name || 'محصول'}</option>`;
            });
        }
        return options;
    },

    getKpiDealStatusOptions(selectedValues = []) {
        const selectedSet = new Set((selectedValues || []).map(v => String(v)));
        const options = [
            { value: 'pending', label: 'جاری' },
            { value: 'won', label: 'موفق' },
            { value: 'lost', label: 'ناموفق' }
        ];
        return options.map(opt => (
            `<option value="${opt.value}" ${selectedSet.has(opt.value) ? 'selected' : ''}>${opt.label}</option>`
        )).join('');
    },

    getKpiDealLeadTypeOptions(selectedValues = []) {
        const selectedSet = new Set((selectedValues || []).map(v => String(v)));
        const options = [
            { value: 'new', label: 'معامله لید جدید' },
            { value: 'old', label: 'معامله لید قدیمی' }
        ];
        return options.map(opt => (
            `<option value="${opt.value}" ${selectedSet.has(opt.value) ? 'selected' : ''}>${opt.label}</option>`
        )).join('');
    },

    getMultiSelectedValues(elementId) {
        const element = document.getElementById(elementId);
        if (!element) return [];
        const selected = Array.from(element.selectedOptions || [])
            .map(opt => (opt?.value || '').trim())
            .filter(Boolean);
        const hasAll = selected.includes('__all__');
        if (hasAll) {
            return [];
        }
        return selected;
    },

    syncKpiMultiFiltersToDom() {
        const filters = this.state.kpiFilters || {};
        this.setMultiSelectValues('kpi-owner-filter', filters.owner_didar_ids || []);
        this.setMultiSelectValues('kpi-product-filter', filters.product_ids || []);
        this.setMultiSelectValues('kpi-deal-status-filter', filters.deal_statuses || []);
        this.setMultiSelectValues('kpi-deal-lead-type-filter', filters.deal_lead_types || []);
    },

    setMultiSelectValues(elementId, values) {
        const element = document.getElementById(elementId);
        if (!element) return;
        const valueSet = new Set((values || []).map(v => String(v)));
        const hasAll = valueSet.size === 0 || valueSet.has('__all__');
        Array.from(element.options || []).forEach(opt => {
            if (String(opt.value) === '__all__') {
                opt.selected = hasAll;
                return;
            }
            opt.selected = !hasAll && valueSet.has(String(opt.value));
        });
    },

    getKpiPickerConfigs() {
        return {
            owner: {
                key: 'owner',
                selectId: 'kpi-owner-filter',
                pickerId: 'kpi-owner-picker',
                title: 'انتخاب مسئول'
            },
            product: {
                key: 'product',
                selectId: 'kpi-product-filter',
                pickerId: 'kpi-product-picker',
                title: 'انتخاب محصول'
            },
            dealStatus: {
                key: 'dealStatus',
                selectId: 'kpi-deal-status-filter',
                pickerId: 'kpi-deal-status-picker',
                title: 'انتخاب وضعیت معامله'
            },
            dealLeadType: {
                key: 'dealLeadType',
                selectId: 'kpi-deal-lead-type-filter',
                pickerId: 'kpi-deal-lead-type-picker',
                title: 'انتخاب نوع معامله'
            }
        };
    },

    initKpiTagPickers() {
        this.ensureKpiPickerGlobalHandlers();
        const configs = this.getKpiPickerConfigs();
        Object.values(configs).forEach(cfg => this.renderKpiTagPicker(cfg.key));
    },

    ensureKpiPickerGlobalHandlers() {
        if (this._kpiPickerHandlersBound) return;
        this._kpiPickerHandlersBound = true;

        document.addEventListener('pointerdown', (event) => {
            if (this.isKpiPickerInteractionLocked()) {
                return;
            }
            if (!this.isEventInsideKpiPicker(event)) {
                this.closeAllKpiPickers();
            }
        });

        document.addEventListener('keydown', (event) => {
            if (event.key === 'Escape') {
                this.closeAllKpiPickers();
            }
            this.handleDrilldownKeydown(event);
        });
    },

    isDrilldownOpen() {
        const el = document.getElementById('modalKpiDrilldown');
        return !!el && !el.classList.contains('hidden');
    },

    onDrilldownOverlayClick(event) {
        // Dismiss only when the backdrop itself is clicked, not the shell content.
        if (event && event.target && event.target.id === 'modalKpiDrilldown') {
            this.closeModalFromUser('modalKpiDrilldown');
        }
    },

    handleDrilldownKeydown(event) {
        if (!this.isDrilldownOpen()) return;
        if (event.key === 'Escape') {
            event.stopPropagation();
            this.closeModalFromUser('modalKpiDrilldown');
            return;
        }
        if (event.key !== 'Tab') return;
        // Focus trap within the drill-down shell.
        const shell = document.querySelector('#modalKpiDrilldown .dd-shell');
        if (!shell) return;
        const focusables = Array.from(shell.querySelectorAll(
            'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"])'
        )).filter((node) => node.offsetParent !== null || node === document.activeElement);
        if (!focusables.length) return;
        const first = focusables[0];
        const last = focusables[focusables.length - 1];
        if (event.shiftKey && document.activeElement === first) {
            event.preventDefault();
            last.focus();
        } else if (!event.shiftKey && document.activeElement === last) {
            event.preventDefault();
            first.focus();
        }
    },

    focusDrilldownClose() {
        const shell = document.querySelector('#modalKpiDrilldown .dd-shell');
        // Do not steal focus if the user is already interacting inside the shell (e.g. criteria refresh).
        if (shell && document.activeElement && shell.contains(document.activeElement)) return;
        const btn = document.querySelector('#modalKpiDrilldown .dd-close');
        if (btn && typeof btn.focus === 'function') {
            try { btn.focus({ preventScroll: true }); } catch (e) { btn.focus(); }
        }
    },

    isEventInsideKpiPicker(event) {
        if (!event) return false;
        if (typeof event.composedPath === 'function') {
            const path = event.composedPath() || [];
            return path.some(node => node?.classList?.contains?.('kpi-tag-picker-root'));
        }
        return !!event.target?.closest?.('.kpi-tag-picker-root');
    },

    setKpiPickerInteractionLock(ms = 180) {
        this._kpiPickerInteractionLockUntil = Date.now() + Number(ms || 0);
    },

    isKpiPickerInteractionLocked() {
        return Number(this._kpiPickerInteractionLockUntil || 0) > Date.now();
    },

    renderKpiTagPicker(key) {
        const config = this.getKpiPickerConfigs()[key];
        if (!config) return;
        const picker = document.getElementById(config.pickerId);
        const select = document.getElementById(config.selectId);
        if (!picker || !select) return;
        if (!this.state.kpiPickerSearch) {
            this.state.kpiPickerSearch = {};
        }

        const allOptions = Array.from(select.options || []);
        const activeSearch = String(this.state.kpiPickerSearch[key] || '').trim().toLowerCase();
        const visibleOptions = activeSearch
            ? allOptions.filter(opt => String(opt.textContent || '').toLowerCase().includes(activeSearch))
            : allOptions;
        const selected = allOptions.filter(opt => opt.selected);
        const allOption = allOptions.find(opt => String(opt.value) === '__all__');
        const selectedReal = selected.filter(opt => String(opt.value) !== '__all__');
        const allSelected = !!allOption?.selected || selectedReal.length === 0;
        const isOpen = this.state.kpiPickerOpenKey === key;

        const selectedChips = allSelected
            ? `<span class="inline-flex items-center gap-1 bg-emerald-900/60 border border-emerald-700 text-emerald-100 px-2 py-1 rounded text-xs">همه</span>`
            : (selectedReal.length > 0 ? selectedReal.map(opt => `
                <span class="inline-flex items-center gap-1 bg-blue-900/60 border border-blue-700 text-blue-100 px-2 py-1 rounded text-xs">
                    <span class="truncate max-w-[120px]">${this.escapeHtml(opt.textContent || '')}</span>
                    <button type="button" class="text-blue-200 hover:text-white" onclick="app.removeKpiPickerValue('${key}','${String(opt.value)}', event)">×</button>
                </span>
            `).join('')
            : '<span class="text-xs text-gray-500">انتخاب نشده</span>');
        const selectedCountText = allSelected ? 'همه' : `${selectedReal.length} مورد`;

        const listItems = visibleOptions.map(opt => `
            <label class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-700 cursor-pointer text-sm">
                <input type="checkbox" ${opt.selected ? 'checked' : ''} onchange="app.toggleKpiPickerValue('${key}','${String(opt.value)}', this.checked, event)">
                <span class="text-gray-200">${this.escapeHtml(opt.textContent || '')}</span>
            </label>
        `).join('');

        picker.innerHTML = `
            <div class="kpi-tag-picker-root relative" onpointerdown="event.stopPropagation()" onclick="event.stopPropagation()">
                <button type="button" onclick="app.toggleKpiPicker('${key}', event)"
                    class="w-full min-h-[44px] bg-gray-800 border border-gray-700 rounded p-2 text-left">
                    <div class="flex items-center justify-between gap-2">
                        <div class="flex flex-wrap gap-1 flex-1">${selectedChips}</div>
                        <span class="text-[11px] text-gray-400 whitespace-nowrap">${selectedCountText}</span>
                        <span class="text-gray-400 text-xs">${isOpen ? '▲' : '▼'}</span>
                    </div>
                </button>
                <div id="kpi-picker-panel-${key}" class="${isOpen ? '' : 'hidden'} absolute z-20 mt-1 w-full bg-gray-900 border border-gray-700 rounded shadow-2xl">
                    <div class="p-2 border-b border-gray-700">
                        <div class="text-xs text-gray-400 mb-1">${config.title}</div>
                        <div class="flex items-center gap-2 mb-2">
                            <button type="button" class="text-[11px] px-2 py-1 rounded bg-emerald-700/70 hover:bg-emerald-600 text-white" onclick="app.selectAllKpiPickerValues('${key}', event)">همه</button>
                            <button type="button" class="text-[11px] px-2 py-1 rounded bg-gray-700 hover:bg-gray-600 text-white" onclick="app.clearKpiPickerValues('${key}', event)">پاک‌کردن</button>
                        </div>
                        <input id="kpi-picker-search-${key}" type="text" oninput="app.filterKpiPickerOptions('${key}', this.value)"
                            class="w-full bg-gray-800 border border-gray-700 rounded p-2 text-xs text-white" placeholder="جستجو..." value="${this.escapeHtml(this.state.kpiPickerSearch[key] || '')}">
                    </div>
                    <div id="kpi-picker-list-${key}" class="max-h-56 overflow-auto p-1">${listItems || '<div class="text-xs text-gray-500 p-2">نتیجه‌ای پیدا نشد.</div>'}</div>
                </div>
            </div>
        `;
    },

    toggleKpiPicker(key, event) {
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = (this.state.kpiPickerOpenKey === key) ? null : key;
        const configs = this.getKpiPickerConfigs();
        Object.values(configs).forEach(cfg => this.renderKpiTagPicker(cfg.key));
        if (this.state.kpiPickerOpenKey === key) {
            setTimeout(() => {
                const searchInput = document.getElementById(`kpi-picker-search-${key}`);
                if (searchInput) searchInput.focus();
            }, 0);
        }
    },

    closeAllKpiPickers() {
        if (!this.state.kpiPickerOpenKey) return;
        this.state.kpiPickerOpenKey = null;
        const configs = this.getKpiPickerConfigs();
        Object.values(configs).forEach(cfg => this.renderKpiTagPicker(cfg.key));
    },

    filterKpiPickerOptions(key, searchValue) {
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = key;
        if (!this.state.kpiPickerSearch) {
            this.state.kpiPickerSearch = {};
        }
        this.state.kpiPickerSearch[key] = String(searchValue || '');
        this.renderKpiTagPicker(key);
        setTimeout(() => {
            const searchInput = document.getElementById(`kpi-picker-search-${key}`);
            if (searchInput) {
                searchInput.focus();
                const len = searchInput.value.length;
                searchInput.setSelectionRange(len, len);
            }
        }, 0);
    },

    toggleKpiPickerValue(key, value, isChecked, event = null) {
        if (event) {
            event.stopPropagation();
        }
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = key;
        const config = this.getKpiPickerConfigs()[key];
        if (!config) return;
        const select = document.getElementById(config.selectId);
        if (!select) return;
        const options = Array.from(select.options || []);
        const normalizedValue = String(value);
        const match = options.find(opt => String(opt.value) === normalizedValue);
        if (!match) return;

        if (normalizedValue === '__all__') {
            options.forEach(opt => {
                opt.selected = String(opt.value) === '__all__' ? !!isChecked : false;
            });
            this.renderKpiTagPicker(key);
            return;
        }

        match.selected = !!isChecked;
        const allOption = options.find(opt => String(opt.value) === '__all__');
        if (allOption) {
            allOption.selected = false;
        }

        const selectedRealCount = options.filter(opt => String(opt.value) !== '__all__' && opt.selected).length;
        if (selectedRealCount === 0 && allOption) {
            allOption.selected = true;
        }
        this.renderKpiTagPicker(key);
        setTimeout(() => {
            const searchInput = document.getElementById(`kpi-picker-search-${key}`);
            if (searchInput) searchInput.focus();
        }, 0);
    },

    selectAllKpiPickerValues(key, event = null) {
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = key;
        const config = this.getKpiPickerConfigs()[key];
        if (!config) return;
        const select = document.getElementById(config.selectId);
        if (!select) return;
        const options = Array.from(select.options || []);
        options.forEach(opt => {
            opt.selected = String(opt.value) === '__all__';
        });
        this.renderKpiTagPicker(key);
    },

    clearKpiPickerValues(key, event = null) {
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = key;
        const config = this.getKpiPickerConfigs()[key];
        if (!config) return;
        const select = document.getElementById(config.selectId);
        if (!select) return;
        const options = Array.from(select.options || []);
        const hasAll = options.some(opt => String(opt.value) === '__all__');
        options.forEach(opt => {
            opt.selected = hasAll ? String(opt.value) === '__all__' : false;
        });
        this.renderKpiTagPicker(key);
    },

    removeKpiPickerValue(key, value, event) {
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        this.setKpiPickerInteractionLock();
        this.state.kpiPickerOpenKey = key;
        this.toggleKpiPickerValue(key, value, false);
    },

    renderKpiActiveFilterChips(filters = {}) {
        const chips = [];
        const ownerCount = Array.isArray(filters.owner_didar_ids) ? filters.owner_didar_ids.length : 0;
        const productCount = Array.isArray(filters.product_ids) ? filters.product_ids.length : 0;
        const statusCount = Array.isArray(filters.deal_statuses) ? filters.deal_statuses.length : 0;
        const typeCount = Array.isArray(filters.deal_lead_types) ? filters.deal_lead_types.length : 0;

        if (filters.date_from || filters.date_to) {
            chips.push(`بازه: ${this.formatKpiDate(filters.date_from || '')} تا ${this.formatKpiDate(filters.date_to || '')}`);
        }
        if (ownerCount > 0) chips.push(`مسئول: ${this.formatNumber(ownerCount)} مورد`);
        if (productCount > 0) chips.push(`محصول: ${this.formatNumber(productCount)} مورد`);
        if (statusCount > 0) chips.push(`وضعیت معامله: ${this.formatNumber(statusCount)} مورد`);
        if (typeCount > 0) chips.push(`نوع معامله: ${this.formatNumber(typeCount)} مورد`);

        if (chips.length === 0) {
            return '<div class="text-xs text-gray-500">فیلتر فعالی انتخاب نشده است.</div>';
        }

        return `
            <div class="flex flex-wrap gap-2">
                ${chips.map(c => `<span class="text-xs px-3 py-1 rounded-full bg-blue-900/60 border border-blue-700 text-blue-100">${this.escapeHtml(c)}</span>`).join('')}
            </div>
        `;
    },

    clearKpiFilters() {
        const range = this.getJalaliMonthRange();
        this.state.kpiFilters = {
            date_from: range.date_from,
            date_to: range.date_to,
            owner_didar_id: '',
            product_id: '',
            owner_didar_ids: [],
            product_ids: [],
            deal_statuses: [],
            deal_lead_types: [],
            criteria: this.getDefaultKpiCriteria()
        };
        this.loadKPIDashboard({ useState: true });
    },

    setKpiQuickRange(type) {
        const now = new Date();
        const toIso = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
        let fromDate = new Date(now);
        let toDate = new Date(now);

        if (type === 'week') {
            fromDate.setDate(now.getDate() - 6);
        } else if (type === 'month') {
            fromDate = new Date(now.getFullYear(), now.getMonth(), 1);
            toDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
        }

        this.state.kpiFilters = {
            ...(this.state.kpiFilters || {}),
            date_from: toIso(fromDate),
            date_to: toIso(toDate)
        };
        this.loadKPIDashboard({ useState: true });
    },

    formatKpiDate(dateValue) {
        const raw = String(dateValue || '').trim();
        if (!raw) return '-';
        const dateOnly = raw.length >= 10 ? raw.slice(0, 10) : raw;
        return toPersianDate(dateOnly) || dateOnly;
    },

    getFirstDayOfMonth() {
        const now = new Date();
        return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
    },

    getLastDayOfMonth() {
        const now = new Date();
        return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate()}`;
    },

    getJalaliMonthRange() {
        const fallback = {
            date_from: this.getFirstDayOfMonth(),
            date_to: this.getLastDayOfMonth()
        };
        if (typeof persianDate === 'undefined') {
            return fallback;
        }
        try {
            const start = new persianDate().startOf('month').toDate();
            const end = new persianDate().endOf('month').toDate();
            const format = (dateObj) => {
                const y = dateObj.getFullYear();
                const m = String(dateObj.getMonth() + 1).padStart(2, '0');
                const d = String(dateObj.getDate()).padStart(2, '0');
                return `${y}-${m}-${d}`;
            };
            return {
                date_from: format(start),
                date_to: format(end)
            };
        } catch (error) {
            console.warn('getJalaliMonthRange failed', error);
            return fallback;
        }
    },

    formatNumber(num) {
        if (num === null || num === undefined || num === '') return '0';
        // English (Latin) digits with comma grouping, app-wide policy.
        return new Intl.NumberFormat('en-US').format(num);
    },

    formatMoney(amount) {
        const value = Number(amount || 0);
        if (!Number.isFinite(value)) return '0';
        return `${this.formatNumber(Math.round(value))} تومان`;
    },
    
    // ============ SUPPORT MODULE ============
    
    /**
     * Open support modal
     */
    openSupportModal() {
        const contactId = window.currentContactId;
        if (!contactId) {
            Swal.fire('خطا', 'لطفاً ابتدا یک لید را انتخاب کنید', 'error');
            return;
        }
        
        // Reset form
        document.getElementById('support-contact-id').value = contactId;
        document.getElementById('support-id').value = '';
        document.getElementById('support-channel').value = '';
        document.getElementById('support-direction').value = '';
        document.getElementById('support-result-note').value = '';
        
        this.openModal('modalSupport');
    },
    
    /**
     * Save support log
     */
    async saveSupport(e) {
        e.preventDefault();
        
        const form = e.target;
        const data = new FormData(form);
        
        Swal.fire({
            title: 'در حال ثبت...',
            allowOutsideClick: false,
            didOpen: () => Swal.showLoading()
        });
        
        try {
            const res = await this.req('create_support', Object.fromEntries(data));
            Swal.close();
            
            if (res.status === 'success') {
                Swal.fire({
                    toast: true,
                    position: 'bottom-end',
                    icon: 'success',
                    title: 'پشتیبانی با موفقیت ثبت شد',
                    showConfirmButton: false,
                    timer: 2000
                });
                
                this.closeModal('modalSupport');
                
                // Reload support logs if in support tab
                const contactId = data.get('contact_id');
                if (contactId) {
                    await this.loadSupportLogs(contactId);
                }
            } else {
                Swal.fire('خطا', res.message || 'خطا در ثبت پشتیبانی', 'error');
            }
        } catch (error) {
            console.error('Error saving support:', error);
            Swal.close();
            Swal.fire('خطا', 'خطا در ثبت پشتیبانی: ' + error.message, 'error');
        }
    },
    
    /**
     * Load support logs for a contact
     */
    async loadSupportLogs(contactId) {
        try {
            const res = await this.req('get_support_logs', { contact_id: contactId });
            
            if (res.status === 'success') {
                this.renderSupportLogs(res.support_logs || []);
            } else {
                console.error('Failed to load support logs:', res.message);
                document.getElementById('prof-support').innerHTML = 
                    '<div class="text-center text-red-500 py-4">خطا در بارگذاری پشتیبانی‌ها</div>';
            }
        } catch (error) {
            console.error('Error loading support logs:', error);
            document.getElementById('prof-support').innerHTML =
                '<div class="text-center text-red-500 py-4">خطا در بارگذاری پشتیبانی‌ها</div>';
        }
    },

    // ===== Customer access requests (ticketing) =====
    accessStatusMeta(status) {
        // رنگ‌ها به‌صورت inline اعمال می‌شوند چون بیلد tailwind.min.css این کلاس‌های رنگی را ندارد (purge شده).
        const map = {
            pending: { label: 'در انتظار', icon: 'fa-clock', fg: '#fde047', bg: 'rgba(234,179,8,0.18)', bd: 'rgba(234,179,8,0.6)', accent: 'rgba(234,179,8,0.85)' },
            done: { label: 'تأیید‌شده', icon: 'fa-circle-check', fg: '#6ee7b7', bg: 'rgba(16,185,129,0.18)', bd: 'rgba(16,185,129,0.6)', accent: 'rgba(16,185,129,0.85)' },
            not_done: { label: 'رد‌شده', icon: 'fa-circle-xmark', fg: '#fca5a5', bg: 'rgba(239,68,68,0.18)', bd: 'rgba(239,68,68,0.6)', accent: 'rgba(239,68,68,0.85)' },
        };
        return map[status] || map.pending;
    },

    _accessStatusPill(st) {
        return `<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold whitespace-nowrap" style="background:${st.bg};color:${st.fg};border:1px solid ${st.bd};min-height:32px"><i class="fa-solid ${st.icon}"></i> ${st.label}</span>`;
    },

    // فرمت «تاریخ ساعت» با یک فاصله؛ آن را به «تاریخ - ساعت» تبدیل می‌کنیم.
    _accessFormatDate(value) {
        if (!value) return '';
        const dt = toPersianDateTime(value) || '';
        return dt.includes(' ') ? dt.replace(' ', ' - ') : dt;
    },
    _accessDateText(r) {
        return this._accessFormatDate(r.created_at) || '-';
    },
    // تایلِ تاریخ رسیدگی (تأیید/رد) — فقط وقتی درخواست توسط پشتیبانی نهایی شده باشد.
    // در هر دو کارت پشتیبانی و کارشناس نمایش داده می‌شود تا تاریخ تصمیم مشخص باشد.
    _accessResolvedTile(r) {
        const status = r.status || 'pending';
        if (status !== 'done' && status !== 'not_done') return '';
        const dateTxt = this._accessFormatDate(r.resolved_at);
        if (!dateTxt) return '';
        const label = status === 'done' ? 'تاریخ تأیید' : 'تاریخ رد';
        return this._accessTile(label, dateTxt, { ltr: true });
    },

    accessTabOrder: ['pending', 'done', 'not_done'],

    _accessCountByStatus(list) {
        const c = { pending: 0, done: 0, not_done: 0 };
        (Array.isArray(list) ? list : []).forEach(r => {
            const s = r.status || 'pending';
            if (c[s] !== undefined) c[s] += 1;
        });
        return c;
    },

    buildAccessTabBar(activeTab, counts, onclickName) {
        return this.accessTabOrder.map(s => {
            const meta = this.accessStatusMeta(s);
            const active = s === activeTab;
            const n = counts[s] || 0;
            const style = active ? ` style="background:${meta.bg};color:${meta.fg};border:1px solid ${meta.bd}"` : '';
            const cls = active ? 'font-bold' : 'border border-gray-700 text-gray-400 hover:text-white';
            return `<button type="button" onclick="${onclickName}('${s}')"${style} class="px-3 py-1.5 rounded-lg text-xs ${cls}">${meta.label} <span class="opacity-70">(${n})</span></button>`;
        }).join('');
    },

    renderAccessDealOptions(deals, loading = false) {
        const dealSelect = document.getElementById('access-deal-id');
        if (!dealSelect) return;
        const list = Array.isArray(deals) ? deals : [];
        if (loading) {
            dealSelect.innerHTML = '<option value="">در حال بارگذاری معامله‌ها...</option>';
            dealSelect.disabled = true;
            return;
        }
        dealSelect.disabled = false;
        if (!list.length) {
            dealSelect.innerHTML = '<option value="">معامله‌ای برای این مشتری ثبت نشده</option>';
            return;
        }
        dealSelect.innerHTML = '<option value="">-- انتخاب معامله --</option>' + list.map(d => {
            const id = d.id || d.deal_id || '';
            const title = d.title || ('معامله #' + id);
            const phone = d.contact_phone || d.mobile_phone || '';
            const label = phone ? `${title} | ${phone}` : title;
            return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(label)}</option>`;
        }).join('');
    },

    renderAccessPhoneOptions(person) {
        const phoneSelect = document.getElementById('access-phone');
        if (!phoneSelect) return;
        const p = person || {};
        const phones = [p.mobile_phone, p.secondary_mobile_phone, p.mobile_phone_3, p.mobile_phone_4, p.work_phone]
            .map(v => String(v || '').trim()).filter(Boolean);
        phoneSelect.innerHTML = '<option value="">-- انتخاب شماره --</option>' + phones.map(v => `<option value="${this.escapeHtml(v)}">${this.escapeHtml(v)}</option>`).join('');
    },

    async refreshAccessRequestContext(contactId, opts = {}) {
        const token = `${Date.now()}:${Math.random().toString(36).slice(2)}`;
        this._accessRequestContextToken = token;
        if (opts.showLoading) {
            this.renderAccessDealOptions([], true);
        }
        try {
            const res = await this.req('get_person_profile', { contact_id: contactId, deal_limit: 100, activity_limit: 1 }, {
                requestKey: `accessRequest:profile:${String(contactId || '')}`,
                silentAbort: true
            });
            if (res?.aborted || this._accessRequestContextToken !== token) return;
            if (res.status === 'success') {
                const person = res.person || {};
                this.state.currentLead = person;
                this.state.currentDeals = Array.isArray(res.deals) ? res.deals : [];
                this.renderAccessDealOptions(this.state.currentDeals);
                this.renderAccessPhoneOptions(person);
                const ownerName = (window.APP_USER && APP_USER.name) || person.owner_name || '';
                const ownerInput = document.getElementById('access-owner-name');
                if (ownerInput && !ownerInput.value) ownerInput.value = ownerName;
            } else {
                this.renderAccessDealOptions(this.state.currentDeals || []);
            }
        } catch (error) {
            this.renderAccessDealOptions(this.state.currentDeals || []);
        }
    },

    async openAccessRequestModal() {
        const contactId = window.currentContactId || (this.state.currentLead && this.state.currentLead.didar_contact_id) || '';
        if (!contactId) {
            Swal.fire('خطا', 'لطفاً ابتدا یک لید را انتخاب کنید', 'error');
            return;
        }
        const setVal = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
        setVal('access-contact-id', contactId);
        setVal('access-request-type', 'grant');
        setVal('access-description', '');
        // مسئول معامله: پیش‌فرض همان کاربری که درخواست را ایجاد می‌کند
        setVal('access-owner-name', (window.APP_USER && APP_USER.name) || '');
        // تاریخ درخواست: پیش‌فرض تاریخ امروز
        setVal('access-date', toPersianDate(new Date().toISOString()) || '');

        const currentLeadMatches = this.state.currentLead && String(this.state.currentLead.didar_contact_id || '') === String(contactId);
        const currentDeals = currentLeadMatches && Array.isArray(this.state.currentDeals) ? this.state.currentDeals : [];
        const needsDealRefresh = !currentLeadMatches || currentDeals.length === 0;
        this.renderAccessDealOptions(currentDeals, needsDealRefresh);
        this.renderAccessPhoneOptions(currentLeadMatches ? this.state.currentLead : {});
        this.openModal('modalAccessRequest');
        if (needsDealRefresh) {
            await this.refreshAccessRequestContext(contactId, { showLoading: false });
        }
    },

    accessRequestTypeMeta(type) {
        const key = String(type || 'grant').toLowerCase();
        return {
            grant: { label: 'افزودن دسترسی', icon: 'fa-user-plus' },
            revoke: { label: 'حذف دسترسی', icon: 'fa-user-minus' }
        }[key] || { label: 'افزودن دسترسی', icon: 'fa-user-plus' };
    },

    accessRequestDealLinkId(request) {
        const r = request || {};
        return String(r.deal_didar_id || r.didar_deal_id || r.deal_id || '').trim();
    },

    accessRequestDealButton(request) {
        const dealId = this.accessRequestDealLinkId(request);
        if (!dealId) return '';
        const title = request?.deal_title ? `باز کردن معامله: ${request.deal_title}` : 'باز کردن معامله';
        return `<button type="button"
                    onclick="app.openAccessRequestDeal('${this.escapeHtml(dealId)}')"
                    class="inline-flex items-center justify-center gap-2 rounded-lg text-xs font-bold transition-colors whitespace-nowrap"
                    style="background:rgba(234,179,8,0.13);color:#facc15;border:1px solid rgba(234,179,8,0.45);padding:7px 11px;min-height:32px"
                    title="${this.escapeHtml(title)}">
                    <i class="fa-solid fa-arrow-up-right-from-square"></i>
                    <span>باز کردن معامله</span>
                </button>`;
    },

    async openAccessRequestDeal(dealId) {
        const safeDealId = String(dealId || '').trim();
        if (!safeDealId) {
            Swal.fire('خطا', 'برای این درخواست معامله‌ای ثبت نشده است', 'warning');
            return;
        }
        await this.openDealModal(safeDealId, { initialTab: 'info', source: 'access_request' });
    },

    async saveAccessRequest(e) {
        e.preventDefault();
        const form = e.target;
        const data = Object.fromEntries(new FormData(form));
        Swal.fire({ title: 'در حال ارسال...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });
        try {
            const res = await this.req('create_access_request', data);
            Swal.close();
            if (res.status === 'success') {
                Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'درخواست برای پشتیبانی ارسال شد', showConfirmButton: false, timer: 2000 });
                this.closeModal('modalAccessRequest');
                // درخواست تازه در وضعیت «در انتظار» قرار می‌گیرد
                this.state.accessReqTab = 'pending';
                if (data.contact_id) await this.loadAccessRequests(data.contact_id);
            } else {
                Swal.fire('خطا', res.message || 'خطا در ثبت درخواست', 'error');
            }
        } catch (error) {
            Swal.close();
            Swal.fire('خطا', 'خطا در ثبت درخواست: ' + error.message, 'error');
        }
    },

    async loadAccessRequests(contactId) {
        const container = document.getElementById('prof-access-requests');
        if (!container) return;
        try {
            const res = await this.req('get_access_requests', { contact_id: contactId });
            if (res.status === 'success') {
                this.renderAccessRequests(res.requests || []);
            } else {
                container.innerHTML = '<div class="text-center text-red-500 py-4">خطا در بارگذاری درخواست‌ها</div>';
            }
        } catch (error) {
            container.innerHTML = '<div class="text-center text-red-500 py-4">خطا در بارگذاری درخواست‌ها</div>';
        }
    },

    setAccessReqTab(tab) {
        this.state.accessReqTab = tab;
        this.renderAccessRequests(this._accessReqCache || []);
    },

    _accessRequestCardHtml(r) {
        const st = this.accessStatusMeta(r.status);
        const date = this._accessDateText(r);
        const typeMeta = this.accessRequestTypeMeta(r.request_type);
        return `
            <div class="access-card relative overflow-hidden rounded-lg p-5 shadow-lg" style="background:#172334;border:1px solid rgba(148,163,184,0.28);box-shadow:0 14px 32px rgba(2,6,23,0.22)">
                <div class="absolute top-0 right-0 h-full w-1.5" style="background:${st.accent}"></div>
                <div class="flex items-center justify-between gap-3 flex-wrap">
                    <span class="access-chip inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold text-gray-100" style="background:rgba(55,65,81,0.72);border:1px solid rgba(148,163,184,0.34);min-height:32px">
                        <i class="fa-solid ${typeMeta.icon}"></i> ${this.escapeHtml(typeMeta.label)}
                    </span>
                    <div class="flex items-center gap-2 flex-wrap">
                        ${this.accessRequestDealButton(r)}
                        ${this._accessStatusPill(st)}
                    </div>
                </div>
                <div class="access-divider mt-3 mb-4" style="border-bottom:1px solid rgba(148,163,184,0.18)"></div>
                <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
                    ${this._accessTile('نوع درخواست', typeMeta.label)}
                    ${this._accessTile('معامله', r.deal_title || 'معامله')}
                    ${this._accessTile('شماره تماس لید', r.selected_phone, { ltr: true })}
                    ${this._accessTile('مسئول معامله', r.deal_owner_name)}
                    ${this._accessTile('درخواست‌دهنده', r.requested_by_display || r.requested_by_name)}
                    ${this._accessTile('تاریخ درخواست', date, { ltr: true })}
                    ${this._accessResolvedTile(r)}
                    ${r.description ? this._accessTile('توضیحات', r.description, { full: true }) : ''}
                </div>
                ${this._accessNoteBlock(r)}
            </div>`;
    },

    renderAccessRequests(list) {
        const container = document.getElementById('prof-access-requests');
        if (!container) return;
        this._accessReqCache = Array.isArray(list) ? list : [];
        if (!this._accessReqCache.length) {
            container.innerHTML = '<div class="text-center text-gray-500 py-4">درخواستی ثبت نشده است</div>';
            return;
        }
        const counts = this._accessCountByStatus(this._accessReqCache);
        const active = this.state.accessReqTab || 'pending';
        const tabBar = this.buildAccessTabBar(active, counts, 'app.setAccessReqTab');
        const items = this._accessReqCache.filter(r => (r.status || 'pending') === active);
        const body = items.length
            ? items.map(r => this._accessRequestCardHtml(r)).join('')
            : '<div class="text-center text-gray-500 py-6 text-sm">درخواستی در این وضعیت نیست</div>';
        container.innerHTML = `<div class="flex gap-2 mb-3 flex-wrap">${tabBar}</div><div class="space-y-3">${body}</div>`;
    },

    // ----- Support inbox (dedicated section) -----
    async loadAccessRequestInbox() {
        const container = document.getElementById('access-inbox-list');
        if (!container) return;
        container.innerHTML = '<div class="text-center text-gray-500 py-10 col-span-full">در حال بارگذاری...</div>';
        try {
            // همهٔ درخواست‌ها لود می‌شوند و در سمت کلاینت بر اساس تب وضعیت تفکیک می‌گردند
            const res = await this.req('get_access_request_inbox', { status: '' });
            if (res.status === 'success') {
                this.renderAccessInbox(res.requests || []);
            } else {
                container.innerHTML = `<div class="text-center text-red-500 py-6 col-span-full">${this.escapeHtml(res.message || 'خطا در بارگذاری')}</div>`;
            }
        } catch (error) {
            container.innerHTML = '<div class="text-center text-red-500 py-6 col-span-full">خطا در بارگذاری</div>';
        }
    },

    setAccessInboxTab(tab) {
        this.state.accessInboxTab = tab;
        this.renderAccessInbox(this._accessInboxCache || []);
    },

    _accessTile(label, value, opts) {
        const o = opts || {};
        const span = o.full ? ' sm:col-span-2 lg:col-span-3' : '';
        const ltr = o.ltr ? ' dir="ltr"' : '';
        return `<div class="access-tile min-w-0 px-4 py-3 rounded-lg${span}" style="background:rgba(15,23,42,0.72);border:1px solid rgba(148,163,184,0.14)">
            <div class="access-tile-label text-[11px] font-bold" style="color:#9ca3af;margin-bottom:8px">${label}</div>
            <div class="access-tile-value text-sm font-semibold break-words leading-6" style="color:#f3f4f6;text-align:right${o.valueStyle ? ';' + o.valueStyle : ''}"${ltr}>${this.escapeHtml(value || '-')}</div>
        </div>`;
    },

    _accessNoteBlock(r) {
        if (!r.resolution_note) return '';
        const isReject = (r.status || 'pending') === 'not_done';
        const label = isReject ? 'علت رد درخواست' : 'یادداشت پشتیبانی';
        const valueColor = isReject ? '#fca5a5' : '#6ee7b7';
        return `<div class="access-tile mt-3 px-4 py-3 rounded-lg" style="background:rgba(15,23,42,0.72);border:1px solid rgba(148,163,184,0.14)">
            <div class="access-tile-label text-[11px] font-bold" style="color:#9ca3af;margin-bottom:8px">${label}</div>
            <div class="access-note-value ${isReject ? 'is-reject' : 'is-approve'} text-sm font-semibold break-words leading-7" style="color:${valueColor}">${this.escapeHtml(r.resolution_note)}</div>
        </div>`;
    },

    _accessInboxCardHtml(r) {
        const st = this.accessStatusMeta(r.status);
        const date = this._accessDateText(r);
        const status = r.status || 'pending';
        const typeMeta = this.accessRequestTypeMeta(r.request_type);
        // فقط درخواست‌های «در انتظار» قابل اعمال وضعیت‌اند؛ پس از تأیید یا رد، درخواست قفل می‌شود.
        const actionsFooter = (status === 'pending')
            ? `<div class="flex items-center justify-start gap-3 mt-4 pt-4 flex-wrap" style="border-top:1px solid rgba(148,163,184,0.18)">
                    <button type="button" onclick="app.setAccessRequestStatus(${r.id}, 'done')" class="rounded-lg text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="background:rgba(16,185,129,0.18);color:#6ee7b7;border:1px solid rgba(16,185,129,0.6);padding:10px 16px;min-width:92px;min-height:44px"><i class="fa-solid fa-check"></i> تأیید</button>
                    <button type="button" onclick="app.setAccessRequestStatus(${r.id}, 'not_done')" class="rounded-lg text-sm font-bold inline-flex items-center justify-center gap-2 transition-colors whitespace-nowrap" style="background:rgba(239,68,68,0.18);color:#fca5a5;border:1px solid rgba(239,68,68,0.6);padding:10px 16px;min-width:92px;min-height:44px"><i class="fa-solid fa-xmark"></i> رد</button>
               </div>`
            : '';
        return `
            <div class="access-card relative overflow-hidden rounded-lg p-5 shadow-lg" style="background:#172334;border:1px solid rgba(148,163,184,0.28);box-shadow:0 14px 32px rgba(2,6,23,0.22)">
                <div class="absolute top-0 right-0 h-full w-1.5" style="background:${st.accent}"></div>
                <div class="flex items-center justify-between gap-3 flex-wrap">
                    <span class="access-chip inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold text-gray-100" style="background:rgba(55,65,81,0.72);border:1px solid rgba(148,163,184,0.34);min-height:32px">
                        <i class="fa-solid ${typeMeta.icon}"></i> ${this.escapeHtml(typeMeta.label)}
                    </span>
                    <div class="flex items-center gap-2 flex-wrap">
                        ${this.accessRequestDealButton(r)}
                        ${this._accessStatusPill(st)}
                    </div>
                </div>
                <div class="access-divider mt-3 mb-4" style="border-bottom:1px solid rgba(148,163,184,0.18)"></div>
                <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
                    ${this._accessTile('نوع درخواست', typeMeta.label)}
                    ${this._accessTile('معامله', r.deal_title)}
                    ${this._accessTile('شماره تماس لید', r.selected_phone, { ltr: true })}
                    ${this._accessTile('اسم لید', r.contact_name || 'مشتری')}
                    ${this._accessTile('درخواست‌دهنده', r.requested_by_display || r.requested_by_name)}
                    ${this._accessTile('تاریخ درخواست', date, { ltr: true })}
                    ${this._accessResolvedTile(r)}
                    ${r.description ? this._accessTile('توضیحات', r.description, { full: true }) : ''}
                </div>
                ${this._accessNoteBlock(r)}
                ${actionsFooter}
            </div>`;
    },

    renderAccessInbox(list) {
        const container = document.getElementById('access-inbox-list');
        if (!container) return;
        this._accessInboxCache = Array.isArray(list) ? list : [];
        if (!this._accessInboxCache.length) {
            container.innerHTML = '<div class="text-center text-gray-500 py-10 col-span-full">درخواستی وجود ندارد</div>';
            return;
        }
        const counts = this._accessCountByStatus(this._accessInboxCache);
        const active = this.state.accessInboxTab || 'pending';
        const tabBar = this.buildAccessTabBar(active, counts, 'app.setAccessInboxTab');
        const items = this._accessInboxCache.filter(r => (r.status || 'pending') === active);
        const body = items.length
            ? items.map(r => this._accessInboxCardHtml(r)).join('')
            : '<div class="text-center text-gray-500 py-10 col-span-full text-sm">درخواستی در این وضعیت نیست</div>';
        container.innerHTML = `<div class="col-span-full flex gap-2 mb-1 flex-wrap">${tabBar}</div>${body}`;
    },

    async setAccessRequestStatus(id, status) {
        let resolutionNote = '';
        // رد کردن نیازمند توضیح علت است؛ این علت به کارشناس نمایش داده می‌شود.
        if (status === 'not_done') {
            const result = await Swal.fire({
                title: 'علت رد درخواست',
                input: 'textarea',
                inputPlaceholder: 'لطفاً علت رد شدن این درخواست را بنویسید...',
                inputAttributes: { 'aria-label': 'علت رد درخواست', style: 'min-height:110px' },
                showCancelButton: true,
                confirmButtonText: 'ثبت رد درخواست',
                cancelButtonText: 'انصراف',
                confirmButtonColor: '#ef4444',
                inputValidator: (v) => (!v || !v.trim()) ? 'وارد کردن علت رد الزامی است' : undefined,
            });
            if (!result.isConfirmed) return;
            resolutionNote = (result.value || '').trim();
        }
        try {
            const res = await this.req('set_access_request_status', { id, status, resolution_note: resolutionNote });
            if (res.status === 'success') {
                Swal.fire({ toast: true, position: 'bottom-end', icon: 'success', title: 'وضعیت به‌روزرسانی شد', showConfirmButton: false, timer: 1500 });
                await this.loadAccessRequestInbox();
            } else {
                Swal.fire('خطا', res.message || 'خطا در تغییر وضعیت', 'error');
            }
        } catch (error) {
            Swal.fire('خطا', 'خطا در تغییر وضعیت: ' + error.message, 'error');
        }
    },

    /**
     * Render support logs as cards
     */
    renderSupportLogs(logs) {
        const container = document.getElementById('prof-support');
        
        if (!logs || logs.length === 0) {
            container.innerHTML = '<div class="text-center text-gray-500 py-4">هیچ پشتیبانی ثبت نشده است</div>';
            return;
        }
        
        const isAdmin = window.APP_USER?.role === 'admin';
        
        const channelLabels = {
            'call': 'تماس',
            'chat': 'چت',
            'visit': 'حضوری',
            'email': 'ایمیل'
        };
        
        const directionLabels = {
            'incoming': 'ورودی',
            'outgoing': 'خروجی'
        };
        
        const channelIcons = {
            'call': 'fa-phone',
            'chat': 'fa-comment',
            'visit': 'fa-user',
            'email': 'fa-envelope'
        };
        
        const html = logs.map(log => {
            const channelLabel = channelLabels[log.channel] || log.channel;
            const directionLabel = directionLabels[log.direction] || log.direction;
            const channelIcon = channelIcons[log.channel] || 'fa-circle';
            const createdAt = log.created_at ? toPersianDateTime(log.created_at) : '-';
            const creatorName = log.creator_name || 'نامشخص';
            // Shared accent by direction (mobile-only CSS; inert on desktop).
            const supClass = log.direction === 'outgoing' ? 's--success' : 's--info';

            return `
                <div class="glass p-4 rounded-lg border border-gray-700/50 accent-card ${supClass}">
                    <div class="flex justify-between items-start mb-3">
                        <div class="flex items-center gap-3">
                            <div class="w-10 h-10 rounded-full bg-blue-600/20 flex items-center justify-center">
                                <i class="fas ${channelIcon} text-blue-400"></i>
                            </div>
                            <div>
                                <div class="font-bold text-white">${channelLabel} - ${directionLabel}</div>
                                <div class="text-xs text-gray-400 flex items-center gap-2 mt-1">
                                    <i class="fa-solid fa-user text-[10px]"></i>
                                    <span>${creatorName}</span>
                                    <span class="mx-1">•</span>
                                    <i class="fa-solid fa-clock text-[10px]"></i>
                                    <span>${createdAt}</span>
                                </div>
                            </div>
                        </div>
                        ${isAdmin ? `
                            <button onclick="app.editSupportLog(${log.id})" 
                                    class="text-gray-400 hover:text-yellow-500 transition px-2">
                                <i class="fa-solid fa-edit"></i>
                            </button>
                        ` : ''}
                    </div>
                    <div class="text-sm text-gray-300 whitespace-pre-line bg-gray-900/50 p-3 rounded">
                        ${this.escapeHtml(log.result_note || '')}
                    </div>
                </div>
            `;
        }).join('');
        
        container.innerHTML = html;
    },
    
    /**
     * Edit support log (admin only)
     */
    async editSupportLog(id) {
        // TODO: Implement edit functionality if needed
        Swal.fire('اطلاعات', 'قابلیت ویرایش به زودی اضافه می‌شود', 'info');
    },
    
    /**
     * Escape HTML to prevent XSS
     */
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // ============ END SUPPORT MODULE ============
};

if (typeof window !== 'undefined') {
    window.app = app;
}

// Initialize on page load
// Note: User initialization is handled in the layout file

// ============ MOBILE UX IMPROVEMENTS ============

/**
 * Initialize mobile viewport height fix
 * Accounts for mobile browser chrome (address bar, nav bar)
 */
function initMobileViewportHeight() {
    const setVh = () => {
        document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
    };
    
    // Set on load
    setVh();
    
    // Set on resize (debounced)
    let resizeTimer;
    window.addEventListener('resize', () => {
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(setVh, 200);
    });
}

/**
 * Initialize swipe gestures for mobile
 * Swipe right to open sidebar, swipe left to close
 */
function initSwipeGestures() {
    if (typeof window === 'undefined') return;
    return; // Disabled: swipe gestures conflict with mobile scroll/touch UX
    
    let touchStartX = 0;
    let touchEndX = 0;
    let touchStartTime = 0;
    const swipeThreshold = 50; // Minimum distance for swipe
    const timeThreshold = 300; // Maximum time for swipe (ms)
    
    // Only enable on mobile
    const isMobile = () => window.innerWidth < 768;
    
    document.addEventListener('touchstart', (e) => {
        if (!isMobile()) return;
        touchStartX = e.changedTouches[0].screenX;
        touchStartTime = Date.now();
    }, { passive: true });
    
    document.addEventListener('touchend', (e) => {
        if (!isMobile()) return;
        touchEndX = e.changedTouches[0].screenX;
        const touchEndTime = Date.now();
        const swipeTime = touchEndTime - touchStartTime;
        
        if (swipeTime > timeThreshold) return; // Too slow, not a swipe
        
        const swipeDistance = touchStartX - touchEndX;
        
        // Swipe left (close sidebar)
        if (swipeDistance < -swipeThreshold && swipeDistance < 0) {
            const sidebar = document.getElementById('main-sidebar');
            if (sidebar && !sidebar.classList.contains('translate-x-full')) {
                if (typeof app !== 'undefined' && typeof app.closeSidebar === 'function') {
                    app.closeSidebar();
                }
            }
        }
        
        // Swipe right (open sidebar)
        if (swipeDistance > swipeThreshold && swipeDistance > 0) {
            const sidebar = document.getElementById('main-sidebar');
            if (sidebar && sidebar.classList.contains('translate-x-full')) {
                // Only open if not already open and not in a modal
                const anyModalOpen = document.querySelector('[id^="modal"]:not(.hidden)');
                if (!anyModalOpen) {
                    if (typeof app !== 'undefined' && typeof app.toggleSidebar === 'function') {
                        app.toggleSidebar();
                    }
                }
            }
        }
    }, { passive: true });
}

/**
 * Mobile gesture engine (safe rewrite of the old, disabled initSwipeGestures).
 *
 * Lessons from the previous version (which broke scrolling and is left
 * disabled above):
 *  - NEVER call preventDefault on touchmove — this app scrolls inside inner
 *    containers, so blocking the page touchmove killed native scrolling.
 *  - A horizontal swipe must be horizontal-DOMINANT, or vertical scrolls get
 *    misread as swipes.
 *
 * This engine is 100% passive (no preventDefault anywhere) and heavily guarded,
 * so it can never block a scroll. Two gestures:
 *   1) Swipe left/right on the page → switch to the prev/next bottom-nav tab
 *      (reuses each tab's real onclick, so navigation/RBAC behaviour is identical).
 *   2) Drag the bottom-sheet grip/header down → dismiss the sheet.
 */
function initMobileGestures() {
    if (typeof window === 'undefined' || typeof document === 'undefined') return;
    if (window.__mobileGesturesInit) return;
    window.__mobileGesturesInit = true;

    var isMobile = function () { return window.innerWidth < 768; };
    // Zones where a horizontal drag means something else (scrollers, inputs,
    // overlays, the nav itself) — starting a swipe here is ignored.
    var NO_SWIPE = '.overflow-x-auto, .no-scrollbar, table, input, select, textarea,' +
        ' .bottom-sheet, .swipe-content, [data-no-swipe], #mobile-bottom-nav, #mobile-fab, #main-sidebar, [id^="modal"]';

    /* ---- 1) Swipe to switch bottom-nav tabs ---- */
    var sx = 0, sy = 0, st = 0, tracking = false, locked = false, isH = false;

    document.addEventListener('touchstart', function (e) {
        if (!isMobile() || e.touches.length !== 1) { tracking = false; return; }
        var t = e.target;
        if (t && t.closest && t.closest(NO_SWIPE)) { tracking = false; return; }
        // Don't switch while an overlay/sheet/sidebar is open.
        if (document.querySelector('.bottom-sheet.active') ||
            document.querySelector('[id^="modal"]:not(.hidden)')) { tracking = false; return; }
        var sb = document.getElementById('main-sidebar');
        if (sb && !sb.classList.contains('translate-x-full')) { tracking = false; return; }
        // Leave the screen edges to the sidebar edge-swipe (gesture 4).
        var x0 = e.touches[0].clientX;
        if (x0 > window.innerWidth - 40 || x0 < 40) { tracking = false; return; }
        sx = x0; sy = e.touches[0].clientY; st = Date.now();
        tracking = true; locked = false; isH = false;
    }, { passive: true });

    document.addEventListener('touchmove', function (e) {
        if (!tracking || e.touches.length !== 1) return;
        var dx = e.touches[0].clientX - sx, dy = e.touches[0].clientY - sy;
        if (!locked && (Math.abs(dx) > 12 || Math.abs(dy) > 12)) {
            locked = true;
            isH = Math.abs(dx) > Math.abs(dy) * 1.8; // horizontal-dominant only
            if (!isH) tracking = false; // it's a scroll — bail, never preventDefault
        }
    }, { passive: true });

    document.addEventListener('touchend', function (e) {
        if (!tracking || !isH) { tracking = false; return; }
        tracking = false;
        var dx = e.changedTouches[0].clientX - sx;
        var dy = e.changedTouches[0].clientY - sy;
        if (Date.now() - st > 600) return;
        if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy) * 1.8) return;
        switchTab(dx < 0 ? 1 : -1);
    }, { passive: true });

    function switchTab(dir) {
        var tabs = [].slice.call(document.querySelectorAll('#mobile-bottom-nav .mobile-tab'))
            .filter(function (b) { return b.id !== 'tab-more' && b.offsetParent !== null; });
        if (tabs.length < 2) return;
        var idx = -1;
        for (var i = 0; i < tabs.length; i++) { if (tabs[i].classList.contains('active')) { idx = i; break; } }
        if (idx < 0) return;
        var next = idx + dir;
        if (next >= tabs.length) {
            // Swiped past the last tab → open the "بیشتر" menu. This is the
            // reliable way to open it: mobile browsers hijack the actual screen
            // edge for their own back-gesture, so edge-swipe alone is flaky.
            try { if (typeof app !== 'undefined' && app.toggleSidebar) { if (app.haptic) app.haptic(); app.toggleSidebar(); } } catch (err) {}
            return;
        }
        if (next < 0) return; // no wrap before the first tab
        try { if (typeof app !== 'undefined' && app.haptic) app.haptic(); } catch (err) {}
        tabs[next].click();
        var view = document.querySelector('.view-section:not(.hidden)');
        if (view) {
            view.classList.remove('view-swipe-in-l', 'view-swipe-in-r');
            void view.offsetWidth;
            view.classList.add(dir < 0 ? 'view-swipe-in-l' : 'view-swipe-in-r');
        }
    }

    /* ---- 2) Drag the bottom-sheet grip/header down to dismiss ---- */
    var sheetEl = null, gy = 0, gdy = 0, gDrag = false;
    document.addEventListener('touchstart', function (e) {
        var h = e.target && e.target.closest && e.target.closest('.bottom-sheet-grip, .bottom-sheet-header');
        if (!h) { gDrag = false; return; }
        sheetEl = h.closest('.bottom-sheet');
        if (!sheetEl) { gDrag = false; return; }
        gy = e.touches[0].clientY; gdy = 0; gDrag = true;
        sheetEl.style.transition = 'none';
    }, { passive: true });
    document.addEventListener('touchmove', function (e) {
        if (!gDrag || !sheetEl) return;
        gdy = e.touches[0].clientY - gy;
        if (gdy > 0) sheetEl.style.transform = 'translateY(' + gdy + 'px)';
    }, { passive: true });
    document.addEventListener('touchend', function () {
        if (!gDrag || !sheetEl) return;
        gDrag = false;
        var el = sheetEl; sheetEl = null;
        el.style.transition = '';
        if (gdy > 90) {
            try { if (typeof app !== 'undefined' && app.sheet && app.sheet.close) app.sheet.close(); } catch (err) {}
        } else {
            el.style.transform = '';
        }
    }, { passive: true });

    /* ---- 3) Swipe a card to reveal its quick actions ---- */
    var scEl = null, cx = 0, cy = 0, cBase = 0, cW = 0, cLock = false, cIsH = false, cTx = 0;
    function closeSwipe(el) { if (!el) return; el.classList.remove('is-open'); el.style.transition = ''; el.style.transform = ''; }
    function closeAllSwipe(except) {
        [].forEach.call(document.querySelectorAll('.swipe-content.is-open'), function (el) { if (el !== except) closeSwipe(el); });
    }
    document.addEventListener('touchstart', function (e) {
        if (!isMobile() || e.touches.length !== 1) { scEl = null; return; }
        var c = e.target && e.target.closest && e.target.closest('.swipe-content');
        if (!c) { closeAllSwipe(null); scEl = null; return; }
        var row = c.closest('.swipe-row');
        var acts = row && row.querySelector('.swipe-actions');
        cW = acts ? acts.offsetWidth : 0;
        if (!cW) { scEl = null; return; }
        if (scEl && scEl !== c) closeSwipe(scEl);
        scEl = c; cx = e.touches[0].clientX; cy = e.touches[0].clientY;
        cBase = c.classList.contains('is-open') ? cW : 0;
        cLock = false; cIsH = false; cTx = cBase;
        c.style.transition = 'none';
    }, { passive: true });
    document.addEventListener('touchmove', function (e) {
        if (!scEl) return;
        var dx = e.touches[0].clientX - cx, dy = e.touches[0].clientY - cy;
        if (!cLock && (Math.abs(dx) > 10 || Math.abs(dy) > 10)) {
            cLock = true; cIsH = Math.abs(dx) > Math.abs(dy) * 1.4;
            if (!cIsH) { scEl.style.transition = ''; scEl = null; return; }
        }
        if (!cIsH) return;
        // Actions live at the inline-end; reveal by sliding content toward
        // inline-start (positive translateX in this RTL UI). Clamp to [0, cW].
        cTx = cBase + dx; if (cTx < 0) cTx = 0; if (cTx > cW) cTx = cW;
        scEl.style.transform = 'translateX(' + cTx + 'px)';
    }, { passive: true });
    document.addEventListener('touchend', function () {
        if (!scEl) return;
        var c = scEl; scEl = null; c.style.transition = '';
        if (!cIsH) return;
        if (cTx > cW * 0.5) { c.classList.add('is-open'); c.style.transform = 'translateX(' + cW + 'px)'; }
        else closeSwipe(c);
    }, { passive: true });
    // A tap on an OPEN card closes it instead of activating the card.
    document.addEventListener('click', function (e) {
        var c = e.target && e.target.closest && e.target.closest('.swipe-content.is-open');
        if (c && !(e.target.closest && e.target.closest('.swipe-actions'))) {
            e.stopPropagation(); e.preventDefault(); closeSwipe(c);
        }
    }, true);

    /* ---- 4) Edge-swipe to open / swipe to close the "more" sidebar ---- */
    var egX = 0, egY = 0, egEdge = false, egTrack = false;
    document.addEventListener('touchstart', function (e) {
        if (!isMobile() || e.touches.length !== 1) { egTrack = false; return; }
        if (document.querySelector('.bottom-sheet.active') ||
            document.querySelector('[id^="modal"]:not(.hidden)')) { egTrack = false; return; }
        egX = e.touches[0].clientX; egY = e.touches[0].clientY;
        egEdge = egX > (window.innerWidth - 40);
        egTrack = true;
    }, { passive: true });
    document.addEventListener('touchend', function (e) {
        if (!egTrack) return; egTrack = false;
        var dx = e.changedTouches[0].clientX - egX, dy = e.changedTouches[0].clientY - egY;
        if (Math.abs(dx) < 45 || Math.abs(dx) < Math.abs(dy) * 1.8) return;
        var sb = document.getElementById('main-sidebar');
        if (!sb) return;
        var open = !sb.classList.contains('translate-x-full');
        try {
            if (open && dx > 0 && app.closeSidebar) app.closeSidebar();
            else if (!open && egEdge && dx < 0 && app.toggleSidebar) app.toggleSidebar();
        } catch (err) {}
    }, { passive: true });

    /* ---- 5) Swipe to switch tabs inside the profile / deal modals ---- */
    var MODAL_TABS = [
        { modal: 'modalProfile', tab: '.profile-tab' },
        { modal: 'modalDeal', tab: '.deal-form-tab-btn' }
    ];
    var mx = 0, my = 0, mTrack = false, mLock = false, mIsH = false, mCfg = null;
    var M_NO = '.overflow-x-auto, .no-scrollbar, table, input, select, textarea, [data-no-swipe], .swipe-content';
    document.addEventListener('touchstart', function (e) {
        if (!isMobile() || e.touches.length !== 1) { mTrack = false; return; }
        var t = e.target;
        if (t && t.closest && t.closest(M_NO)) { mTrack = false; return; }
        mCfg = null;
        for (var i = 0; i < MODAL_TABS.length; i++) {
            var m = document.getElementById(MODAL_TABS[i].modal);
            if (m && !m.classList.contains('hidden') && m.contains(t)) { mCfg = MODAL_TABS[i]; break; }
        }
        if (!mCfg) { mTrack = false; return; }
        mx = e.touches[0].clientX; my = e.touches[0].clientY;
        mTrack = true; mLock = false; mIsH = false;
    }, { passive: true });
    document.addEventListener('touchmove', function (e) {
        if (!mTrack) return;
        var dx = e.touches[0].clientX - mx, dy = e.touches[0].clientY - my;
        if (!mLock && (Math.abs(dx) > 12 || Math.abs(dy) > 12)) {
            mLock = true; mIsH = Math.abs(dx) > Math.abs(dy) * 1.8;
            if (!mIsH) mTrack = false;
        }
    }, { passive: true });
    document.addEventListener('touchend', function (e) {
        if (!mTrack || !mIsH) { mTrack = false; return; }
        mTrack = false;
        var dx = e.changedTouches[0].clientX - mx, dy = e.changedTouches[0].clientY - my;
        if (Math.abs(dx) < 55 || Math.abs(dx) < Math.abs(dy) * 1.8) return;
        switchModalTab(mCfg, dx < 0 ? 1 : -1);
    }, { passive: true });
    function switchModalTab(cfg, dir) {
        var modal = document.getElementById(cfg.modal);
        if (!modal) return;
        var tabs = [].slice.call(modal.querySelectorAll(cfg.tab)).filter(function (b) { return b.offsetParent !== null; });
        if (tabs.length < 2) return;
        var idx = -1;
        for (var i = 0; i < tabs.length; i++) {
            var b = tabs[i];
            if (b.classList.contains('active') || b.classList.contains('is-active') || b.getAttribute('aria-selected') === 'true') { idx = i; break; }
        }
        if (idx < 0) idx = 0;
        var next = idx + dir;
        if (next < 0 || next >= tabs.length) return;
        try { if (typeof app !== 'undefined' && app.haptic) app.haptic(); } catch (err) {}
        tabs[next].click();
    }
}

// Initialize mobile UX enhancements on DOM ready
if (typeof document !== 'undefined') {
    document.addEventListener('DOMContentLoaded', () => {
        // Initialize mobile viewport height
        initMobileViewportHeight();

        // Initialize swipe gestures
        initSwipeGestures();

        // Initialize the safe mobile gesture engine (tab swipe + sheet dismiss)
        initMobileGestures();

        // Initialize mobile filter enhancements
        initMobileFilters();
    });
}

// ============ MOBILE FILTER FUNCTIONS ============

/**
 * Initialize mobile filter enhancements
 */
function initMobileFilters() {
    // Initialize filter state tracking
    window.filterState = {
        leads: { active: false, count: 0 },
        deals: { active: false, count: 0 }
    };
    
    // Add change listeners to filter inputs
    document.addEventListener('change', (e) => {
        if (e.target.classList.contains('filter-input')) {
            updateFilterCount(e.target);
        }
    });
}

/**
 * Toggle collapsible filters
 */
app.toggleFilters = function(view) {
    const filtersContainer = document.getElementById(`${view}-filters`);
    if (!filtersContainer) return;
    
    const isOpen = filtersContainer.classList.contains('open');
    
    if (isOpen) {
        filtersContainer.classList.remove('open');
        window.filterState[view].active = false;
    } else {
        filtersContainer.classList.add('open');
        window.filterState[view].active = true;
    }
    app.syncFilterToggleUi?.(view);
};

app.syncFilterToggleUi = function(view) {
    const filtersContainer = document.getElementById(`${view}-filters`);
    if (!filtersContainer) return;
    const desktopToggle = document.getElementById(`${view}-desktop-filter-toggle`);
    const desktopText = document.getElementById(`${view}-desktop-filter-toggle-text`);
    if (desktopToggle) {
        const isOpen = filtersContainer.classList.contains('open');
        desktopToggle.classList.toggle('open', isOpen);
        if (desktopText) {
            desktopText.textContent = isOpen ? 'پنهان‌کردن فیلترها' : 'نمایش فیلترها';
        }
    }
};

/**
 * Update filter count badge
 */
function updateFilterCount(input) {
    const view = input.id.split('-')[0]; // leads or deals
    let count = 0;
    
    // Count all non-empty filter inputs
    const filterInputs = document.querySelectorAll(`#${view}-filters .filter-input`);
    filterInputs.forEach(input => {
        if (input.value && input.value !== 'all' && input.value !== '') {
            count++;
        }
        // Toggle active class
        if (input.value && input.value !== 'all' && input.value !== '') {
            input.classList.add('active');
        } else {
            input.classList.remove('active');
        }
    });
    
    window.filterState[view].count = count;
    
    // Update badge
    const badge = document.getElementById(`${view}-filter-count`);
    const bottomBadge = document.getElementById(`${view}-bottom-filter-count`);
    
    if (badge) {
        badge.textContent = count;
        badge.classList.toggle('hidden', count === 0);
    }
    
    if (bottomBadge) {
        bottomBadge.textContent = count;
        bottomBadge.classList.toggle('hidden', count === 0);
    }
    const desktopBadge = document.getElementById(`${view}-desktop-filter-count`);
    if (desktopBadge) {
        desktopBadge.textContent = count;
        desktopBadge.classList.toggle('hidden', count === 0);
    }
}

/**
 * Clear all filters for a view
 */
app.clearAllFilters = function(view) {
    const filtersContainer = document.getElementById(`${view}-filters`);
    if (!filtersContainer) return;
    
    // Reset all filter inputs
    const filterInputs = filtersContainer.querySelectorAll('.filter-input');
    filterInputs.forEach(input => {
        if (input.tagName === 'SELECT') {
            input.selectedIndex = 0;
        } else {
            input.value = '';
        }
        input.classList.remove('active');
    });
    
    // Reset count
    window.filterState[view].count = 0;
    const badge = document.getElementById(`${view}-filter-count`);
    const bottomBadge = document.getElementById(`${view}-bottom-filter-count`);
    
    if (badge) {
        badge.textContent = '0';
        badge.classList.add('hidden');
    }
    
    if (bottomBadge) {
        bottomBadge.classList.add('hidden');
    }
    const desktopBadge = document.getElementById(`${view}-desktop-filter-count`);
    if (desktopBadge) {
        desktopBadge.textContent = '0';
        desktopBadge.classList.add('hidden');
    }
    
    // Reload data
    if (view === 'leads') {
        app.loadLeads(1);
    } else if (view === 'deals') {
        app.loadDeals();
    }
};

/**
 * Apply filters (mobile)
 */
app.applyFilters = function(view) {
    // Close collapsible filters
    const filtersContainer = document.getElementById(`${view}-filters`);
    if (filtersContainer) {
        filtersContainer.classList.remove('open');
    }
    
    // Reload data
    if (view === 'leads') {
        app.loadLeads(1);
    } else if (view === 'deals') {
        app.loadDeals();
    }
};

/**
 * Handle filter change
 */
app.onFilterChange = function(view) {
    updateFilterCount(document.querySelector(`#${view}-filters .filter-input`));
};

/**
 * Open bottom sheet filters
 */
app.openBottomSheetFilters = function(view) {
    // Create bottom sheet if not exists
    if (!document.getElementById('bottom-sheet-filters')) {
        createBottomSheetFilters(view);
    }
    
    const overlay = document.getElementById('bottom-sheet-overlay');
    const sheet = document.getElementById('bottom-sheet-filters');
    
    if (overlay && sheet) {
        overlay.classList.add('active');
        sheet.classList.add('active');
    }
};

/**
 * Close bottom sheet filters
 */
app.closeBottomSheetFilters = function() {
    const overlay = document.getElementById('bottom-sheet-overlay');
    const sheet = document.getElementById('bottom-sheet-filters');
    
    if (overlay && sheet) {
        overlay.classList.remove('active');
        sheet.classList.remove('active');
        
        setTimeout(() => {
            if (overlay) overlay.remove();
            if (sheet) sheet.remove();
        }, 300);
    }
};

/**
 * Create bottom sheet filters component
 */
function createBottomSheetFilters(view) {
    const overlay = document.createElement('div');
    overlay.id = 'bottom-sheet-overlay';
    overlay.className = 'bottom-sheet-overlay';
    overlay.onclick = () => app.closeBottomSheetFilters();
    
    const sheet = document.createElement('div');
    sheet.id = 'bottom-sheet-filters';
    sheet.className = 'bottom-sheet';
    
    sheet.innerHTML = `
        <div class="bottom-sheet-header">
            <div class="bottom-sheet-title">فیلترهای ${view === 'leads' ? 'لیدها' : 'معاملات'}</div>
            <button onclick="app.closeBottomSheetFilters()" class="bottom-sheet-close">
                <i class="fa-solid fa-times"></i>
            </button>
        </div>
        <div class="bottom-sheet-content">
            <p class="text-center text-gray-400 py-8">فیلترهای پیشرفته به زودی...</p>
        </div>
        <div class="bottom-sheet-footer">
            <button onclick="app.closeBottomSheetFilters()" class="bg-gray-700 hover:bg-gray-600 text-white rounded-lg">انصراف</button>
            <button onclick="app.applyBottomSheetFilters()" class="btn-primary">اعمال فیلتر</button>
        </div>
    `;
    
    document.body.appendChild(overlay);
    document.body.appendChild(sheet);
}

/**
 * Apply bottom sheet filters
 */
app.applyBottomSheetFilters = function() {
    app.closeBottomSheetFilters();
    // TODO: Implement filter logic
};

/**
 * Render simplified mobile lead card
 */
app.renderMobileLeadCard = function(lead) {
    const statusKey = lead.sale_status === 1 ? 'success' : (lead.sale_status === 0 ? 'failed' : 'unknown');
    const statusMeta = this.state.saleStatusMap[statusKey] || this.state.saleStatusMap.unknown;
    // Shared semantic component classes (mobile-only CSS): accent stripe +
    // status pill. Inert on desktop, which keeps its own rendering branch.
    const sClass = statusKey === 'success' ? 's--success' : (statusKey === 'failed' ? 's--danger' : 's--neutral');
    const phone = lead.mobile_phone || '-';
    const leadId = lead.didar_contact_id || lead.id;

    return `
        <div class="swipe-row">
            <div class="swipe-actions" aria-hidden="true">
                <button class="sa-call" onclick="event.stopPropagation(); app.quickContact('${phone}', '${leadId}')" aria-label="تماس"><i class="fa-solid fa-phone"></i>تماس</button>
                <button class="sa-view" onclick="event.stopPropagation(); app.openPersonProfile('${leadId}')" aria-label="مشاهده"><i class="fa-solid fa-eye"></i>مشاهده</button>
            </div>
            <div class="swipe-content">
                <div class="glass lead-card-mobile accent-card ${sClass}" onclick="app.openPersonProfile('${leadId}')">
                    <div class="lcm-row">
                        <div class="card-name">${lead.first_name || ''} ${lead.last_name || ''}</div>
                        <span class="s-pill ${sClass}">${statusMeta.icon} ${statusMeta.title || ''}</span>
                    </div>
                    <div class="lcm-row lcm-sub">
                        <div class="lcm-meta">
                            <span class="card-phone">${this.bidi(phone)}</span>
                            <span class="lcm-sep">·</span>
                            <span class="lcm-owner">${lead.owner_name || '---'}</span>
                        </div>
                        <button onclick="event.stopPropagation(); app.quickContact('${phone}', '${leadId}')" class="lcm-call haptic-button" aria-label="تماس">
                            <i class="fa-solid fa-phone"></i>
                        </button>
                    </div>
                </div>
            </div>
        </div>
    `;
};

/**
 * Render lead cards (mobile-optimized)
 */
app.renderLeads = function() {
    const container = document.getElementById('leads-list');
    if (!container) return;
    
    const isMobile = window.innerWidth < 768;
    
    if (this.state.leads.length === 0) {
        const searchValue = document.getElementById('leads-search')?.value || '';
        
        if (searchValue.trim()) {
            container.innerHTML = `
                <div class="text-center py-12">
                    <div class="mb-4">
                        <i class="fa-solid fa-search text-4xl text-gray-600"></i>
                    </div>
                    <p class="text-gray-400 font-semibold text-lg">لیدی با این مشخصات یافت نشد</p>
                    <p class="text-gray-500 text-sm mt-3">جستجو: <span class="text-yellow-500 font-mono">${this.escapeHtml(searchValue)}</span></p>
                    <button onclick="document.getElementById('leads-search').value = ''; app.loadLeads();" class="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition">
                        پاک کردن جستجو
                    </button>
                </div>
            `;
        } else {
            container.innerHTML = '<div class="text-center text-gray-500 py-8">لیدی یافت نشد</div>';
        }
        return;
    }
    
    // Use mobile-optimized rendering on mobile
    if (isMobile) {
        container.innerHTML = this.state.leads.map(lead => this.renderMobileLeadCard(lead)).join('');
    } else {
        // Desktop rendering (existing code)
        container.innerHTML = this.state.leads.map(lead => {
            const statusKey = lead.sale_status === 1 ? 'success' : (lead.sale_status === 0 ? 'failed' : 'unknown');
            const statusMeta = this.state.saleStatusMap[statusKey] || this.state.saleStatusMap.unknown;
            const regDate = lead.register_time_jalali || (lead.register_time ? toPersianDate(lead.register_time) : '');
            const products = this.escapeHtml(this.getLeadProductsDisplayValue(lead)) || '-';
            
            return `
                <div class="glass p-4 hover:bg-gray-800/50 transition cursor-pointer" onclick="app.openPersonProfile('${lead.didar_contact_id || lead.id || ''}')">
                    <div class="flex justify-between items-start mb-2">
                        <div>
                            <div class="font-bold text-white text-sm">${lead.first_name || ''} ${lead.last_name || ''}</div>
                            <div class="text-xs text-gray-400 mt-1">${lead.mobile_phone || '-'} ${lead.email ? '| ' + lead.email : ''}</div>
                            <div class="text-[11px] text-gray-500 mt-1">مسئول: ${lead.owner_name || '---'}</div>
                        </div>
                        <div class="flex flex-col items-end gap-1">
                            <span class="text-xs px-2 py-1 rounded ${statusMeta.bg} text-white">${statusMeta.icon} ${statusMeta.title}</span>
                        </div>
                    </div>
                    <div class="text-[11px] text-gray-400 mt-1">محصولات: ${products}</div>
                    <div class="flex justify-between items-center mt-2 text-xs text-gray-500">
                        <span>تاریخ ثبت: ${regDate || '-'}</span>
                        <span>${statusMeta.title}</span>
                    </div>
                </div>
            `;
        }).join('');
    }
    
    // Update sticky pagination on mobile
    if (isMobile) {
        this.renderStickyPagination('leads');
    }
};

/**
 * Render sticky pagination
 */
app.renderStickyPagination = function(view) {
    const paginationContainer = document.getElementById(`${view}-sticky-pagination`);
    if (!paginationContainer) return;
    
    const pagination = this.state[`${view}Pagination`];
    if (!pagination || pagination.totalPages <= 1) {
        paginationContainer.classList.add('hidden');
        return;
    }
    
    paginationContainer.classList.remove('hidden');
    
    const maxVisiblePages = 5;
    let startPage = Math.max(1, pagination.currentPage - Math.floor(maxVisiblePages / 2));
    let endPage = Math.min(pagination.totalPages, startPage + maxVisiblePages - 1);
    
    if (endPage - startPage + 1 < maxVisiblePages) {
        startPage = Math.max(1, endPage - maxVisiblePages + 1);
    }
    
    let html = '<div class="flex justify-center items-center gap-2 overflow-x-auto no-scrollbar">';
    
    // Previous button
    if (pagination.currentPage > 1) {
        html += `<button onclick="app.load${view.charAt(0).toUpperCase() + view.slice(1)}(${pagination.currentPage - 1})" class="bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition flex items-center gap-1">
            <i class="fa-solid fa-chevron-right"></i> قبلی
        </button>`;
    } else {
        html += `<button disabled class="bg-gray-800 text-gray-600 rounded-lg cursor-not-allowed px-4 py-2">قبلی</button>`;
    }
    
    // Page numbers
    if (startPage > 1) {
        html += `<button onclick="app.load${view.charAt(0).toUpperCase() + view.slice(1)}(1)" class="bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition px-4 py-2">1</button>`;
        if (startPage > 2) html += '<span class="text-gray-500 px-2">...</span>';
    }
    
    for (let i = startPage; i <= endPage; i++) {
        const activeClass = i === pagination.currentPage ? 'bg-yellow-600 text-white' : 'bg-gray-700 hover:bg-gray-600 text-white';
        html += `<button onclick="app.load${view.charAt(0).toUpperCase() + view.slice(1)}(${i})" class="${activeClass} rounded-lg transition px-4 py-2">${i}</button>`;
    }
    
    if (endPage < pagination.totalPages) {
        if (endPage < pagination.totalPages - 1) html += '<span class="text-gray-500 px-2">...</span>';
        html += `<button onclick="app.load${view.charAt(0).toUpperCase() + view.slice(1)}(${pagination.totalPages})" class="bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition px-4 py-2">${pagination.totalPages}</button>`;
    }
    
    // Next button
    if (pagination.currentPage < pagination.totalPages) {
        html += `<button onclick="app.load${view.charAt(0).toUpperCase() + view.slice(1)}(${pagination.currentPage + 1})" class="bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition flex items-center gap-1">
            بعدی <i class="fa-solid fa-chevron-left"></i>
        </button>`;
    } else {
        html += `<button disabled class="bg-gray-800 text-gray-600 rounded-lg cursor-not-allowed px-4 py-2">بعدی</button>`;
    }
    
    html += `</div>`;
    paginationContainer.innerHTML = html;
};

// ============ PHASE 3 & 4: ADVANCED MOBILE FEATURES ============

/**
 * Initialize Phase 3 & 4 features
 */
function initAdvancedMobileFeatures() {
    // Initialize filter presets
    initFilterPresets();
    
    // Initialize filter history
    initFilterHistory();
    
    // Initialize voice search
    initVoiceSearch();
    
    // Initialize haptic feedback
    initHapticFeedback();
    
    // Initialize pull-to-refresh
    initPullToRefresh();
    
    console.log('[MOBILE] Phase 3 & 4 features initialized');
}

/**
 * Filter Presets - Quick common filters
 */
function initFilterPresets() {
    window.filterPresets = {
        leads: [
            { id: 'today', label: 'امروز', icon: 'fa-calendar-day', filters: { register_from: 'today', register_to: 'today' } },
            { id: 'week', label: 'این هفته', icon: 'fa-calendar-week', filters: { register_from: 'week_start', register_to: 'today' } },
            { id: 'month', label: 'این ماه', icon: 'fa-calendar', filters: { register_from: 'month_start', register_to: 'today' } },
            { id: 'my_leads', label: 'لیدهای من', icon: 'fa-user', filters: { owner: 'me' } },
            { id: 'hot', label: 'لیدهای داغ', icon: 'fa-fire', filters: { customer_level: 'طلایی' } },
            { id: 'new', label: 'جدید', icon: 'fa-star', filters: { stage: 'new' } }
        ],
        deals: [
            { id: 'today', label: 'امروز', icon: 'fa-calendar-day', filters: { register_from: 'today', register_to: 'today' } },
            { id: 'won', label: 'موفق', icon: 'fa-check-circle', filters: { status: 'won' } },
            { id: 'pending', label: 'جاری', icon: 'fa-clock', filters: { status: 'pending' } },
            { id: 'high_value', label: 'باارزش', icon: 'fa-sack-dollar', filters: { min_price: '10000000' } }
        ]
    };
}

/**
 * Render filter presets
 */
app.renderFilterPresets = function(view, containerId) {
    const container = document.getElementById(containerId);
    if (!container) return;
    
    const presets = window.filterPresets[view] || [];
    
    container.innerHTML = `
        <div class="filter-preset-bar">
            ${presets.map(preset => `
                <div class="filter-preset-chip" onclick="app.applyFilterPreset('${view}', '${preset.id}')">
                    <i class="fa-solid ${preset.icon}"></i>
                    ${preset.label}
                </div>
            `).join('')}
        </div>
    `;
};

/**
 * Apply filter preset
 */
app.applyFilterPreset = function(view, presetId) {
    const presets = window.filterPresets[view] || [];
    const preset = presets.find(p => p.id === presetId);
    if (!preset) return;
    
    // Apply filters based on preset
    const filters = preset.filters;
    
    if (view === 'leads') {
        if (filters.register_from) {
            const fromDate = this.getDateRangeValue(filters.register_from);
            document.getElementById('leads-register-from').value = fromDate;
        }
        if (filters.register_to) {
            const toDate = this.getDateRangeValue(filters.register_to);
            document.getElementById('leads-register-to').value = toDate;
        }
        if (filters.owner === 'me') {
            document.getElementById('leads-owner-filter').value = window.APP_USER?.didar_id || '';
        }
        if (filters.customer_level) {
            document.getElementById('leads-customer-level-filter').value = filters.customer_level;
        }
        if (filters.stage) {
            document.getElementById('leads-stage-filter').value = filters.stage;
        }
        
        this.loadLeads(1);
    }
    
    // Save to history
    this.saveFilterHistory(view, preset.label);
};

/**
 * Get date range value from preset
 */
app.getDateRangeValue = function(range) {
    const now = new Date();
    let date;
    
    switch (range) {
        case 'today':
            date = now;
            break;
        case 'week_start':
            date = new Date(now);
            date.setDate(date.getDate() - 7);
            break;
        case 'month_start':
            date = new Date(now.getFullYear(), now.getMonth(), 1);
            break;
        default:
            date = now;
    }
    
    // Convert to Persian date
    if (typeof persianDate !== 'undefined') {
        const pDate = new persianDate(date);
        return pDate.format('YYYY/MM/DD');
    }
    
    return date.toISOString().split('T')[0];
};

/**
 * Filter History - Save recent combinations
 */
function initFilterHistory() {
    window.filterHistory = {
        leads: JSON.parse(localStorage.getItem('filterHistory_leads') || '[]'),
        deals: JSON.parse(localStorage.getItem('filterHistory_deals') || '[]')
    };
}

/**
 * Save filter to history
 */
app.saveFilterHistory = function(view, label) {
    const history = window.filterHistory[view] || [];
    const timestamp = new Date().toLocaleString('fa-IR');
    
    // Add to beginning
    history.unshift({
        label: label,
        timestamp: timestamp,
        date: Date.now()
    });
    
    // Keep only last 10
    history.splice(10);
    
    // Save to localStorage
    window.filterHistory[view] = history;
    localStorage.setItem(`filterHistory_${view}`, JSON.stringify(history));
    
    // Update UI
    this.renderFilterHistory(view);
};

/**
 * Render filter history
 */
app.renderFilterHistory = function(view) {
    const container = document.getElementById(`${view}-filter-history-dropdown`);
    if (!container) return;
    
    const history = window.filterHistory[view] || [];
    
    if (history.length === 0) {
        container.innerHTML = '<div class="p-4 text-center text-gray-400 text-sm">تاریخچه‌ای وجود ندارد</div>';
        return;
    }
    
    container.innerHTML = history.map((item, index) => `
        <div class="filter-history-item" onclick="app.loadFilterHistory('${view}', '${item.label}')">
            <div class="filter-history-label">${item.label}</div>
            <div class="filter-history-meta">
                <span>${item.timestamp}</span>
                <button onclick="event.stopPropagation(); app.deleteFilterHistory('${view}', ${index})" class="filter-history-delete">
                    <i class="fa-solid fa-trash"></i>
                </button>
            </div>
        </div>
    `).join('');
};

/**
 * Load filter from history
 */
app.loadFilterHistory = function(view, label) {
    // TODO: Implement loading actual filter values from history
    console.log(`Loading filter: ${label} for ${view}`);
    
    // Close dropdown
    const dropdown = document.getElementById(`${view}-filter-history-dropdown`);
    if (dropdown) {
        dropdown.classList.remove('open');
    }
};

/**
 * Delete filter from history
 */
app.deleteFilterHistory = function(view, index) {
    const history = window.filterHistory[view] || [];
    history.splice(index, 1);
    window.filterHistory[view] = history;
    localStorage.setItem(`filterHistory_${view}`, JSON.stringify(history));
    this.renderFilterHistory(view);
};

/**
 * Toggle filter history dropdown
 */
app.toggleFilterHistory = function(view) {
    const dropdown = document.getElementById(`${view}-filter-history-dropdown`);
    if (!dropdown) return;
    
    dropdown.classList.toggle('open');
    this.renderFilterHistory(view);
};

/**
 * Voice Search - Microphone input
 */
function initVoiceSearch() {
    // Check if Web Speech API is supported
    window.voiceSearchSupported = 'webkitSpeechRecognition' in window || 'SpeechRecognition' in window;
    
    if (window.voiceSearchSupported) {
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        window.voiceRecognition = new SpeechRecognition();
        window.voiceRecognition.continuous = false;
        window.voiceRecognition.interimResults = false;
        window.voiceRecognition.lang = 'fa-IR';
        
        window.voiceRecognition.onresult = (event) => {
            const transcript = event.results[0][0].transcript;
            const searchInput = document.getElementById('leads-search');
            if (searchInput) {
                searchInput.value = transcript;
                app.loadLeads(1);
            }
            
            // Stop listening
            app.stopVoiceSearch();
        };
        
        window.voiceRecognition.onerror = (event) => {
            console.error('Voice search error:', event.error);
            app.stopVoiceSearch();
            
            if (event.error === 'not-allowed') {
                Swal.fire('خطا', 'دسترسی به میکروفون داده نشد', 'error');
            }
        };
        
        window.voiceRecognition.onend = () => {
            app.stopVoiceSearch();
        };
    }
}

/**
 * Start voice search
 */
app.startVoiceSearch = function() {
    if (!window.voiceSearchSupported) {
        Swal.fire('خطا', 'مرورگر شما از جستجوی صوتی پشتیبانی نمی‌کند', 'error');
        return;
    }
    
    const btn = document.querySelector('.voice-search-btn');
    if (btn) {
        btn.classList.add('listening');
    }
    
    try {
        window.voiceRecognition.start();
        
        // Haptic feedback
        if ('vibrate' in navigator) {
            navigator.vibrate(50);
        }
    } catch (e) {
        console.error('Voice search start error:', e);
        this.stopVoiceSearch();
    }
};

/**
 * Stop voice search
 */
app.stopVoiceSearch = function() {
    const btn = document.querySelector('.voice-search-btn');
    if (btn) {
        btn.classList.remove('listening');
    }
    
    try {
        if (window.voiceRecognition) {
            window.voiceRecognition.stop();
        }
    } catch (e) {
        // Ignore errors
    }
};

/**
 * Haptic Feedback
 */
function initHapticFeedback() {
    window.hapticEnabled = 'vibrate' in navigator;
}

/**
 * Trigger haptic feedback
 */
app.haptic = function(pattern = 10) {
    if (window.hapticEnabled) {
        navigator.vibrate(pattern);
    }
};

/**
 * Add haptic to all buttons
 */
app.addHapticToButtons = function() {
    document.querySelectorAll('button').forEach(btn => {
        btn.addEventListener('click', () => {
            app.haptic(10);
        });
    });
};

/**
 * Pull-to-Refresh
 */
function initPullToRefresh() {
    return; // Disabled: pull-to-refresh touchmove handler breaks native scrolling
    let startY = 0;
    let currentY = 0;
    let isPulling = false;
    const threshold = 100; // px to trigger refresh
    
    document.addEventListener('touchstart', (e) => {
        if (e.touches.length === 1 && window.scrollY === 0) {
            startY = e.touches[0].clientY;
            isPulling = true;
        }
    }, { passive: true });
    
    document.addEventListener('touchmove', (e) => {
        if (!isPulling) return;
        
        currentY = e.touches[0].clientY;
        const diff = currentY - startY;
        
        if (diff > 0 && diff < threshold * 2) {
            e.preventDefault();
            
            const indicator = document.querySelector('.pull-to-refresh-indicator');
            if (indicator) {
                indicator.style.top = `${-60 + diff / 2}px`;
                
                if (diff > threshold) {
                    indicator.classList.add('ready');
                } else {
                    indicator.classList.remove('ready');
                }
            }
        }
    }, { passive: false });
    
    document.addEventListener('touchend', () => {
        if (!isPulling) return;
        
        isPulling = false;
        
        const indicator = document.querySelector('.pull-to-refresh-indicator');
        if (indicator && indicator.classList.contains('ready')) {
            // Trigger refresh
            app.refresh();
        }
        
        if (indicator) {
            indicator.style.top = '-60px';
            indicator.classList.remove('ready');
        }
    });
}

/**
 * Quick Filter Toolbar
 */
app.renderQuickFilterToolbar = function(view) {
    const quickFilters = {
        leads: [
            { id: 'all', label: 'همه', active: true },
            { id: 'new', label: 'جدید', active: false },
            { id: 'contacted', label: 'تماس شده', active: false },
            { id: 'qualified', label: 'صالحیت‌دار', active: false },
            { id: 'converted', label: 'تبدیل شده', active: false }
        ],
        deals: [
            { id: 'all', label: 'همه', active: true },
            { id: 'pending', label: 'جاری', active: false },
            { id: 'won', label: 'موفق', active: false },
            { id: 'lost', label: 'ناموفق', active: false }
        ]
    };
    
    const container = document.getElementById('quick-filter-toolbar');
    if (!container) return;
    
    const filters = quickFilters[view] || [];
    
    container.innerHTML = `
        <div class="quick-filter-scroll">
            ${filters.map(filter => `
                <div class="quick-filter-item ${filter.active ? 'active' : ''}" 
                     onclick="app.applyQuickFilter('${view}', '${filter.id}', this)">
                    ${filter.label}
                </div>
            `).join('')}
        </div>
    `;
};

/**
 * Apply quick filter
 */
app.applyQuickFilter = function(view, filterId, element) {
    // Update active state
    element.parentElement.querySelectorAll('.quick-filter-item').forEach(item => {
        item.classList.remove('active');
    });
    element.classList.add('active');
    
    // Apply filter
    if (view === 'leads') {
        if (filterId === 'new') {
            document.getElementById('leads-stage-filter').value = 'new';
        } else if (filterId === 'all') {
            document.getElementById('leads-stage-filter').value = 'all';
        }
        this.loadLeads(1);
    } else if (view === 'deals') {
        if (filterId === 'pending') {
            document.getElementById('deals-status-filter').value = 'pending';
        } else if (filterId === 'won') {
            document.getElementById('deals-status-filter').value = 'won';
        } else if (filterId === 'lost') {
            document.getElementById('deals-status-filter').value = 'lost';
        } else if (filterId === 'all') {
            document.getElementById('deals-status-filter').value = 'all';
        }
        this.loadDeals();
    }
    
    // Haptic feedback
    this.haptic(10);
};

/**
 * Enhanced Loading State
 */
app.showLoading = function(message = 'در حال بارگذاری...') {
    let overlay = document.querySelector('.loading-overlay');
    
    if (!overlay) {
        overlay = document.createElement('div');
        overlay.className = 'loading-overlay';
        overlay.innerHTML = `
            <div class="loading-spinner"></div>
            <div class="loading-text">${message}</div>
        `;
        document.body.appendChild(overlay);
    }
    
    overlay.querySelector('.loading-text').textContent = message;
    overlay.classList.add('active');
};

/**
 * Hide Loading State
 */
app.hideLoading = function() {
    const overlay = document.querySelector('.loading-overlay');
    if (overlay) {
        overlay.classList.remove('active');
    }
};

app.operationManagerState = {
    loaded: false,
    rows: [],
    pagination: { current_page: 1, per_page: 20, total_count: 0, total_pages: 0 },
    selectedIds: new Set(),
    selectAllMatching: false,
    rules: [],
    runs: [],
    pools: [],
    conditionBuilderRows: []
};

app.loadOperationManager = async function() {
    if (!document.getElementById('view-operation-manager')) return;
    this.operationManagerState = this.operationManagerState || {};
    this.operationManagerState.selectedIds = this.operationManagerState.selectedIds || new Set();
    await this.loadOperationManagerOwners();
    this.initOperationManagerDatePickers();
    this.updateOperationManagerActionVisibility();
    this.applyOperationManagerPermissions();
    if (!this.operationManagerState.loaded) {
        this.fillDefaultOperationRule();
        this.operationManagerState.loaded = true;
    }
    await Promise.all([
        this.previewOperationManager(1),
        this.loadOperationManagerRules(),
        this.loadOperationManagerPools(),
        this.loadOperationManagerRuns()
    ]);
};

app.applyOperationManagerPermissions = function() {
    const canExecute = this.canCurrentUserCapabilityClient('operation_manager.execute');
    const canManageRules = this.canCurrentUserCapabilityClient('operation_manager.rules.manage');
    const executeBtn = document.getElementById('om-execute-btn');
    if (executeBtn) {
        executeBtn.classList.toggle('hidden', !canExecute);
        executeBtn.disabled = !canExecute;
    }
    const saveBtn = document.getElementById('om-rule-save-btn');
    if (saveBtn) {
        saveBtn.classList.toggle('hidden', !canManageRules);
        saveBtn.disabled = !canManageRules;
    }
};

app.loadOperationManagerOwners = async function() {
    if (Array.isArray(this.state.didarUsers) && this.state.didarUsers.length > 0) {
        this.populateOperationManagerOwnerSelects();
        return;
    }
    try {
        const res = await this.req('get_didar_users', { resource_key: 'operation_manager' });
        if (res.status === 'success') {
            this.state.didarUsers = res.users || [];
        } else {
            this.setDidarUsersFallbackToCurrentUser();
        }
    } catch (e) {
        console.warn('Operation Manager owner list failed', e);
        this.setDidarUsersFallbackToCurrentUser();
    }
    this.populateOperationManagerOwnerSelects();
};

app.populateOperationManagerOwnerSelects = function() {
    const users = Array.isArray(this.state.didarUsers) ? this.state.didarUsers : [];
    const options = users.map(user => {
        const id = user.owner_id || user.didar_user_id || user.didari_user_id || '';
        const name = user.display_name || user.name || user.email || `${user.first_name || ''} ${user.last_name || ''}`.trim() || id;
        if (!id) return '';
        return `<option value="${this.escapeHtml(id)}">${this.escapeHtml(name)}</option>`;
    }).join('');
    const singleSelects = {
        'om-prev-owner-filter': 'همه مسئول‌های قبلی',
        'om-operation-owner': 'انتخاب مسئول',
        'om-rule-operation-owner': 'انتخاب مسئول'
    };
    Object.entries(singleSelects).forEach(([id, label]) => {
        const el = document.getElementById(id);
        if (!el) return;
        const current = el.value || '';
        el.innerHTML = `<option value="">${label}</option>${options}`;
        if (current) el.value = current;
    });
    // Populate multi-owner dropdown
    const list = document.getElementById('om-owner-filter-list');
    if (list) {
        list.innerHTML = users.map(user => {
            const id = user.owner_id || user.didar_user_id || user.didari_user_id || '';
            const name = user.display_name || user.name || user.email || `${user.first_name || ''} ${user.last_name || ''}`.trim() || id;
            if (!id) return '';
            return `<label class="flex items-center gap-2 px-3 py-2 hover:bg-gray-800 cursor-pointer text-sm text-gray-200">
                <input type="checkbox" class="accent-yellow-500 om-owner-check" value="${this.escapeHtml(id)}" onchange="app.omOwnerFilterChange()">
                ${this.escapeHtml(name)}
            </label>`;
        }).join('');
        this.omOwnerFilterUpdateLabel();
    }
};

app.toggleOmOwnerDropdown = function() {
    const dd = document.getElementById('om-owner-filter-dropdown');
    if (!dd) return;
    dd.classList.toggle('hidden');
    // Close when clicking outside
    if (!dd.classList.contains('hidden')) {
        const close = (e) => {
            const wrap = document.getElementById('om-owner-filter-wrap');
            if (wrap && !wrap.contains(e.target)) {
                dd.classList.add('hidden');
                document.removeEventListener('click', close);
            }
        };
        setTimeout(() => document.addEventListener('click', close), 0);
    }
};

app.omOwnerFilterToggleAll = function(checked) {
    document.querySelectorAll('.om-owner-check').forEach(cb => { cb.checked = false; });
    const allCb = document.getElementById('om-owner-all');
    if (allCb) allCb.checked = true;
    this.omOwnerFilterUpdateLabel();
};

app.omOwnerFilterChange = function() {
    const checked = Array.from(document.querySelectorAll('.om-owner-check:checked'));
    const allCb = document.getElementById('om-owner-all');
    if (allCb) allCb.checked = checked.length === 0;
    this.omOwnerFilterUpdateLabel();
};

app.omOwnerFilterUpdateLabel = function() {
    const label = document.getElementById('om-owner-filter-label');
    if (!label) return;
    const checked = Array.from(document.querySelectorAll('.om-owner-check:checked'));
    if (checked.length === 0) {
        label.textContent = 'همه مسئول‌ها';
        label.classList.remove('text-yellow-300');
        label.classList.add('text-gray-300');
        return;
    }
    label.classList.add('text-yellow-300');
    label.classList.remove('text-gray-300');
    if (checked.length === 1) {
        label.textContent = checked[0].closest('label')?.textContent?.trim() || checked[0].value;
    } else {
        label.textContent = `${checked.length} مسئول انتخاب شده`;
    }
};

app.getOmOwnerFilterValues = function() {
    const checked = Array.from(document.querySelectorAll('.om-owner-check:checked'));
    return checked.map(cb => cb.value);
};

// ===== Shared Jalali date-range FILTER component =====
// Wires a from/to filter pair onto the shared neo datepicker with the same behavior
// as the performance dashboard: only-one-open + single-click (from the library),
// max = today (no future dates) unless opts.allowFuture, and from<=to coupling with
// the "تا" picker's min kept in sync with the chosen "از".
app.initJalaliRangeFilter = function(fromId, toId, opts = {}) {
    const self = this;
    const allowFuture = !!opts.allowFuture;
    if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') {
        setTimeout(() => self.initJalaliRangeFilter(fromId, toId, opts), 200);
        return;
    }
    const fromEl = document.getElementById(fromId);
    const toEl = document.getElementById(toId);
    if (!fromEl && !toEl) return;
    [fromEl, toEl].forEach(el => {
        if (!el) return;
        const $i = $(el);
        if ($i.data('rangeFilterInit') === 1) return;
        try { if ($i.data('persianDatepicker')) $i.persianDatepicker('destroy'); } catch (_) {}
        $i.persianDatepicker({
            format: 'YYYY/MM/DD',
            initialValue: false,
            autoClose: true,
            observer: true,
            maxDate: allowFuture ? null : self.getTodayGregorianDateOnly(),
            onSelect: () => setTimeout(() => self.syncJalaliRangeFilter(fromId, toId, el, opts), 0)
        });
        $i.on('input change blur', () => self.syncJalaliRangeFilter(fromId, toId, el, opts));
        if (el === toEl) {
            ['focus', 'pointerdown', 'mousedown', 'click'].forEach((eventName) => {
                el.addEventListener(eventName, () => self.applyJalaliRangeLimits(fromId, toId, opts), true);
            });
        }
        $i.data('rangeFilterInit', 1);
    });
    self.applyJalaliRangeLimits(fromId, toId, opts);
};

// Normalize the changed field (English digits), clamp future->today, then refresh limits.
app.syncJalaliRangeFilter = function(fromId, toId, changedEl, opts = {}) {
    if (!changedEl) return;
    const allowFuture = !!opts.allowFuture;
    const norm = this.normalizeJalaliInputValue(changedEl.value || '');
    if (norm && norm !== changedEl.value) changedEl.value = norm;
    if (!allowFuture && norm) {
        const today = this.getTodayGregorianDateOnly();
        const g = this.jalaliToGregorianDateOnly(norm, false);
        if (g && g > today) changedEl.value = toPersianDate(today) || changedEl.value;
    }
    this.applyJalaliRangeLimits(fromId, toId, opts);
    if (typeof opts.onChange === 'function') {
        opts.onChange(changedEl);
    }
};

// Couple the pair (to >= from) and re-apply both pickers' live min/max in place.
app.applyJalaliRangeLimits = function(fromId, toId, opts = {}) {
    if (typeof $ === 'undefined' || typeof $.fn?.persianDatepicker !== 'function') return;
    const self = this;
    const allowFuture = !!opts.allowFuture;
    const today = this.getTodayGregorianDateOnly();
    const fromEl = document.getElementById(fromId);
    const toEl = document.getElementById(toId);
    const fromG = fromEl ? this.jalaliToGregorianDateOnly(this.normalizeJalaliInputValue(fromEl.value || ''), false) : '';
    if (toEl && fromG) {
        const toG = this.jalaliToGregorianDateOnly(this.normalizeJalaliInputValue(toEl.value || ''), true);
        if (toG && toG < fromG) toEl.value = toPersianDate(fromG) || toEl.value; // "تا" can't precede "از"
    }
    const limit = (el, role) => {
        if (!el || !el._neoJdp) return;
        try {
            $(el).persianDatepicker({
                minDate: (role === 'to' && fromG) ? fromG : null,
                maxDate: allowFuture ? null : today,
                onSelect: () => setTimeout(() => self.syncJalaliRangeFilter(fromId, toId, el, opts), 0)
            });
        } catch (_) {}
    };
    limit(fromEl, 'from');
    limit(toEl, 'to');
};

app.initOperationManagerDatePickers = function() {
    // Filter date ranges: max = today (no future), with from<=to coupling, on the shared component.
    this.initJalaliRangeFilter('om-lead-date-from', 'om-lead-date-to');
    this.initJalaliRangeFilter('om-last-activity-from', 'om-last-activity-to');
    this.initJalaliRangeFilter('om-last-deal-from', 'om-last-deal-to');
    this.initJalaliRangeFilter('om-last-payment-from', 'om-last-payment-to');
};

app.operationManagerDateValue = function(id, endOfDay = false) {
    const raw = (document.getElementById(id)?.value || '').trim();
    if (!raw) return '';
    const normalized = this.toEnglishDigits(raw).replace(/\//g, '-');
    if (/^20\d{2}-\d{1,2}-\d{1,2}$/.test(normalized)) return normalized;
    const converted = persianToGregorian(this.toEnglishDigits(raw), endOfDay ? '23:59' : '00:00');
    return converted ? String(converted).split(' ')[0] : normalized;
};

app.collectOperationManagerFilters = function() {
    const filters = {};
    const add = (key, value) => {
        const v = typeof value === 'string' ? value.trim() : value;
        if (v !== '' && v !== null && v !== undefined) filters[key] = v;
    };
    add('search', document.getElementById('om-search')?.value || '');
    const ownerFilterValues = this.getOmOwnerFilterValues();
    const poolValues = ownerFilterValues.filter(v => String(v).startsWith('__pool:'));
    const ownerIds = ownerFilterValues.filter(v => !String(v).startsWith('__pool:'));
    if (poolValues.length > 0) {
        add('pool_key', String(poolValues[0]).slice(7));
        filters.no_owner = 1;
    }
    if (ownerIds.length > 0) {
        filters.owner_didar_ids = ownerIds;
    }
    add('previous_owner_id', document.getElementById('om-prev-owner-filter')?.value || '');
    add('sale_status', document.getElementById('om-sale-status-filter')?.value || '');
    add('deal_status', document.getElementById('om-deal-status-filter')?.value || '');
    if (!filters.pool_key) {
        add('pool_key', document.getElementById('om-pool-filter')?.value || '');
    }
    add('has_payment', document.getElementById('om-has-payment-filter')?.value || '');
    add('deal_older_than_days', document.getElementById('om-deal-older-than-days')?.value || '');
    if (document.getElementById('om-no-owner-filter')?.checked) filters.no_owner = 1;
    add('lead_date_from', this.operationManagerDateValue('om-lead-date-from'));
    add('lead_date_to', this.operationManagerDateValue('om-lead-date-to', true));
    add('last_activity_from', this.operationManagerDateValue('om-last-activity-from'));
    add('last_activity_to', this.operationManagerDateValue('om-last-activity-to', true));
    add('last_deal_from', this.operationManagerDateValue('om-last-deal-from'));
    add('last_deal_to', this.operationManagerDateValue('om-last-deal-to', true));
    add('last_payment_from', this.operationManagerDateValue('om-last-payment-from'));
    add('last_payment_to', this.operationManagerDateValue('om-last-payment-to', true));
    return filters;
};

app.previewOperationManager = async function(page = 1) {
    const results = document.getElementById('om-results');
    const summary = document.getElementById('om-preview-summary');
    if (!results) return;
    results.innerHTML = '<div class="p-6 text-center text-gray-400">در حال دریافت نتایج...</div>';
    try {
        const res = await this.req('operation_manager_preview', {
            filters: JSON.stringify(this.collectOperationManagerFilters()),
            page,
            per_page: this.operationManagerState?.pagination?.per_page || 20
        }, { requestKey: 'operation_manager_preview' });
        if (res.status !== 'success') {
            results.innerHTML = `<div class="p-6 text-center text-red-400">${this.escapeHtml(res.message || 'خطا در دریافت نتایج')}</div>`;
            return;
        }
        this.operationManagerState.rows = res.rows || [];
        this.operationManagerState.pagination = res.pagination || { current_page: page, per_page: 20, total_count: 0, total_pages: 0 };
        if (summary) {
            summary.innerHTML = `تعداد مطابق فیلتر: <b class="text-yellow-400">${this.formatNumber(this.operationManagerState.pagination.total_count || 0)}</b> | انتخاب‌شده: <b class="text-cyan-300">${this.operationManagerState.selectAllMatching ? 'همه' : this.formatNumber(this.operationManagerState.selectedIds.size || 0)}</b>`;
        }
        this.renderOperationManagerRows();
        this.renderOperationManagerPagination();
    } catch (e) {
        results.innerHTML = `<div class="p-6 text-center text-red-400">${this.escapeHtml(e.message || 'خطای ارتباط')}</div>`;
    }
};

app.renderOperationManagerRows = function() {
    const container = document.getElementById('om-results');
    if (!container) return;
    const rows = this.operationManagerState.rows || [];
    if (!rows.length) {
        container.innerHTML = '<div class="p-8 text-center text-gray-500">موردی مطابق فیلترها پیدا نشد</div>';
        return;
    }
    const money = value => Number(value || 0) > 0 ? `${this.formatNumber(Number(value || 0))} تومان` : '-';
    container.innerHTML = `
        <table class="w-full text-sm">
            <thead class="bg-gray-900/80 text-gray-400 border-b border-gray-700">
                <tr>
                    <th class="p-3 text-right w-10"></th>
                    <th class="p-3 text-right">لید</th>
                    <th class="p-3 text-right">مسئول</th>
                    <th class="p-3 text-right">آخرین معامله</th>
                    <th class="p-3 text-right">آخرین فعالیت</th>
                    <th class="p-3 text-right">آخرین پرداخت</th>
                    <th class="p-3 text-right">مخزن</th>
                </tr>
            </thead>
            <tbody class="divide-y divide-gray-800">
                ${rows.map(row => {
                    const id = String(row.person_id || '');
                    const selected = this.operationManagerState.selectedIds.has(id);
                    const name = `${row.first_name || ''} ${row.last_name || ''}`.trim() || row.display_name || '-';
                    return `
                        <tr class="hover:bg-gray-800/50">
                            <td class="p-3 align-top">
                                <input type="checkbox" class="accent-yellow-500" ${selected ? 'checked' : ''} onchange="app.toggleOperationManagerRow('${this.escapeHtml(id)}', this.checked)">
                            </td>
                            <td class="p-3 align-top">
                                <button onclick="app.openPersonProfile('${this.escapeHtml(row.didar_contact_id || id)}')" class="text-right">
                                    <div class="font-bold text-white hover:text-yellow-400">${this.escapeHtml(name)}</div>
                                    <div class="text-xs text-gray-500 dir-ltr text-left">${this.escapeHtml(row.mobile_phone || '-')}</div>
                                    <div class="text-[11px] text-gray-500">کد مشتری: ${this.escapeHtml(row.customer_code || '-')}</div>
                                </button>
                            </td>
                            <td class="p-3 align-top">
                                <div class="text-gray-200">${this.escapeHtml(row.owner_name || 'بدون مسئول')}</div>
                                <div class="text-[11px] text-gray-500">قبلی: ${this.escapeHtml(row.previous_owner_name || '-')}</div>
                            </td>
                            <td class="p-3 align-top">
                                <div class="text-gray-200">${this.escapeHtml(row.last_deal_status || '-')}</div>
                                <div class="text-[11px] text-gray-500">${this.formatKpiDate(row.last_deal_at || '') || '-'}</div>
                                <div class="text-[11px] text-gray-500">${this.escapeHtml(row.last_deal_pipeline_stage || '')}</div>
                            </td>
                            <td class="p-3 align-top">
                                <div class="text-gray-200">${this.formatKpiDate(row.last_activity_at || '') || '-'}</div>
                                <div class="text-[11px] text-gray-500">${this.escapeHtml(row.last_activity_type || '-')}</div>
                            </td>
                            <td class="p-3 align-top">
                                <div class="${row.has_any_payment ? 'text-emerald-300' : 'text-gray-500'}">${this.formatKpiDate(row.last_payment_at || '') || '-'}</div>
                                <div class="text-[11px] text-gray-500">${money(row.last_payment_amount)}</div>
                            </td>
                            <td class="p-3 align-top">
                                <span class="px-2 py-1 rounded bg-gray-800 border border-gray-700 text-xs">${this.escapeHtml(row.pool_title || row.pool_key || '-')}</span>
                            </td>
                        </tr>
                    `;
                }).join('')}
            </tbody>
        </table>
    `;
};

app.renderOperationManagerPagination = function() {
    const container = document.getElementById('om-pagination');
    if (!container) return;
    const p = this.operationManagerState.pagination || {};
    const totalPages = Number(p.total_pages || 0);
    if (totalPages <= 1) {
        container.innerHTML = '';
        return;
    }
    const current = Number(p.current_page || 1);
    const start = Math.max(1, current - 2);
    const end = Math.min(totalPages, current + 2);
    let html = '<div class="flex justify-center items-center gap-2 flex-wrap">';
    html += `<button ${current <= 1 ? 'disabled' : ''} onclick="app.previewOperationManager(${current - 1})" class="px-3 py-1 bg-gray-800 hover:bg-gray-700 disabled:opacity-40 rounded text-sm">قبلی</button>`;
    for (let i = start; i <= end; i++) {
        html += `<button onclick="app.previewOperationManager(${i})" class="px-3 py-1 ${i === current ? 'bg-yellow-600 text-white' : 'bg-gray-800 hover:bg-gray-700 text-gray-200'} rounded text-sm">${i}</button>`;
    }
    html += `<button ${current >= totalPages ? 'disabled' : ''} onclick="app.previewOperationManager(${current + 1})" class="px-3 py-1 bg-gray-800 hover:bg-gray-700 disabled:opacity-40 rounded text-sm">بعدی</button>`;
    html += `<span class="text-xs text-gray-500 mr-2">${this.formatNumber(p.total_count || 0)} مورد</span></div>`;
    container.innerHTML = html;
};

app.toggleOperationManagerRow = function(id, checked) {
    this.operationManagerState.selectedIds = this.operationManagerState.selectedIds || new Set();
    if (checked) this.operationManagerState.selectedIds.add(String(id));
    else this.operationManagerState.selectedIds.delete(String(id));
    this.operationManagerState.selectAllMatching = false;
    const all = document.getElementById('om-select-all-matching');
    if (all) all.checked = false;
    this.updateOperationManagerSummarySelection();
};

app.toggleOperationManagerAllMatching = function(checked) {
    this.operationManagerState.selectAllMatching = !!checked;
    if (checked) this.operationManagerState.selectedIds.clear();
    this.renderOperationManagerRows();
    this.updateOperationManagerSummarySelection();
};

app.clearOperationManagerSelection = function() {
    this.operationManagerState.selectedIds.clear();
    this.operationManagerState.selectAllMatching = false;
    const all = document.getElementById('om-select-all-matching');
    if (all) all.checked = false;
    this.renderOperationManagerRows();
    this.updateOperationManagerSummarySelection();
};

app.updateOperationManagerSummarySelection = function() {
    const summary = document.getElementById('om-preview-summary');
    const p = this.operationManagerState.pagination || {};
    if (summary) {
        summary.innerHTML = `تعداد مطابق فیلتر: <b class="text-yellow-400">${this.formatNumber(p.total_count || 0)}</b> | انتخاب‌شده: <b class="text-cyan-300">${this.operationManagerState.selectAllMatching ? 'همه' : this.formatNumber(this.operationManagerState.selectedIds.size || 0)}</b>`;
    }
};

app.updateOperationManagerActionVisibility = function() {
    const type = document.getElementById('om-operation-type')?.value || 'change_owner';
    const ownerWrap = document.getElementById('om-operation-owner-wrap');
    if (ownerWrap) ownerWrap.classList.toggle('hidden', type !== 'change_owner');
};

app.buildOperationManagerOperation = function() {
    const type = document.getElementById('om-operation-type')?.value || 'change_owner';
    const reason = (document.getElementById('om-operation-reason')?.value || '').trim();
    if (type === 'change_owner') {
        const owner = document.getElementById('om-operation-owner')?.value || '';
        if (!owner) throw new Error('مسئول جدید را انتخاب کنید');
        return { actions: [{ type: 'change_owner', owner_didar_id: owner, reason }] };
    }
    if (type === 'move_to_crm_pool') {
        return { actions: [{ type: 'move_lead_to_pool', pool_key: 'crm_leads', reason: reason || 'Manual Operation Manager move' }] };
    }
    return {
        actions: [
            { type: 'mark_last_deal_lost', failure_reason: 'sales_agent_underperformance', reason: reason || 'عدم توانمندی کارشناس فروش' },
            { type: 'move_lead_to_pool', pool_key: 'crm_leads', reason: reason || 'Moved by Operation Manager' }
        ]
    };
};

app.executeOperationManager = async function() {
    try {
        const selectedIds = Array.from(this.operationManagerState.selectedIds || []);
        if (!this.operationManagerState.selectAllMatching && selectedIds.length === 0) {
            Swal.fire('انتخاب نشده', 'حداقل یک لید را انتخاب کنید یا گزینه انتخاب همه موارد مطابق فیلتر را بزنید.', 'warning');
            return;
        }
        const operation = this.buildOperationManagerOperation();
        const countText = this.operationManagerState.selectAllMatching ? 'همه موارد مطابق فیلتر' : `${this.formatNumber(selectedIds.length)} لید`;
        const confirmed = await Swal.fire({
            title: 'اجرای عملیات',
            text: `عملیات روی ${countText} اجرا شود؟`,
            icon: 'warning',
            showCancelButton: true,
            confirmButtonText: 'اجرا',
            cancelButtonText: 'انصراف',
            reverseButtons: true
        });
        if (!confirmed.isConfirmed) return;
        const filters = this.collectOperationManagerFilters();
        const res = await this.req('operation_manager_execute', {
            filters: JSON.stringify(filters),
            operation: JSON.stringify(operation),
            selected_ids: JSON.stringify(selectedIds),
            select_all: this.operationManagerState.selectAllMatching ? 1 : 0
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'عملیات انجام نشد', 'error');
            return;
        }
        Swal.fire('انجام شد', `موفق: ${this.formatNumber(res.summary?.success || 0)} | خطا: ${this.formatNumber(res.summary?.failed || 0)}`, 'success');
        this.clearOperationManagerSelection();
        await Promise.all([this.previewOperationManager(1), this.loadOperationManagerRuns(), this.loadOperationManagerPools()]);
    } catch (e) {
        Swal.fire('خطا', e.message || 'خطا در اجرای عملیات', 'error');
    }
};

app.clearOperationManagerFilters = function() {
    ['om-search','om-deal-older-than-days','om-lead-date-from','om-lead-date-to','om-last-activity-from','om-last-activity-to','om-last-deal-from','om-last-deal-to','om-last-payment-from','om-last-payment-to'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
    ['om-prev-owner-filter','om-sale-status-filter','om-deal-status-filter','om-pool-filter','om-has-payment-filter'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
    document.querySelectorAll('.om-owner-check').forEach(cb => { cb.checked = false; });
    const allCb = document.getElementById('om-owner-all');
    if (allCb) allCb.checked = true;
    this.omOwnerFilterUpdateLabel();
    const noOwner = document.getElementById('om-no-owner-filter');
    if (noOwner) noOwner.checked = false;
    this.clearOperationManagerSelection();
    this.previewOperationManager(1);
};

app.exportOperationManagerResults = async function() {
    const total = this.operationManagerState?.pagination?.total_count || 0;
    if (total === 0) {
        Swal.fire('توجه', 'ابتدا پیش‌نمایش را اجرا کنید تا نتایجی برای خروجی وجود داشته باشد.', 'info');
        return;
    }

    const confirmed = await Swal.fire({
        title: 'خروجی CSV',
        text: `خروجی ${this.formatNumber(total)} رکورد مطابق فیلترهای فعلی دریافت شود؟`,
        icon: 'question',
        showCancelButton: true,
        confirmButtonText: 'دانلود',
        cancelButtonText: 'انصراف'
    });
    if (!confirmed.isConfirmed) return;

    Swal.fire({ title: 'در حال آماده‌سازی...', allowOutsideClick: false, didOpen: () => Swal.showLoading() });

    try {
        const filters = this.collectOperationManagerFilters();
        const formData = new FormData();
        formData.append('filters', JSON.stringify(filters));

        const response = await fetch('?api=operation_manager_export', {
            method: 'POST',
            body: formData,
            credentials: 'same-origin'
        });

        if (!response.ok) {
            throw new Error('خطا در دریافت فایل');
        }

        const blob = await response.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'operation_manager_' + new Date().toISOString().slice(0, 10) + '.csv';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
        Swal.close();
    } catch (e) {
        console.error('Export failed:', e);
        Swal.close();
        Swal.fire('خطا', e.message || 'خطا در خروجی گرفتن', 'error');
    }
};

app.fillDefaultOperationRule = function() {
    const name = document.getElementById('om-rule-name');
    const filter = document.getElementById('om-rule-json-filter');
    const condition = document.getElementById('om-rule-json-condition');
    const operation = document.getElementById('om-rule-json-operation');
    if (name && !name.value) name.value = 'انتقال لیدهای بدون پرداخت پس از یک ماه';
    if (filter && !filter.value) filter.value = JSON.stringify({ deal_older_than_days: 30, has_payment: 0, deal_status: 'Pending' }, null, 2);
    if (condition && !condition.value) condition.value = JSON.stringify({ all: [
        { field: 'last_deal_at', operator: 'older_than', value: 30, unit: 'days' },
        { field: 'has_any_payment', operator: 'equals', value: false },
        { field: 'last_deal_status', operator: 'not_equals', value: 'Won' }
    ] }, null, 2);
    if (operation && !operation.value) operation.value = JSON.stringify({ actions: [
        { type: 'mark_last_deal_lost', failure_reason: 'sales_agent_underperformance', reason: 'عدم توانمندی کارشناس فروش' },
        { type: 'move_lead_to_pool', pool_key: 'crm_leads', reason: 'No conversion after 30 days without payment' }
    ] }, null, 2);
    this.syncOperationRuleJsonToBuilder();
};

app.resetOperationManagerRuleForm = function() {
    ['om-rule-id','om-rule-name','om-rule-json-filter','om-rule-json-condition','om-rule-json-operation'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
    const active = document.getElementById('om-rule-active');
    if (active) active.checked = true;
    const schedule = document.getElementById('om-rule-schedule-type');
    if (schedule) schedule.value = 'daily';
    const operationType = document.getElementById('om-rule-operation-type');
    if (operationType) operationType.value = 'mark_lost_and_pool';
    const operationOwner = document.getElementById('om-rule-operation-owner');
    if (operationOwner) operationOwner.value = '';
    const reason = document.getElementById('om-rule-operation-reason');
    if (reason) reason.value = 'عدم توانمندی کارشناس فروش';
    this.operationManagerState.conditionBuilderRows = [];
    this.fillDefaultOperationRule();
};

app.parseOperationRuleJson = function(id) {
    const raw = (document.getElementById(id)?.value || '').trim();
    if (!raw) return {};
    return JSON.parse(raw);
};

app.decodeOperationRuleJson = function(rule, key) {
    const direct = rule?.[key];
    if (direct && typeof direct === 'object') return direct;
    const raw = rule?.[`${key}_json`];
    if (!raw) return {};
    try {
        const parsed = JSON.parse(raw);
        return parsed && typeof parsed === 'object' ? parsed : {};
    } catch (_) {
        return {};
    }
};

app.operationRuleConditionFields = function() {
    return {
        last_deal_at: {
            label: 'آخرین معامله',
            operators: ['older_than'],
            valueType: 'number',
            unit: 'days',
            defaultValue: 30
        },
        last_activity_at: {
            label: 'آخرین فعالیت',
            operators: ['older_than'],
            valueType: 'number',
            unit: 'days',
            defaultValue: 30
        },
        lead_registered_at: {
            label: 'تاریخ ثبت لید',
            operators: ['older_than'],
            valueType: 'number',
            unit: 'days',
            defaultValue: 30
        },
        last_payment_at: {
            label: 'آخرین پرداخت',
            operators: ['older_than'],
            valueType: 'number',
            unit: 'days',
            defaultValue: 30
        },
        has_any_payment: {
            label: 'پرداخت',
            operators: ['equals'],
            valueType: 'boolean',
            defaultValue: false
        },
        last_deal_status: {
            label: 'وضعیت آخرین معامله',
            operators: ['equals', 'not_equals'],
            valueType: 'deal_status',
            defaultValue: 'Won'
        },
        sale_status: {
            label: 'وضعیت فروش لید',
            operators: ['equals'],
            valueType: 'sale_status',
            defaultValue: 0
        },
        owner_empty: {
            label: 'مسئول ندارد',
            operators: ['equals'],
            valueType: 'boolean',
            defaultValue: true
        },
        pool_key: {
            label: 'مخزن لید',
            operators: ['equals'],
            valueType: 'pool_key',
            defaultValue: 'crm_leads'
        }
    };
};

app.operationRuleOperatorLabel = function(operator) {
    return {
        older_than: 'قدیمی‌تر از',
        equals: 'برابر با',
        not_equals: 'برابر نباشد با'
    }[operator] || operator;
};

app.normalizeOperationRuleConditionRow = function(row = {}) {
    const fields = this.operationRuleConditionFields();
    const field = fields[row.field] ? row.field : 'last_deal_at';
    const meta = fields[field];
    const operator = meta.operators.includes(row.operator) ? row.operator : meta.operators[0];
    const value = row.value !== undefined && row.value !== null && row.value !== '' ? row.value : meta.defaultValue;
    return { field, operator, value, unit: meta.unit || row.unit || '' };
};

app.addOperationRuleCondition = function(row = {}) {
    this.operationManagerState.conditionBuilderRows = this.operationManagerState.conditionBuilderRows || [];
    this.operationManagerState.conditionBuilderRows.push(this.normalizeOperationRuleConditionRow(row));
    this.renderOperationRuleConditionBuilder();
    this.syncOperationRuleBuilderToJson();
};

app.removeOperationRuleCondition = function(index) {
    this.operationManagerState.conditionBuilderRows = this.operationManagerState.conditionBuilderRows || [];
    this.operationManagerState.conditionBuilderRows.splice(index, 1);
    this.renderOperationRuleConditionBuilder();
    this.syncOperationRuleBuilderToJson();
};

app.updateOperationRuleCondition = function(index, key, value) {
    this.operationManagerState.conditionBuilderRows = this.operationManagerState.conditionBuilderRows || [];
    const row = this.operationManagerState.conditionBuilderRows[index] || {};
    row[key] = value;
    this.operationManagerState.conditionBuilderRows[index] = this.normalizeOperationRuleConditionRow(row);
    this.renderOperationRuleConditionBuilder();
    this.syncOperationRuleBuilderToJson();
};

app.renderOperationRuleConditionBuilder = function() {
    const container = document.getElementById('om-rule-condition-builder');
    if (!container) return;
    const fields = this.operationRuleConditionFields();
    const rows = this.operationManagerState.conditionBuilderRows || [];
    if (!rows.length) {
        container.innerHTML = '<div class="text-xs text-gray-500 border border-dashed border-gray-700 rounded p-3 text-center">شرطی تعریف نشده است. از «شرط جدید» یا «قالب یک ماهه» استفاده کنید.</div>';
        return;
    }
    container.innerHTML = rows.map((row, index) => {
        const meta = fields[row.field] || fields.last_deal_at;
        const fieldOptions = Object.entries(fields).map(([key, item]) => `<option value="${key}" ${key === row.field ? 'selected' : ''}>${this.escapeHtml(item.label)}</option>`).join('');
        const operatorOptions = meta.operators.map(op => `<option value="${op}" ${op === row.operator ? 'selected' : ''}>${this.escapeHtml(this.operationRuleOperatorLabel(op))}</option>`).join('');
        return `
            <div class="grid grid-cols-12 gap-2 items-center bg-gray-950/50 border border-gray-800 rounded p-2">
                <select class="input bg-gray-900 col-span-12 md:col-span-4 text-xs" onchange="app.updateOperationRuleCondition(${index}, 'field', this.value)">
                    ${fieldOptions}
                </select>
                <select class="input bg-gray-900 col-span-6 md:col-span-3 text-xs" onchange="app.updateOperationRuleCondition(${index}, 'operator', this.value)">
                    ${operatorOptions}
                </select>
                <div class="col-span-5 md:col-span-4">
                    ${this.renderOperationRuleConditionValue(index, row, meta)}
                </div>
                <button onclick="app.removeOperationRuleCondition(${index})" class="col-span-1 text-red-300 hover:text-red-200 text-sm" title="حذف شرط">
                    <i class="fa-solid fa-trash"></i>
                </button>
            </div>
        `;
    }).join('');
};

app.renderOperationRuleConditionValue = function(index, row, meta) {
    const value = row.value;
    if (meta.valueType === 'boolean') {
        return `
            <select class="input bg-gray-900 w-full text-xs" onchange="app.updateOperationRuleCondition(${index}, 'value', this.value === 'true')">
                <option value="true" ${value === true || value === 1 || value === 'true' ? 'selected' : ''}>بله</option>
                <option value="false" ${value === false || value === 0 || value === 'false' ? 'selected' : ''}>خیر</option>
            </select>
        `;
    }
    if (meta.valueType === 'deal_status') {
        return `
            <select class="input bg-gray-900 w-full text-xs" onchange="app.updateOperationRuleCondition(${index}, 'value', this.value)">
                <option value="Pending" ${value === 'Pending' ? 'selected' : ''}>در جریان</option>
                <option value="Won" ${value === 'Won' ? 'selected' : ''}>موفق</option>
                <option value="Lost" ${value === 'Lost' ? 'selected' : ''}>ناموفق</option>
            </select>
        `;
    }
    if (meta.valueType === 'sale_status') {
        return `
            <select class="input bg-gray-900 w-full text-xs" onchange="app.updateOperationRuleCondition(${index}, 'value', Number(this.value))">
                <option value="0" ${Number(value) === 0 ? 'selected' : ''}>ناموفق</option>
                <option value="1" ${Number(value) === 1 ? 'selected' : ''}>موفق</option>
            </select>
        `;
    }
    if (meta.valueType === 'pool_key') {
        return `
            <select class="input bg-gray-900 w-full text-xs" onchange="app.updateOperationRuleCondition(${index}, 'value', this.value)">
                <option value="crm_leads" ${value === 'crm_leads' ? 'selected' : ''}>لید بدون مسئول</option>
            </select>
        `;
    }
    return `
        <div class="flex items-center gap-2">
            <input type="number" min="1" class="input bg-gray-900 w-full text-xs" value="${this.escapeHtml(String(value || 30))}" onchange="app.updateOperationRuleCondition(${index}, 'value', Number(this.value || 0))">
            <span class="text-[11px] text-gray-500 whitespace-nowrap">روز</span>
        </div>
    `;
};

app.syncOperationRuleBuilderToJson = function() {
    const conditionTextarea = document.getElementById('om-rule-json-condition');
    const operationTextarea = document.getElementById('om-rule-json-operation');
    const rows = (this.operationManagerState.conditionBuilderRows || []).map(row => this.normalizeOperationRuleConditionRow(row));
    if (conditionTextarea) {
        conditionTextarea.value = JSON.stringify({ all: rows }, null, 2);
    }
    if (operationTextarea) {
        operationTextarea.value = JSON.stringify(this.buildOperationRuleOperationFromBuilder(), null, 2);
    }
    const operationType = document.getElementById('om-rule-operation-type')?.value || 'mark_lost_and_pool';
    const ownerWrap = document.getElementById('om-rule-operation-owner-wrap');
    if (ownerWrap) ownerWrap.classList.toggle('hidden', operationType !== 'change_owner');
};

app.syncOperationRuleJsonToBuilder = function() {
    try {
        const condition = this.parseOperationRuleJson('om-rule-json-condition');
        const rows = Array.isArray(condition.all) ? condition.all : [];
        this.operationManagerState.conditionBuilderRows = rows.map(row => this.normalizeOperationRuleConditionRow(row));
        this.renderOperationRuleConditionBuilder();
    } catch (_) {}
    try {
        const operation = this.parseOperationRuleJson('om-rule-json-operation');
        this.applyOperationRuleOperationToBuilder(operation);
    } catch (_) {}
};

app.buildOperationRuleOperationFromBuilder = function() {
    const type = document.getElementById('om-rule-operation-type')?.value || 'mark_lost_and_pool';
    const owner = document.getElementById('om-rule-operation-owner')?.value || '';
    const reason = (document.getElementById('om-rule-operation-reason')?.value || '').trim() || 'Operation Manager rule';
    if (type === 'change_owner') {
        return { actions: [{ type: 'change_owner', owner_didar_id: owner, reason }] };
    }
    if (type === 'move_to_crm_pool') {
        return { actions: [{ type: 'move_lead_to_pool', pool_key: 'crm_leads', reason }] };
    }
    return { actions: [
        { type: 'mark_last_deal_lost', failure_reason: 'sales_agent_underperformance', reason },
        { type: 'move_lead_to_pool', pool_key: 'crm_leads', reason }
    ] };
};

app.applyOperationRuleOperationToBuilder = function(operation) {
    const actions = Array.isArray(operation?.actions) ? operation.actions : [];
    const hasLost = actions.some(a => a.type === 'mark_last_deal_lost');
    const poolAction = actions.find(a => a.type === 'move_lead_to_pool');
    const ownerAction = actions.find(a => a.type === 'change_owner');
    const typeEl = document.getElementById('om-rule-operation-type');
    if (typeEl) {
        typeEl.value = ownerAction ? 'change_owner' : (hasLost && poolAction ? 'mark_lost_and_pool' : 'move_to_crm_pool');
    }
    const ownerEl = document.getElementById('om-rule-operation-owner');
    if (ownerEl && ownerAction) ownerEl.value = ownerAction.owner_didar_id || '';
    const reasonEl = document.getElementById('om-rule-operation-reason');
    const reason = ownerAction?.reason || poolAction?.reason || actions[0]?.reason || '';
    if (reasonEl && reason) reasonEl.value = reason;
    const ownerWrap = document.getElementById('om-rule-operation-owner-wrap');
    if (ownerWrap && typeEl) ownerWrap.classList.toggle('hidden', typeEl.value !== 'change_owner');
};

app.saveOperationManagerRule = async function() {
    try {
        this.syncOperationRuleBuilderToJson();
        const name = (document.getElementById('om-rule-name')?.value || '').trim();
        if (!name) throw new Error('نام شرط را وارد کنید');
        const res = await this.req('operation_rules_save', {
            id: document.getElementById('om-rule-id')?.value || '',
            name,
            is_active: document.getElementById('om-rule-active')?.checked ? 1 : 0,
            schedule_type: document.getElementById('om-rule-schedule-type')?.value || 'daily',
            filter: JSON.stringify(this.parseOperationRuleJson('om-rule-json-filter')),
            condition: JSON.stringify(this.parseOperationRuleJson('om-rule-json-condition')),
            operation: JSON.stringify(this.parseOperationRuleJson('om-rule-json-operation'))
        });
        if (res.status !== 'success') {
            Swal.fire('خطا', res.message || 'شرط ذخیره نشد', 'error');
            return;
        }
        Swal.fire('ذخیره شد', 'شرط عملیات ذخیره شد.', 'success');
        await this.loadOperationManagerRules();
        this.resetOperationManagerRuleForm();
    } catch (e) {
        Swal.fire('خطا', e.message || 'JSON نامعتبر است', 'error');
    }
};

app.loadOperationManagerRules = async function() {
    const container = document.getElementById('om-rules-list');
    if (!container) return;
    container.innerHTML = '<div class="text-xs text-gray-500">در حال دریافت شرط‌ها...</div>';
    const res = await this.req('operation_rules_list');
    if (res.status !== 'success') {
        container.innerHTML = `<div class="text-xs text-red-400">${this.escapeHtml(res.message || 'خطا')}</div>`;
        return;
    }
    this.operationManagerState.rules = res.rules || [];
    if (!this.operationManagerState.rules.length) {
        container.innerHTML = '<div class="text-xs text-gray-500">هنوز شرطی ذخیره نشده است.</div>';
        return;
    }
    const canManageRules = this.canCurrentUserCapabilityClient('operation_manager.rules.manage');
    const canRunRules = this.canCurrentUserCapabilityClient('operation_manager.automation.run');
    container.innerHTML = this.operationManagerState.rules.map(rule => `
        <div class="bg-gray-900/70 border border-gray-700 rounded p-3">
            <div class="flex items-start justify-between gap-2">
                <div>
                    <div class="font-bold text-sm text-white">${this.escapeHtml(rule.name || '-')}</div>
                    <div class="text-[11px] text-gray-500">اجرا: ${this.escapeHtml(rule.schedule_type || '-')} | وضعیت: ${Number(rule.is_active) === 1 ? 'فعال' : 'غیرفعال'}</div>
                </div>
                <div class="flex gap-2 text-xs">
                    ${canManageRules ? `<button onclick="app.editOperationManagerRule(${Number(rule.id)})" class="text-cyan-300 hover:text-cyan-200">ویرایش</button>` : ''}
                    ${canRunRules ? `<button onclick="app.runOperationManagerRule(${Number(rule.id)})" class="text-yellow-300 hover:text-yellow-200">اجرا</button>` : ''}
                    ${canManageRules ? `<button onclick="app.deleteOperationManagerRule(${Number(rule.id)})" class="text-red-300 hover:text-red-200">حذف</button>` : ''}
                </div>
            </div>
        </div>
    `).join('');
};

app.editOperationManagerRule = function(id) {
    const rule = (this.operationManagerState.rules || []).find(r => Number(r.id) === Number(id));
    if (!rule) return;
    document.getElementById('om-rule-id').value = rule.id || '';
    document.getElementById('om-rule-name').value = rule.name || '';
    document.getElementById('om-rule-active').checked = Number(rule.is_active) === 1;
    document.getElementById('om-rule-schedule-type').value = rule.schedule_type || 'daily';
    document.getElementById('om-rule-json-filter').value = JSON.stringify(this.decodeOperationRuleJson(rule, 'filter'), null, 2);
    document.getElementById('om-rule-json-condition').value = JSON.stringify(this.decodeOperationRuleJson(rule, 'condition'), null, 2);
    document.getElementById('om-rule-json-operation').value = JSON.stringify(this.decodeOperationRuleJson(rule, 'operation'), null, 2);
    this.syncOperationRuleJsonToBuilder();
};

app.runOperationManagerRule = async function(id) {
    const confirmed = await Swal.fire({ title: 'اجرای شرط', text: 'این شرط الان اجرا شود؟', icon: 'warning', showCancelButton: true, confirmButtonText: 'اجرا', cancelButtonText: 'انصراف', reverseButtons: true });
    if (!confirmed.isConfirmed) return;
    const res = await this.req('operation_rules_run', { id });
    if (res.status === 'success') {
        Swal.fire('انجام شد', `موفق: ${this.formatNumber(res.summary?.success || 0)} | خطا: ${this.formatNumber(res.summary?.failed || 0)}`, 'success');
        await Promise.all([this.previewOperationManager(1), this.loadOperationManagerRuns(), this.loadOperationManagerPools()]);
    } else {
        Swal.fire('خطا', res.message || 'اجرا نشد', 'error');
    }
};

app.deleteOperationManagerRule = async function(id) {
    const confirmed = await Swal.fire({ title: 'حذف شرط', text: 'این شرط حذف شود؟', icon: 'warning', showCancelButton: true, confirmButtonText: 'حذف', cancelButtonText: 'انصراف', reverseButtons: true });
    if (!confirmed.isConfirmed) return;
    const res = await this.req('operation_rules_delete', { id });
    if (res.status === 'success') {
        await this.loadOperationManagerRules();
    } else {
        Swal.fire('خطا', res.message || 'حذف نشد', 'error');
    }
};

app.loadOperationManagerRuns = async function() {
    const container = document.getElementById('om-runs-list');
    if (!container) return;
    const res = await this.req('operation_runs_list', { limit: 5 });
    if (res.status !== 'success') {
        container.innerHTML = `<div class="text-xs text-red-400">${this.escapeHtml(res.message || 'خطا')}</div>`;
        return;
    }
    const runs = res.runs || [];
    if (!runs.length) {
        container.innerHTML = '<div class="text-xs text-gray-500">اجرایی ثبت نشده است.</div>';
        return;
    }
    container.innerHTML = runs.map(run => `
        <div class="bg-gray-900/70 border border-gray-700 rounded p-3">
            <div class="flex justify-between gap-2">
                <div class="font-bold text-white text-xs">${this.escapeHtml(run.rule_name || run.source || '-')}</div>
                <div class="text-[11px] text-gray-500">${this.formatKpiDate(run.started_at || '') || '-'}</div>
            </div>
            <div class="text-[11px] text-gray-400 mt-1">اثرگذار: ${this.formatNumber(run.affected_count || 0)} | خطا: ${this.formatNumber(run.failed_count || 0)} | بررسی: ${this.formatNumber(run.matched_count || 0)}</div>
        </div>
    `).join('');
};

app.loadOperationManagerPools = async function() {
    const container = document.getElementById('om-pools-list');
    if (!container) return;
    const res = await this.req('crm_lead_pool_list');
    if (res.status !== 'success') {
        container.innerHTML = `<div class="text-xs text-red-400">${this.escapeHtml(res.message || 'خطا')}</div>`;
        return;
    }
    const pools = res.pools || [];
    if (!pools.length) {
        container.innerHTML = '<div class="text-xs text-gray-500">مخزنی ثبت نشده است.</div>';
        return;
    }
    container.innerHTML = pools.map(pool => `
        <div class="bg-gray-900/70 border border-gray-700 rounded p-3 flex items-center justify-between">
            <div>
                <div class="font-bold text-white">${this.escapeHtml(pool.title || pool.name || pool.pool_key || '-')}</div>
                <div class="text-[11px] text-gray-500">${this.escapeHtml(pool.description || pool.pool_key || '')}</div>
            </div>
            <div class="text-lg font-bold text-emerald-300">${this.formatNumber(pool.lead_count || 0)}</div>
        </div>
    `).join('');
};

/**
 * Quick Contact - Make phone calls with number selection
 */
app.quickContact = function(phone, leadId = null) {
    // Haptic feedback
    this.haptic(10);
    
    // If leadId provided, get lead data to check for multiple phones
    if (leadId) {
        const lead = this.state.leads.find(l => l.didar_contact_id === leadId || l.id === leadId);
        if (lead) {
            this.showPhoneSelector(lead);
            return;
        }
    }
    
    // Single phone, make call directly
    if (phone) {
        this.makePhoneCall(phone);
    }
};

/**
 * Show phone number selector for leads with multiple numbers
 */
app.showPhoneSelector = function(lead) {
    const phones = [];
    
    // Collect all available phone numbers
    if (lead.mobile_phone) phones.push({ number: lead.mobile_phone, label: 'شماره اصلی', icon: 'fa-mobile-alt' });
    if (lead.secondary_mobile_phone) phones.push({ number: lead.secondary_mobile_phone, label: 'شماره دوم', icon: 'fa-mobile-alt' });
    if (lead.mobile_phone_3) phones.push({ number: lead.mobile_phone_3, label: 'شماره سوم', icon: 'fa-mobile-alt' });
    if (lead.mobile_phone_4) phones.push({ number: lead.mobile_phone_4, label: 'شماره چهارم', icon: 'fa-mobile-alt' });
    if (lead.work_phone) phones.push({ number: lead.work_phone, label: 'شماره کار', icon: 'fa-building' });
    
    // If only one phone, call directly
    if (phones.length === 1) {
        this.makePhoneCall(phones[0].number);
        return;
    }
    
    // Show phone selection modal
    const name = `${lead.first_name || ''} ${lead.last_name || ''}`;
    const self = this; // Store reference to app
    
    Swal.fire({
        title: 'انتخاب شماره تماس',
        html: `
            <div class="text-right mb-4">
                <p class="text-sm text-gray-400 mb-2">تماس با:</p>
                <p class="text-lg font-bold text-white">${name}</p>
            </div>
            <div class="space-y-2">
                ${phones.map((phone, index) => `
                    <button data-phone="${phone.number}"
                            class="phone-select-btn w-full bg-gray-800 hover:bg-gray-700 border border-gray-700 
                                   text-white py-4 px-4 rounded-lg transition flex items-center gap-3
                                   touch-input haptic-button">
                        <i class="fa-solid ${phone.icon} text-yellow-500 text-xl"></i>
                        <div class="text-right flex-1">
                            <div class="font-bold text-sm">${phone.label}</div>
                            <div class="text-xs text-gray-400 dir-ltr text-left">${phone.number}</div>
                        </div>
                        <i class="fa-solid fa-phone text-green-500"></i>
                    </button>
                `).join('')}
            </div>
            <div class="mt-4 pt-4 border-t border-gray-700">
                <button onclick="Swal.close()" 
                        class="w-full bg-gray-700 hover:bg-gray-600 text-white py-3 rounded-lg transition">
                    انصراف
                </button>
            </div>
        `,
        showConfirmButton: false,
        customClass: {
            popup: 'glass',
            htmlContainer: 'text-right'
        },
        didOpen: () => {
            // Add click handlers to phone buttons
            document.querySelectorAll('.phone-select-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const phoneNumber = this.getAttribute('data-phone');
                    // Close modal first, then make call
                    Swal.close();
                    setTimeout(() => {
                        self.makePhoneCall(phoneNumber);
                    }, 300);
                });
                
                // Add haptic feedback
                btn.addEventListener('click', () => {
                    self.haptic(10);
                });
            });
        }
    });
};

/**
 * Make actual phone call
 */
app.makePhoneCall = function(phoneNumber) {
    if (!phoneNumber) {
        Swal.fire('خطا', 'شماره تماسی وجود ندارد', 'error');
        return;
    }
    
    // Clean phone number (remove spaces, dashes, etc.)
    const cleanNumber = phoneNumber.replace(/[^\d+]/g, '');
    
    // Show calling confirmation
    Swal.fire({
        title: 'تماس تلفنی',
        html: `
            <div class="text-center py-4">
                <i class="fa-solid fa-phone text-green-500 text-4xl mb-4 animate-pulse"></i>
                <p class="text-lg font-bold text-white dir-ltr">${cleanNumber}</p>
                <p class="text-sm text-gray-400 mt-2">در حال برقراری تماس...</p>
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'تماس',
        cancelButtonText: 'انصراف',
        confirmButtonColor: '#22c55e',
        cancelButtonColor: '#6b7280',
        reverseButtons: true,
        customClass: {
            popup: 'glass'
        }
    }).then((result) => {
        if (result.isConfirmed) {
            // Create tel: link and trigger it
            const telLink = document.createElement('a');
            telLink.href = `tel:${cleanNumber}`;
            document.body.appendChild(telLink);
            telLink.click();
            document.body.removeChild(telLink);
            
            // Log the call
            console.log(`[CALL] Initiating call to: ${cleanNumber}`);
            
            // Haptic feedback
            this.haptic([10, 50, 10]);
        }
    });
};

// Initialize advanced features on DOM ready
if (typeof document !== 'undefined') {
    document.addEventListener('DOMContentLoaded', () => {
        setTimeout(() => {
            initAdvancedMobileFeatures();
            app.addHapticToButtons();
        }, 1000);
    });
}


