/**
 * ContractListSection.js — ماژول قراردادها (طراحی جدید)
 */
import SectionBase from "../../accounting/SectionBase.js";

const TYPE_META = {
    purchase:     { label: 'خرید ملک',          icon: '🏠', color: '#3b82f6', bg: '#eff6ff', border: '#bfdbfe' },
    construction: { label: 'مشارکت در ساخت',    icon: '🏗️', color: '#f97316', bg: '#fff7ed', border: '#fed7aa' },
    sale:         { label: 'فروش واحد',          icon: '🔑', color: '#10b981', bg: '#f0fdf4', border: '#a7f3d0' },
    lease:        { label: 'اجاره',              icon: '📋', color: '#8b5cf6', bg: '#f5f3ff', border: '#ddd6fe' },
    consulting:   { label: 'مشاوره',             icon: '💼', color: '#6b7280', bg: '#f9fafb', border: '#e5e7eb' },
};
const STATUS_META = {
    draft:      { label: 'پیش‌نویس',  color: '#94a3b8', bg: '#f8fafc' },
    active:     { label: 'فعال',      color: '#10b981', bg: '#f0fdf4' },
    completed:  { label: 'تکمیل‌شده', color: '#3b82f6', bg: '#eff6ff' },
    terminated: { label: 'فسخ‌شده',   color: '#ef4444', bg: '#fef2f2' },
    suspended:  { label: 'معلق',      color: '#f59e0b', bg: '#fffbeb' },
};
const ROLE_META = {
    seller:     { label: 'فروشنده', color: '#3b82f6' },
    buyer:      { label: 'خریدار',  color: '#10b981' },
    contractor: { label: 'پیمانکار',color: '#f97316' },
    consultant: { label: 'مشاور',   color: '#8b5cf6' },
    guarantor:  { label: 'ضامن',    color: '#f59e0b' },
    owner:      { label: 'مالک',    color: '#6b7280' },
    other:      { label: 'سایر',    color: '#94a3b8' },
};

/* ─── Inline CSS (یک‌بار تزریق می‌شود) ─── */
const CSS = `
.cl-wrap { display:flex;height:calc(100vh - 120px);overflow:hidden;direction:rtl;font-family:inherit; }

/* ── sidebar لیست ── */
.cl-sidebar {
    width:340px;flex-shrink:0;display:flex;flex-direction:column;
    border-left:1px solid #e2e8f0;background:#fafafa;
    transition:width .25s;
}
.cl-toolbar {
    padding:14px;border-bottom:1px solid #e2e8f0;background:#fff;
    display:flex;flex-direction:column;gap:8px;
}
.cl-toolbar-top { display:flex;gap:6px;align-items:center; }
.cl-search {
    flex:1;padding:8px 12px;border:1.5px solid #e2e8f0;border-radius:10px;
    font-size:.82rem;font-family:inherit;background:#f8fafc;
    transition:border-color .2s,background .2s;
}
.cl-search:focus { outline:none;border-color:#3b82f6;background:#fff; }
.cl-filter-row { display:flex;gap:6px; }
.cl-sel {
    flex:1;padding:6px 8px;border:1.5px solid #e2e8f0;border-radius:8px;
    font-size:.78rem;font-family:inherit;background:#fff;
}
.cl-btn {
    padding:7px 14px;border-radius:10px;border:1.5px solid #e2e8f0;
    background:#fff;font-size:.78rem;font-weight:700;cursor:pointer;
    font-family:inherit;color:#374151;transition:all .15s;white-space:nowrap;
}
.cl-btn:hover { border-color:#cbd5e1;background:#f8fafc; }
.cl-btn--primary { background:#3b82f6;color:#fff;border-color:#2563eb; }
.cl-btn--primary:hover { background:#2563eb; }
.cl-btn--orange { background:#f97316;color:#fff;border-color:#ea580c; }
.cl-btn--orange:hover { background:#ea580c; }
.cl-btn--sm { padding:5px 10px;font-size:.72rem; }
.cl-btn--xs { padding:3px 8px;font-size:.68rem;border-radius:7px; }

/* آمار sidebar */
.cl-stats { display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:12px 14px;border-bottom:1px solid #e2e8f0; }
.cl-stat { background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:8px 10px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.03); }
.cl-stat-n { font-size:1.1rem;font-weight:800;line-height:1; }
.cl-stat-l { font-size:.68rem;color:#94a3b8;margin-top:2px; }

/* لیست کارت‌ها */
.cl-list { flex:1;overflow-y:auto;padding:10px 8px; }
.cl-list::-webkit-scrollbar { width:4px; }
.cl-list::-webkit-scrollbar-thumb { background:#e2e8f0;border-radius:99px; }

/* کارت */
.cl-card {
    background:#fff;border:1.5px solid #e2e8f0;border-radius:14px;
    padding:12px 14px;margin-bottom:8px;cursor:pointer;
    transition:all .18s;position:relative;overflow:hidden;
    animation:cardIn .3s ease both;
}
.cl-card::before {
    content:'';position:absolute;top:0;right:0;bottom:0;width:4px;
    border-radius:0 4px 4px 0;background:var(--tc,#94a3b8);
}
@keyframes cardIn { from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none} }
.cl-card:hover { border-color:#cbd5e1;box-shadow:0 4px 16px rgba(0,0,0,.06);transform:translateY(-1px); }
.cl-card--active { border-color:#3b82f6;background:#eff6ff;box-shadow:0 4px 20px rgba(59,130,246,.12); }

.cl-card-head { display:flex;align-items:flex-start;gap:10px;margin-bottom:6px; }
.cl-card-icon { width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1.2rem;flex-shrink:0; }
.cl-card-title { font-size:.85rem;font-weight:700;color:#1e293b;line-height:1.3; }
.cl-card-num { font-size:.7rem;color:#94a3b8;margin-top:2px;font-family:monospace; }

.cl-card-badges { display:flex;gap:4px;flex-wrap:wrap;margin-bottom:6px;padding-right:48px; }
.cl-badge {
    font-size:.66rem;font-weight:700;padding:2px 7px;border-radius:20px;border:1.5px solid;
}
.cl-card-amount { font-size:.82rem;font-weight:800;color:#1e293b;padding-right:48px; }
.cl-card-amount span { font-size:.68rem;color:#94a3b8;font-weight:400; }
.cl-card-parties { font-size:.72rem;color:#64748b;padding-right:48px;margin-top:3px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }

.cl-progress { margin:8px 0 4px;padding-right:48px; }
.cl-progress-bar { height:4px;background:#f1f5f9;border-radius:99px;overflow:hidden; }
.cl-progress-fill { height:100%;border-radius:99px;transition:width .6s ease; }
.cl-progress-pct { font-size:.65rem;color:#94a3b8;margin-top:2px;text-align:left; }

/* ── پانل جزئیات ── */
.cl-detail {
    flex:1;display:flex;flex-direction:column;
    background:#fff;overflow:hidden;
}
.cl-detail-empty {
    flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
    color:#cbd5e1;gap:12px;
}
.cl-detail-empty-icon { font-size:3.5rem;opacity:.4; }

/* هدر جزئیات */
.cl-detail-head {
    padding:18px 24px 14px;border-bottom:1px solid #f1f5f9;
    display:flex;align-items:flex-start;gap:16px;
    background:linear-gradient(135deg,var(--hbg,#f8fafc) 0%,#fff 60%);
}
.cl-detail-icon { width:52px;height:52px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:1.6rem;flex-shrink:0; }
.cl-detail-title { font-size:1rem;font-weight:800;color:#1e293b; }
.cl-detail-sub { font-size:.75rem;color:#64748b;margin-top:4px;display:flex;gap:10px;flex-wrap:wrap;align-items:center; }
.cl-detail-actions { display:flex;gap:6px;margin-top:10px;flex-wrap:wrap; }

/* خلاصه مالی */
.cl-fin {
    display:grid;grid-template-columns:repeat(3,1fr);gap:10px;
    padding:14px 24px;border-bottom:1px solid #f1f5f9;
    background:#fafafa;
}
.cl-fin-card { background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:10px 12px;text-align:center; }
.cl-fin-lbl { font-size:.7rem;color:#94a3b8;margin-bottom:4px; }
.cl-fin-val { font-size:1rem;font-weight:800; }
.cl-fin-progress { padding:0 24px 12px;border-bottom:1px solid #f1f5f9; }
.cl-fp-row { display:flex;justify-content:space-between;font-size:.72rem;color:#94a3b8;margin-bottom:4px; }
.cl-fp-bar { height:6px;background:#f1f5f9;border-radius:99px;overflow:hidden; }
.cl-fp-fill { height:100%;border-radius:99px;transition:width .8s ease; }

/* تب‌ها */
.cl-tabs { display:flex;gap:2px;padding:10px 24px 0;border-bottom:1px solid #f1f5f9;background:#fff; }
.cl-tab {
    padding:8px 16px;font-size:.8rem;font-weight:600;cursor:pointer;
    border:none;background:none;color:#64748b;border-bottom:2.5px solid transparent;
    font-family:inherit;transition:all .15s;
}
.cl-tab:hover { color:#1e293b; }
.cl-tab--active { color:#3b82f6;border-bottom-color:#3b82f6; }

/* محتوای تب */
.cl-tab-body { flex:1;overflow-y:auto;padding:16px 24px; }
.cl-tab-body::-webkit-scrollbar { width:4px; }
.cl-tab-body::-webkit-scrollbar-thumb { background:#e2e8f0;border-radius:99px; }

/* جزئیات تب */
.cl-info-grid { display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px; }
.cl-info-box { background:#f8fafc;border-radius:10px;padding:8px 12px; }
.cl-info-box-lbl { font-size:.7rem;color:#94a3b8;margin-bottom:2px; }
.cl-info-box-val { font-size:.88rem;color:#1e293b;font-weight:600; }

/* کارت طرف قرارداد */
.cl-party-card {
    background:#f8fafc;border:1.5px solid #e2e8f0;border-radius:12px;
    padding:12px 14px;margin-bottom:8px;display:flex;align-items:flex-start;gap:12px;
}
.cl-party-avatar { width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0; }
.cl-party-info { flex:1;min-width:0; }
.cl-party-name { font-size:.88rem;font-weight:700;color:#1e293b; }
.cl-party-meta { font-size:.72rem;color:#64748b;margin-top:3px;display:flex;gap:10px;flex-wrap:wrap; }

/* ردیف قسط */
.cl-payment-row {
    display:flex;align-items:center;gap:10px;padding:10px 12px;
    border:1.5px solid #e2e8f0;border-radius:10px;margin-bottom:6px;
    background:#fff;transition:border-color .15s;
}
.cl-payment-row--overdue { border-color:#fecaca;background:#fef2f2; }
.cl-payment-row--paid { background:#f0fdf4;border-color:#a7f3d0;opacity:.8; }
.cl-pay-no { width:22px;height:22px;border-radius:6px;background:#f1f5f9;font-size:.7rem;font-weight:800;color:#64748b;display:flex;align-items:center;justify-content:center;flex-shrink:0; }
.cl-pay-body { flex:1;min-width:0; }
.cl-pay-amount { font-size:.88rem;font-weight:700;color:#1e293b; }
.cl-pay-meta { font-size:.7rem;color:#64748b;margin-top:2px; }
.cl-pay-status { font-size:.68rem;font-weight:700;padding:2px 8px;border-radius:20px;border:1.5px solid;white-space:nowrap;flex-shrink:0; }
.cl-pay-actions { display:flex;gap:4px;flex-shrink:0; }

/* متن قرارداد */
.cl-contract-html { padding:4px 0;max-width:100%; }

/* مودال */
.cl-overlay { position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2000;display:flex;align-items:center;justify-content:center;padding:16px; }
.cl-modal { background:#fff;border-radius:20px;width:100%;max-width:560px;max-height:92vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,.2);animation:modalIn .22s ease; }
@keyframes modalIn { from{opacity:0;transform:scale(.95)}to{opacity:1;transform:none} }
.cl-modal-head { padding:18px 20px 14px;border-bottom:1px solid #f1f5f9;display:flex;align-items:center;gap:10px; }
.cl-modal-title { font-size:.95rem;font-weight:800;color:#1e293b;flex:1; }
.cl-modal-body { padding:18px 20px; }
.cl-modal-actions { display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #f1f5f9; }
.cl-form-row { display:grid;grid-template-columns:1fr 1fr;gap:10px; }
.cl-fg { display:flex;flex-direction:column;gap:4px;margin-bottom:10px; }
.cl-fg label { font-size:.74rem;color:#475569;font-weight:600; }
.cl-fg input,.cl-fg select,.cl-fg textarea { padding:8px 10px;border:1.5px solid #e2e8f0;border-radius:9px;font-size:.83rem;font-family:inherit;transition:border-color .2s;width:100%;box-sizing:border-box; }
.cl-fg input:focus,.cl-fg select:focus,.cl-fg textarea:focus { outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1); }
.cl-section-divider { font-size:.72rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.06em;padding:6px 0 4px;border-bottom:1px solid #f1f5f9;margin-bottom:10px;grid-column:1/-1; }

/* toast */
.cl-toast { position:fixed;bottom:28px;left:50%;transform:translateX(-50%);background:#1e293b;color:#fff;padding:12px 24px;border-radius:12px;font-size:.84rem;font-weight:600;z-index:9999;box-shadow:0 8px 30px rgba(0,0,0,.2);direction:rtl;white-space:nowrap;animation:toastIn .2s ease; }
@keyframes toastIn { from{opacity:0;transform:translateX(-50%) translateY(12px)}to{opacity:1;transform:translateX(-50%) translateY(0)} }

/* پانل قرارداد مشارکت ویزارد */
.cl-partner-modal { max-width:700px; }
.cl-partner-grid { display:grid;grid-template-columns:1fr 1fr;gap:10px; }
`;

export default class ContractListSection extends SectionBase {

    constructor(props = {}) {
        super(props);
        this._contracts = [];
        this._selected  = null;
        this._activeTab = 'details';
        this._parcels   = [];
        this._projects  = [];
        this._injectCSS();
    }

    _injectCSS() {
        if (document.getElementById('cl-styles')) return;
        const s = document.createElement('style');
        s.id = 'cl-styles'; s.textContent = CSS;
        document.head.appendChild(s);
    }

    async template() { return `<div data-ref="root" style="height:100%;overflow:hidden;"></div>`; }

    async afterRender() {
        await this._loadLookups();
        await this._render();
    }

    async _loadLookups() {
        try {
            const [pr, pj] = await Promise.all([
                this.api.get('/gis/districts').catch(() => null),
                this.api.get('/pc/projects', { per_page: 200 }).catch(() => null),
            ]);
            this._parcels  = pr?.data ?? [];
            this._projects = pj?.data ?? [];
        } catch {}
    }

    async _render() {
        const root = this.ref('root');
        root.innerHTML = `
        <div class="cl-wrap">
            <!-- sidebar -->
            <div class="cl-sidebar">
                <div class="cl-toolbar">
                    <div class="cl-toolbar-top">
                        <input type="text" class="cl-search" data-ref="searchInp" placeholder="🔍 جستجو در قراردادها…">
                        <button class="cl-btn cl-btn--primary cl-btn--sm" data-ref="addBtn">➕ جدید</button>
                        <button class="cl-btn cl-btn--orange cl-btn--sm" data-ref="tplBtn" title="قالب مشارکت در ساخت">📄</button>
                    </div>
                    <div class="cl-filter-row">
                        <select class="cl-sel" data-ref="typeSel">
                            <option value="">همه انواع</option>
                            ${Object.entries(TYPE_META).map(([k,v]) => `<option value="${k}">${v.icon} ${v.label}</option>`).join('')}
                        </select>
                        <select class="cl-sel" data-ref="statusSel">
                            <option value="">همه وضعیت‌ها</option>
                            ${Object.entries(STATUS_META).map(([k,v]) => `<option value="${k}">${v.label}</option>`).join('')}
                        </select>
                    </div>
                </div>
                <div class="cl-stats" data-ref="statsBar">
                    <div class="cl-stat"><div class="cl-stat-n" style="color:#3b82f6">—</div><div class="cl-stat-l">کل</div></div>
                    <div class="cl-stat"><div class="cl-stat-n" style="color:#10b981">—</div><div class="cl-stat-l">فعال</div></div>
                    <div class="cl-stat"><div class="cl-stat-n" style="color:#f59e0b">—</div><div class="cl-stat-l">پیش‌نویس</div></div>
                    <div class="cl-stat"><div class="cl-stat-n" style="color:#3b82f6">—</div><div class="cl-stat-l">تکمیل</div></div>
                </div>
                <div class="cl-list" data-ref="listBody"></div>
            </div>

            <!-- پانل جزئیات -->
            <div class="cl-detail" data-ref="detailPane">
                <div class="cl-detail-empty" data-ref="detailEmpty">
                    <span class="cl-detail-empty-icon">📋</span>
                    <span style="font-size:.9rem;font-weight:600">یک قرارداد انتخاب کنید</span>
                    <span style="font-size:.78rem">برای مشاهده جزئیات روی کارت کلیک کنید</span>
                </div>
                <div style="display:none;flex:1;flex-direction:column;overflow:hidden;" data-ref="detailContent">
                    <div data-ref="detailHead"></div>
                    <div class="cl-fin" data-ref="detailFin"></div>
                    <div class="cl-fin-progress" data-ref="detailFp"></div>
                    <div class="cl-tabs" data-ref="detailTabs"></div>
                    <div class="cl-tab-body" data-ref="detailTabBody"></div>
                </div>
            </div>
        </div>`;

        root.querySelectorAll('[data-ref]').forEach(el => { this.refs[el.dataset.ref] = el; });

        this.on(this.ref('searchInp'), 'input',  () => this._loadList());
        this.on(this.ref('typeSel'),   'change', () => this._loadList());
        this.on(this.ref('statusSel'), 'change', () => this._loadList());
        this.on(this.ref('addBtn'),    'click',  () => this._openContractModal());
        this.on(this.ref('tplBtn'),    'click',  () => this._openPartnershipWizard());

        await this._loadList();
    }

    async _loadList() {
        const body = this.ref('listBody');
        body.innerHTML = `<div style="display:flex;justify-content:center;padding:40px;"><div class="acc-spinner"></div></div>`;
        try {
            const params = {};
            const s = this.ref('searchInp')?.value;
            const t = this.ref('typeSel')?.value;
            const st = this.ref('statusSel')?.value;
            if (s)  params.search = s;
            if (t)  params.type   = t;
            if (st) params.status = st;
            const res = await this.api.get('/contracts', params);
            this._contracts = res?.data ?? [];
            this._updateStats();
            this._renderList();
        } catch {
            body.innerHTML = `<div style="padding:20px;color:#ef4444;font-size:.82rem;">خطا در بارگذاری</div>`;
        }
    }

    _updateStats() {
        const bar = this.ref('statsBar');
        if (!bar) return;
        const nums = bar.querySelectorAll('.cl-stat-n');
        const c = this._contracts;
        nums[0].textContent = c.length;
        nums[1].textContent = c.filter(i => i.status === 'active').length;
        nums[2].textContent = c.filter(i => i.status === 'draft').length;
        nums[3].textContent = c.filter(i => i.status === 'completed').length;
    }

    _renderList() {
        const body = this.ref('listBody');
        if (!this._contracts.length) {
            body.innerHTML = `<div style="text-align:center;padding:48px 16px;color:#94a3b8;">
                <div style="font-size:2.5rem;margin-bottom:10px;opacity:.4;">📋</div>
                <div style="font-size:.85rem;">قراردادی یافت نشد</div>
            </div>`;
            return;
        }
        body.innerHTML = '';
        this._contracts.forEach((c, idx) => {
            const tm  = TYPE_META[c.type]   || { label: c.type, icon: '📄', color: '#94a3b8', bg: '#f8fafc' };
            const sm  = STATUS_META[c.status] || STATUS_META.draft;
            const paid  = parseFloat(c.paid_amount  || 0);
            const total = parseFloat(c.total_amount || 0);
            const pct   = total > 0 ? Math.round(paid / total * 100) : 0;
            const isActive = this._selected?.id == c.id;

            const card = document.createElement('div');
            card.className = `cl-card${isActive ? ' cl-card--active' : ''}`;
            card.style.cssText = `--tc:${tm.color};animation-delay:${idx * 50}ms`;
            card.innerHTML = `
                <div class="cl-card-head">
                    <div class="cl-card-icon" style="background:${tm.bg}">${tm.icon}</div>
                    <div style="flex:1;min-width:0">
                        <div class="cl-card-title">${this.escape(c.title)}</div>
                        ${c.contract_number ? `<div class="cl-card-num">${this.escape(c.contract_number)}</div>` : ''}
                    </div>
                </div>
                <div class="cl-card-badges">
                    <span class="cl-badge" style="color:${tm.color};border-color:${tm.color};background:${tm.bg}">${tm.icon} ${tm.label}</span>
                    <span class="cl-badge" style="color:${sm.color};border-color:${sm.color};background:${sm.bg}">
                        <span style="display:inline-block;width:5px;height:5px;border-radius:50%;background:${sm.color};margin-left:4px;vertical-align:middle"></span>${sm.label}
                    </span>
                </div>
                ${total > 0 ? `<div class="cl-card-amount">${this._fmt(total)} <span>ریال</span></div>` : ''}
                ${c.party_names ? `<div class="cl-card-parties">👥 ${this.escape(c.party_names)}</div>` : ''}
                ${total > 0 ? `
                <div class="cl-progress">
                    <div class="cl-progress-bar"><div class="cl-progress-fill" style="width:${pct}%;background:${tm.color}"></div></div>
                    <div class="cl-progress-pct">${pct}٪ پرداخت‌شده</div>
                </div>` : ''}`;

            card.addEventListener('click', () => this._openDetail(c));
            body.appendChild(card);
        });
    }

    /* ── پانل جزئیات ── */
    async _openDetail(cSummary) {
        this._selected = cSummary;
        this._renderList();
        this.ref('detailEmpty').style.display  = 'none';
        this.ref('detailContent').style.display = 'flex';

        const head     = this.ref('detailHead');
        const fin      = this.ref('detailFin');
        const fp       = this.ref('detailFp');
        const tabs     = this.ref('detailTabs');
        const tabBody  = this.ref('detailTabBody');

        head.innerHTML = `<div style="padding:18px 24px;"><div class="acc-spinner" style="width:24px;height:24px;border-width:2px;margin:0"></div></div>`;
        fin.innerHTML = tabBody.innerHTML = tabs.innerHTML = '';

        try {
            const res = await this.api.get(`/contracts/${cSummary.id}`);
            const c   = res?.data;
            if (!c) return;
            this._selected = c;

            const tm = TYPE_META[c.type]   || { label: c.type, icon: '📄', color: '#3b82f6', bg: '#eff6ff' };
            const sm = STATUS_META[c.status] || STATUS_META.draft;
            const fs = c.financial_summary || {};
            const pct = fs.progress_pct || 0;

            /* هدر */
            head.style.cssText = `--hbg:${tm.bg}`;
            head.innerHTML = `
            <div class="cl-detail-head" style="--hbg:${tm.bg}">
                <div class="cl-detail-icon" style="background:${tm.bg}">${tm.icon}</div>
                <div style="flex:1;min-width:0">
                    <div class="cl-detail-title">${this.escape(c.title)}</div>
                    <div class="cl-detail-sub">
                        <span class="cl-badge" style="color:${tm.color};border-color:${tm.color};background:${tm.bg}">${tm.label}</span>
                        <span class="cl-badge" style="color:${sm.color};border-color:${sm.color};background:${sm.bg}">${sm.label}</span>
                        ${c.contract_number ? `<span style="font-family:monospace;font-size:.72rem;color:#94a3b8;">№ ${this.escape(c.contract_number)}</span>` : ''}
                    </div>
                    <div class="cl-detail-actions">
                        <button class="cl-btn cl-btn--sm cl-btn--primary" data-ref="editBtn">✏️ ویرایش</button>
                        <button class="cl-btn cl-btn--sm" style="background:#fef2f2;color:#ef4444;border-color:#fecaca;" data-ref="deleteBtn">🗑️ فسخ</button>
                    </div>
                </div>
            </div>`;

            head.querySelectorAll('[data-ref]').forEach(el => { this.refs[el.dataset.ref] = el; });
            this.on(this.ref('editBtn'),   'click', () => this._openContractModal(c));
            this.on(this.ref('deleteBtn'), 'click', async () => {
                if (!confirm(`فسخ قرارداد "${c.title}"؟`)) return;
                await this.api.delete(`/contracts/${c.id}`);
                this._toast('قرارداد فسخ شد', '🗑️', '#ef4444');
                this._selected = null;
                this.ref('detailEmpty').style.display   = 'flex';
                this.ref('detailContent').style.display = 'none';
                await this._loadList();
            });

            /* خلاصه مالی */
            fin.innerHTML = `
                <div class="cl-fin-card">
                    <div class="cl-fin-lbl">مبلغ کل</div>
                    <div class="cl-fin-val" style="color:#1e293b">${this._fmt(fs.total)}</div>
                    <div style="font-size:.68rem;color:#94a3b8;">ریال</div>
                </div>
                <div class="cl-fin-card">
                    <div class="cl-fin-lbl">پرداخت‌شده</div>
                    <div class="cl-fin-val" style="color:#10b981">${this._fmt(fs.paid)}</div>
                    <div style="font-size:.68rem;color:#94a3b8;">ریال</div>
                </div>
                <div class="cl-fin-card">
                    <div class="cl-fin-lbl">مانده</div>
                    <div class="cl-fin-val" style="color:#ef4444">${this._fmt(fs.remaining)}</div>
                    <div style="font-size:.68rem;color:#94a3b8;">ریال</div>
                </div>`;

            fp.innerHTML = `
                <div class="cl-fp-row"><span>پیشرفت پرداخت</span><span style="font-weight:700;color:${tm.color}">${pct}٪</span></div>
                <div class="cl-fp-bar"><div class="cl-fp-fill" style="width:${pct}%;background:${tm.color}"></div></div>`;

            /* تب‌ها */
            const hasHtml = c.description && c.description.trim().startsWith('<');
            const tabList = [
                { key: 'details',  label: `📋 جزئیات` },
                { key: 'parties',  label: `👥 طرفین (${(c.parties||[]).length})` },
                { key: 'payments', label: `💰 اقساط (${(c.payments||[]).length})` },
                ...(hasHtml ? [{ key: 'contract_text', label: '📄 متن قرارداد' }] : []),
            ];
            tabs.innerHTML = tabList.map(t =>
                `<button class="cl-tab${this._activeTab===t.key?' cl-tab--active':''}" data-tab="${t.key}">${t.label}</button>`
            ).join('');
            tabs.querySelectorAll('.cl-tab').forEach(btn => {
                btn.addEventListener('click', () => {
                    tabs.querySelectorAll('.cl-tab').forEach(b => b.classList.remove('cl-tab--active'));
                    btn.classList.add('cl-tab--active');
                    this._activeTab = btn.dataset.tab;
                    this._renderTabBody(btn.dataset.tab, c);
                });
            });

            this._renderTabBody(this._activeTab, c);

        } catch {
            head.innerHTML = `<div style="padding:20px;color:#ef4444;font-size:.82rem;">خطا در بارگذاری</div>`;
        }
    }

    _renderTabBody(tab, c) {
        const body = this.ref('detailTabBody');
        switch (tab) {
            case 'details':       body.innerHTML = this._tabDetails(c);      break;
            case 'parties':       body.innerHTML = this._tabParties(c.parties||[]); this._bindPartyActions(c); break;
            case 'payments':      body.innerHTML = this._tabPayments(c.payments||[], c); this._bindPayActions(c); break;
            case 'contract_text': body.innerHTML = `<div class="cl-contract-html">${c.description}</div>`; break;
            default:              body.innerHTML = '';
        }
    }

    /* ── تب جزئیات ── */
    _tabDetails(c) {
        const box = (l, v) => v ? `<div class="cl-info-box"><div class="cl-info-box-lbl">${l}</div><div class="cl-info-box-val">${v}</div></div>` : '';
        return `
        <div class="cl-info-grid">
            ${box('📅 تاریخ تنظیم', c.contract_date)}
            ${box('🚀 تاریخ شروع',  c.start_date)}
            ${box('🏁 تاریخ پایان', c.end_date)}
            ${box('💱 ارز', c.currency)}
            ${box('👤 ثبت‌کننده', c.created_by_name)}
        </div>
        ${c.description && !c.description.trim().startsWith('<') ? `
        <div style="background:#f8fafc;border-right:3px solid #3b82f6;border-radius:10px;padding:10px 14px;font-size:.82rem;color:#475569;line-height:1.8;">
            ${this.escape(c.description)}
        </div>` : ''}
        ${c.notes ? `<div style="margin-top:10px;background:#fffbeb;border-right:3px solid #f59e0b;border-radius:10px;padding:10px 14px;font-size:.78rem;color:#92400e;">
            📝 ${this.escape(c.notes)}
        </div>` : ''}
        ${c.parcel ? `<div style="margin-top:10px;background:#eff6ff;border-radius:10px;padding:10px 14px;font-size:.78rem;color:#1e40af;">
            📍 پلاک ${this.escape(c.parcel.code||'—')} — ${this.escape(c.parcel.address||'')}
        </div>` : ''}`;
    }

    /* ── تب طرفین ── */
    _tabParties(parties) {
        const cards = parties.map(p => {
            const rm = ROLE_META[p.role] || { label: p.role, color: '#94a3b8' };
            return `<div class="cl-party-card">
                <div class="cl-party-avatar" style="background:${rm.color}18;color:${rm.color}">
                    ${p.is_company == '1' ? '🏢' : '👤'}
                </div>
                <div class="cl-party-info">
                    <div class="cl-party-name">${this.escape(p.name)}</div>
                    ${p.is_company == '1' && p.company_name ? `<div style="font-size:.72rem;color:#64748b;">🏢 ${this.escape(p.company_name)}</div>` : ''}
                    <div class="cl-party-meta">
                        <span class="cl-badge" style="color:${rm.color};border-color:${rm.color};background:${rm.color}18">${rm.label}</span>
                        ${p.national_id ? `<span>🪪 ${this.escape(p.national_id)}</span>` : ''}
                        ${p.phone ? `<span>📞 ${this.escape(p.phone)}</span>` : ''}
                    </div>
                </div>
                <div style="display:flex;gap:4px;">
                    <button class="cl-btn cl-btn--xs" data-edit-party="${p.id}">✏️</button>
                    <button class="cl-btn cl-btn--xs" style="background:#fef2f2;color:#ef4444;border-color:#fecaca;" data-del-party="${p.id}">🗑️</button>
                </div>
            </div>`;
        }).join('') || `<div style="text-align:center;padding:24px;color:#94a3b8;font-size:.8rem;">طرفی ثبت نشده</div>`;

        return `<div>
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
                <span style="font-size:.78rem;font-weight:700;color:#64748b;">طرفین قرارداد</span>
                <button class="cl-btn cl-btn--sm" data-action="add-party">+ افزودن طرف</button>
            </div>
            ${cards}
        </div>`;
    }

    _bindPartyActions(c) {
        const body = this.ref('detailTabBody');
        body.querySelector('[data-action="add-party"]')?.addEventListener('click', () => this._openPartyModal(c.id));
        body.querySelectorAll('[data-edit-party]').forEach(btn => {
            const p = (c.parties || []).find(p => String(p.id) === btn.dataset.editParty);
            if (p) btn.addEventListener('click', () => this._openPartyModal(c.id, p));
        });
        body.querySelectorAll('[data-del-party]').forEach(btn => {
            btn.addEventListener('click', async () => {
                if (!confirm('حذف این طرف؟')) return;
                await this.api.delete(`/contracts/${c.id}/parties/${btn.dataset.delParty}`);
                this._toast('حذف شد', '🗑️'); this._openDetail(c);
            });
        });
    }

    /* ── تب اقساط ── */
    _tabPayments(payments, c) {
        const today = new Date().toISOString().split('T')[0];
        const payStatusMeta = {
            paid:      { label: '✅ پرداخت‌شد',    color: '#10b981', bg: '#f0fdf4', border: '#a7f3d0' },
            overdue:   { label: '⚠️ سررسید گذشت',  color: '#ef4444', bg: '#fef2f2', border: '#fecaca' },
            partial:   { label: '🟡 جزئی',          color: '#f59e0b', bg: '#fffbeb', border: '#fde68a' },
            pending:   { label: '⏳ در انتظار',     color: '#94a3b8', bg: '#f8fafc', border: '#e2e8f0' },
            cancelled: { label: '❌ لغو',           color: '#94a3b8', bg: '#f8fafc', border: '#e2e8f0' },
        };

        const rows = payments.map(p => {
            const isOverdue = p.status === 'overdue' || (p.status === 'pending' && p.due_date && p.due_date < today);
            const pm = payStatusMeta[isOverdue ? 'overdue' : p.status] || payStatusMeta.pending;
            return `
            <div class="cl-payment-row ${isOverdue?'cl-payment-row--overdue':''} ${p.status==='paid'?'cl-payment-row--paid':''}">
                <div class="cl-pay-no">${p.installment_no}</div>
                <div class="cl-pay-body">
                    <div class="cl-pay-amount">${this._fmt(p.amount)} ریال</div>
                    <div class="cl-pay-meta">
                        ${p.description ? this.escape(p.description) + ' · ' : ''}
                        ${p.due_date ? `📅 ${p.due_date}` : ''}
                        ${p.paid_date ? ` · ✅ ${p.paid_date}` : ''}
                    </div>
                </div>
                <span class="cl-pay-status" style="color:${pm.color};border-color:${pm.border};background:${pm.bg}">${pm.label}</span>
                <div class="cl-pay-actions">
                    ${p.status !== 'paid' && p.status !== 'cancelled' ? `<button class="cl-btn cl-btn--xs" style="background:#f0fdf4;color:#166534;border-color:#a7f3d0;" data-pay-inst="${p.id}">💰</button>` : ''}
                    <button class="cl-btn cl-btn--xs" style="color:#ef4444;border-color:#fecaca;" data-del-pay="${p.id}">🗑️</button>
                </div>
            </div>`;
        }).join('') || `<div style="text-align:center;padding:24px;color:#94a3b8;font-size:.8rem;">قسطی تعریف نشده</div>`;

        const totalPaid = payments.reduce((s, p) => s + parseFloat(p.paid_amount || 0), 0);
        return `<div>
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
                <span style="font-size:.78rem;font-weight:700;color:#64748b;">برنامه پرداخت</span>
                <button class="cl-btn cl-btn--sm" data-action="add-payment">+ افزودن قسط</button>
            </div>
            ${rows}
            ${payments.length ? `
            <div style="margin-top:12px;padding:10px 12px;background:#f8fafc;border-radius:10px;display:flex;justify-content:space-between;font-size:.78rem;">
                <span>جمع پرداخت‌شده: <b style="color:#10b981;">${this._fmt(totalPaid)} ریال</b></span>
                <span>مانده: <b style="color:#ef4444;">${this._fmt(parseFloat(c.total_amount||0) - totalPaid)} ریال</b></span>
            </div>` : ''}
        </div>`;
    }

    _bindPayActions(c) {
        const body = this.ref('detailTabBody');
        body.querySelector('[data-action="add-payment"]')?.addEventListener('click', () => this._openPaymentModal(c.id));
        body.querySelectorAll('[data-pay-inst]').forEach(btn => {
            btn.addEventListener('click', () => this._openPayNowModal(c.id, btn.dataset.payInst, c));
        });
        body.querySelectorAll('[data-del-pay]').forEach(btn => {
            btn.addEventListener('click', async () => {
                if (!confirm('حذف این قسط؟')) return;
                await this.api.delete(`/contracts/${c.id}/payments/${btn.dataset.delPay}`);
                this._toast('قسط حذف شد', '🗑️'); this._openDetail(c);
            });
        });
    }

    /* ── مودال قرارداد ── */
    _openContractModal(c = null) {
        const isEdit = !!c;
        const tm = (k) => c?.type === k ? 'selected' : '';
        const sm = (k) => c?.status === k ? 'selected' : '';

        const ov = this._modal(`📋 ${isEdit ? 'ویرایش قرارداد' : 'قرارداد جدید'}`, `
        <div class="cl-fg"><label>عنوان *</label><input name="title" required value="${this.escape(c?.title||'')}"></div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>نوع</label>
                <select name="type">
                    ${Object.entries(TYPE_META).map(([k,v]) => `<option value="${k}" ${tm(k)}>${v.icon} ${v.label}</option>`).join('')}
                </select>
            </div>
            <div class="cl-fg"><label>وضعیت</label>
                <select name="status">
                    ${Object.entries(STATUS_META).map(([k,v]) => `<option value="${k}" ${sm(k)}>${v.label}</option>`).join('')}
                </select>
            </div>
        </div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>شماره قرارداد</label><input name="contract_number" value="${this.escape(c?.contract_number||'')}"></div>
            <div class="cl-fg"><label>مبلغ کل (ریال) *</label><input name="total_amount" type="number" min="0" required value="${c?.total_amount||''}"></div>
        </div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>تاریخ تنظیم</label><input name="contract_date" type="date" value="${c?.contract_date||''}"></div>
            <div class="cl-fg"><label>تاریخ شروع</label><input name="start_date" type="date" value="${c?.start_date||''}"></div>
        </div>
        <div class="cl-fg"><label>تاریخ پایان</label><input name="end_date" type="date" value="${c?.end_date||''}"></div>
        <div class="cl-fg"><label>توضیحات</label><textarea name="description" rows="3">${this.escape(c?.description||'')}</textarea></div>
        <div class="cl-fg"><label>یادداشت</label><textarea name="notes" rows="2">${this.escape(c?.notes||'')}</textarea></div>
        `, isEdit ? 'ذخیره تغییرات' : 'ایجاد قرارداد', async (data, btn, close) => {
            ['contract_date','start_date','end_date','parcel_id','project_id'].forEach(k => { if (!data[k]) delete data[k]; });
            if (isEdit) await this.api.put(`/contracts/${c.id}`, data);
            else        await this.api.post('/contracts', data);
            close();
            this._toast(isEdit ? 'قرارداد به‌روز شد' : 'قرارداد ثبت شد', '✅');
            await this._loadList();
            if (isEdit) this._openDetail({ id: c.id });
        });
    }

    /* ── مودال طرف ── */
    _openPartyModal(contractId, party = null) {
        const isEdit = !!party;
        const p = party || {};
        const sr = (k) => p.role === k ? 'selected' : '';
        this._modal(`👥 ${isEdit?'ویرایش':'افزودن'} طرف قرارداد`, `
        <div class="cl-fg"><label>نقش *</label>
            <select name="role">
                ${Object.entries(ROLE_META).map(([k,v]) => `<option value="${k}" ${sr(k)}>${v.label}</option>`).join('')}
            </select>
        </div>
        <div class="cl-fg"><label>نام / نام خانوادگی *</label><input name="name" required value="${this.escape(p.name||'')}"></div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>کد ملی</label><input name="national_id" value="${this.escape(p.national_id||'')}"></div>
            <div class="cl-fg"><label>تلفن</label><input name="phone" type="tel" value="${this.escape(p.phone||'')}"></div>
        </div>
        <div class="cl-fg">
            <label style="display:flex;gap:8px;align-items:center;cursor:pointer;">
                <input type="checkbox" name="is_company" value="1" ${p.is_company=='1'?'checked':''}> شخص حقوقی
            </label>
        </div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>نام شرکت</label><input name="company_name" value="${this.escape(p.company_name||'')}"></div>
            <div class="cl-fg"><label>شناسه ثبت</label><input name="company_reg" value="${this.escape(p.company_reg||'')}"></div>
        </div>
        `, isEdit ? 'ذخیره' : 'افزودن', async (data, btn, close) => {
            data.is_company = data.is_company ? 1 : 0;
            if (isEdit) await this.api.put(`/contracts/${contractId}/parties/${p.id}`, data);
            else        await this.api.post(`/contracts/${contractId}/parties`, data);
            close(); this._toast('ذخیره شد', '✅'); this._openDetail({ id: contractId });
        });
    }

    /* ── مودال قسط ── */
    _openPaymentModal(contractId) {
        this._modal('💰 افزودن قسط', `
        <div class="cl-fg"><label>شرح</label><input name="description" placeholder="مثلاً: پیش‌پرداخت، قسط اول…"></div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>مبلغ (ریال) *</label><input name="amount" type="number" min="0" required></div>
            <div class="cl-fg"><label>سررسید</label><input name="due_date" type="date"></div>
        </div>
        <div class="cl-fg"><label>یادداشت</label><textarea name="notes" rows="2"></textarea></div>
        `, 'افزودن قسط', async (data, btn, close) => {
            if (!data.due_date) delete data.due_date;
            await this.api.post(`/contracts/${contractId}/payments`, data);
            close(); this._toast('قسط افزوده شد', '✅'); this._openDetail({ id: contractId });
        });
    }

    /* ── مودال ثبت پرداخت ── */
    _openPayNowModal(contractId, paymentId, c) {
        const pay = (c.payments || []).find(p => String(p.id) === String(paymentId));
        if (!pay) return;
        this._modal('💰 ثبت پرداخت', `
        <div style="background:#f0fdf4;border-radius:10px;padding:10px 14px;font-size:.83rem;margin-bottom:14px;">
            ${pay.description ? this.escape(pay.description) + ' — ' : ''}
            مبلغ قسط: <b>${this._fmt(pay.amount)} ریال</b>
        </div>
        <div class="cl-form-row">
            <div class="cl-fg"><label>مبلغ پرداخت‌شده (ریال)</label><input name="paid_amount" type="number" min="0" value="${pay.amount}"></div>
            <div class="cl-fg"><label>تاریخ پرداخت</label><input name="paid_date" type="date" value="${new Date().toISOString().split('T')[0]}"></div>
        </div>
        `, '✅ ثبت پرداخت', async (data, btn, close) => {
            await this.api.post(`/contracts/${contractId}/payments/${paymentId}/pay`, data);
            close(); this._toast('پرداخت ثبت شد', '✅', '#10b981'); this._openDetail({ id: contractId });
        }, '#10b981');
    }

    /* ── ویزارد قالب مشارکت در ساخت ── */
    _openPartnershipWizard() {
        const ov = document.createElement('div');
        ov.className = 'cl-overlay';
        ov.innerHTML = `
        <div class="cl-modal cl-partner-modal" style="max-width:700px;">
            <div class="cl-modal-head" style="background:linear-gradient(135deg,#f97316,#ea580c);border-radius:20px 20px 0 0;color:#fff;">
                <span style="font-size:1.4rem;">🏗️</span>
                <div class="cl-modal-title" style="color:#fff;">قرارداد مشارکت در ساخت — ۱۱ ماده حقوقی</div>
                <button class="cl-btn cl-btn--xs" style="background:rgba(255,255,255,.2);color:#fff;border-color:transparent;" data-close>✕</button>
            </div>
            <div class="cl-modal-body">
                <div id="cl-partner-preview" style="display:none;border:1px solid #e2e8f0;border-radius:10px;padding:12px;margin-bottom:16px;max-height:300px;overflow-y:auto;background:#fafafa;"></div>
                <form id="cl-partner-form">
                <div class="cl-partner-grid">
                    <div class="cl-section-divider">طرف اول — مالک</div>
                    <div class="cl-fg"><label>نام و نام خانوادگی *</label><input name="owner_name" required placeholder="علی رضایی"></div>
                    <div class="cl-fg"><label>کد ملی</label><input name="owner_nid" placeholder="0012345678" dir="ltr"></div>
                    <div class="cl-fg"><label>تلفن</label><input name="owner_phone" placeholder="09…" dir="ltr"></div>
                    <div class="cl-fg"><label>آدرس</label><input name="owner_address" placeholder="شیراز، خ…"></div>
                    <div class="cl-section-divider">طرف دوم — پیمانکار</div>
                    <div class="cl-fg"><label>نام و نام خانوادگی *</label><input name="contractor_name" required placeholder="محمد محمدی"></div>
                    <div class="cl-fg"><label>کد ملی</label><input name="contractor_nid" dir="ltr"></div>
                    <div class="cl-fg"><label>تلفن</label><input name="contractor_phone" dir="ltr"></div>
                    <div class="cl-fg"><label>آدرس</label><input name="contractor_address"></div>
                    <div class="cl-section-divider">مشخصات ملک</div>
                    <div class="cl-fg"><label>توضیح ملک / پلاک</label><input name="parcel_description" placeholder="پلاک ثبتی ۱۲۳/۴"></div>
                    <div class="cl-fg"><label>کد نوسازی</label><input name="renovation_code"></div>
                    <div class="cl-fg"><label>مساحت (م²)</label><input name="area_sqm" type="number" placeholder="200"></div>
                    <div class="cl-fg"><label>طبقات مجاز</label><input name="floors" type="number" placeholder="4"></div>
                    <div class="cl-fg"><label>شهر</label><input name="city" value="شیراز"></div>
                    <div></div>
                    <div class="cl-section-divider">شرایط مالی</div>
                    <div class="cl-fg"><label>سهم مالک (%)</label><input name="owner_percent" type="number" value="50" min="0" max="100"></div>
                    <div class="cl-fg"><label>سهم پیمانکار (%)</label><input name="contractor_percent" type="number" value="50" min="0" max="100"></div>
                    <div class="cl-fg"><label>مدت اجرا (ماه)</label><input name="duration_months" type="number" value="24" min="1"></div>
                    <div class="cl-fg"><label>پیش‌پرداخت (تومان)</label><input name="advance_amount" type="number" placeholder="500000000"></div>
                    <div class="cl-fg"><label>جریمه روزانه (تومان)</label><input name="penalty_daily" type="number" placeholder="1000000"></div>
                    <div class="cl-fg"><label>مبلغ کل (ریال)</label><input name="total_amount" type="number" placeholder="0"></div>
                </div>
                </form>
            </div>
            <div class="cl-modal-actions">
                <button class="cl-btn" data-close>انصراف</button>
                <button class="cl-btn" id="cl-preview-btn" style="background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe;">👁️ پیش‌نمایش</button>
                <button class="cl-btn cl-btn--orange" id="cl-save-tpl-btn">✅ ثبت قرارداد</button>
            </div>
        </div>`;
        document.body.appendChild(ov);
        const close = () => ov.remove();
        ov.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', close));
        ov.addEventListener('click', e => { if (e.target === ov) close(); });

        const getParams = () => Object.fromEntries(new FormData(ov.querySelector('#cl-partner-form')).entries());

        ov.querySelector('#cl-preview-btn').addEventListener('click', async () => {
            const prev = ov.querySelector('#cl-partner-preview');
            prev.innerHTML = '<div style="text-align:center;padding:20px;"><div class="acc-spinner"></div></div>';
            prev.style.display = 'block';
            try {
                const res = await this.api.get('/contracts/template/partnership-construction', getParams());
                prev.innerHTML = res?.data?.html ?? '<p>خطا</p>';
            } catch { prev.innerHTML = '<p style="color:red">خطا در ارتباط با سرور</p>'; }
        });

        ov.querySelector('#cl-save-tpl-btn').addEventListener('click', async () => {
            const btn = ov.querySelector('#cl-save-tpl-btn');
            btn.disabled = true; btn.textContent = '⏳ در حال ثبت…';
            try {
                const res = await this.api.post('/contracts/from-template', getParams());
                if (res?.contract_id) {
                    close();
                    this._toast('قرارداد مشارکت ثبت شد', '✅');
                    await this._loadList();
                    this._openDetail({ id: res.contract_id });
                }
            } catch (err) { alert('خطا: '+(err?.message||'')); btn.disabled = false; btn.textContent = '✅ ثبت قرارداد'; }
        });
    }

    /* ── helper مودال عمومی ── */
    _modal(title, bodyHtml, submitLabel, onSubmit, submitColor = '#3b82f6') {
        const ov = document.createElement('div');
        ov.className = 'cl-overlay';
        ov.innerHTML = `
        <div class="cl-modal">
            <div class="cl-modal-head">
                <div class="cl-modal-title">${title}</div>
                <button class="cl-btn cl-btn--xs" data-close>✕</button>
            </div>
            <div class="cl-modal-body"><form>${bodyHtml}</form></div>
            <div class="cl-modal-actions">
                <button class="cl-btn" data-close>انصراف</button>
                <button class="cl-btn" style="background:${submitColor};color:#fff;border-color:${submitColor};" data-submit>${submitLabel}</button>
            </div>
        </div>`;
        document.body.appendChild(ov);
        const close = () => ov.remove();
        ov.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', close));
        ov.addEventListener('click', e => { if (e.target === ov) close(); });
        ov.querySelector('[data-submit]').addEventListener('click', async () => {
            const btn = ov.querySelector('[data-submit]');
            const form = ov.querySelector('form');
            if (!form.reportValidity()) return;
            btn.disabled = true; btn.textContent = '⏳…';
            try { await onSubmit(Object.fromEntries(new FormData(form).entries()), btn, close); }
            catch (err) { alert('خطا: '+(err?.message||'')); btn.disabled = false; btn.textContent = submitLabel; }
        });
        return ov;
    }

    /* ── helpers ── */
    _fmt(v) { return new Intl.NumberFormat('fa-IR').format(Math.round(Number(v ?? 0))); }

    _toast(msg, icon = '✅', bg = '#1e293b') {
        const t = document.createElement('div');
        t.className = 'cl-toast';
        t.style.background = bg;
        t.innerHTML = `${icon} ${msg}`;
        document.body.appendChild(t);
        setTimeout(() => { t.style.opacity = '0'; t.style.transition = 'opacity .3s'; setTimeout(() => t.remove(), 300); }, 2800);
    }
}
