/* ============================================================
   MAIL MODÜLÜ — istemci uygulaması
   ------------------------------------------------------------
   Bölümler:
     1. Yardımcılar (istek, kaçış, avatar rengi, bildirim)
     2. Durum (state) ve kapsam seçimi
     3. Mesaj listesi
     4. Okuma bölmesi
     5. Toplu işlemler + menüler
     6. Yazma penceresi (alıcı etiketleri, Quill, ekler)
     7. Hesap yönetimi kipi
     8. Klavye kısayolları ve otomatik senkron
   ============================================================ */
(function () {
    'use strict';

    const CFG = window.MailConfig;
    if (!CFG) return;

    const app = document.getElementById('mailApp');
    if (!app) return;

    const hasAccounts = app.dataset.hasAccounts === '1';

    /* ── 1. Yardımcılar ─────────────────────────────────────── */

    const $ = (sel, root) => (root || document).querySelector(sel);
    const $$ = (sel, root) => Array.from((root || document).querySelectorAll(sel));

    const esc = (value) => String(value ?? '')
        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;').replace(/'/g, '&#39;');

    const toast = (icon, title, text) => {
        if (typeof window.appToast === 'function') window.appToast(icon, title, text);
        else if (icon === 'error') console.error(title, text);
    };

    const confirmAsk = async (title, text, confirmText) => {
        if (typeof window.appConfirm === 'function') {
            return window.appConfirm({
                title, text,
                confirmButtonText: confirmText || 'Evet',
                cancelButtonText: 'Vazgeç',
            });
        }
        return window.confirm(title + '\n\n' + (text || ''));
    };

    /**
     * Metin soran modal — panelin kendi bileşenini kullanır, tarayıcının
     * prompt() kutusu değil. İptal edilirse null döner.
     */
    const promptAsk = async (options) => {
        if (typeof window.appAlert !== 'function') {
            const value = window.prompt(options.title);
            return value === null ? null : value.trim();
        }

        const result = await window.appAlert({
            icon: options.icon || 'question',
            title: options.title,
            text: options.text || '',
            input: 'text',
            inputPlaceholder: options.placeholder || '',
            inputValue: options.value || '',
            showCancelButton: true,
            confirmButtonText: options.confirmText || 'Oluştur',
            cancelButtonText: 'Vazgeç',
            preConfirm: (value) => {
                const trimmed = String(value || '').trim();
                if (!trimmed) return false;          // boşsa modal kapanmaz
                return trimmed;
            },
        });

        return result && result.isConfirmed ? String(result.value).trim() : null;
    };

    /** JSON istekleri — CSRF başlığı ve hata normalizasyonu ile. */
    async function api(url, options = {}) {
        const opts = Object.assign({ method: 'GET', headers: {} }, options);
        opts.headers = Object.assign({
            'X-CSRF-TOKEN': CFG.csrf,
            'X-Requested-With': 'XMLHttpRequest',
            'Accept': 'application/json',
        }, opts.headers);

        if (opts.body && !(opts.body instanceof FormData) && typeof opts.body === 'object') {
            opts.headers['Content-Type'] = 'application/json';
            opts.body = JSON.stringify(opts.body);
        }

        const response = await fetch(url, opts);
        let data = null;
        try { data = await response.json(); } catch (e) { /* gövdesiz yanıt */ }

        if (!response.ok) {
            const message = (data && (data.message || data.error))
                || 'İşlem tamamlanamadı (' + response.status + ')';
            throw new Error(message);
        }
        return data;
    }

    /** Adresten kararlı bir avatar rengi üretir. */
    const AVATAR_COLORS = [
        '#2563eb', '#7c3aed', '#db2777', '#dc2626', '#ea580c',
        '#ca8a04', '#16a34a', '#0891b2', '#4f46e5', '#be123c',
    ];

    function avatarColor(seed) {
        const text = String(seed || '?');
        let hash = 0;
        for (let i = 0; i < text.length; i++) hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
        return AVATAR_COLORS[hash % AVATAR_COLORS.length];
    }

    function debounce(fn, wait) {
        let timer = null;
        return function () {
            const args = arguments;
            clearTimeout(timer);
            timer = setTimeout(() => fn.apply(null, args), wait);
        };
    }

    const humanSize = (bytes) => {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / 1048576).toFixed(1) + ' MB';
    };

    /* ── 2. Durum ───────────────────────────────────────────── */

    const state = {
        scope: { kind: 'unified', unified: 'inbox', folderId: null, label: 'Gelen Kutusu', folderType: 'inbox', accountId: null },
        filter: '',
        search: '',
        page: 1,
        total: 0,
        hasMore: false,
        items: [],
        selected: new Set(),
        activeId: null,
        active: null,
        loading: false,
        syncing: false,
        showImages: false,
        // Koyu temada ileti gövdesi açık "kağıt" yüzeyde gösterilir; gönderenin
        // sabit koyu renkleri aksi hâlde zemine karışıp okunmaz oluyor.
        paper: (function () {
            try { return localStorage.getItem('mailPaper') !== '0'; } catch (e) { return true; }
        })(),
    };

    const isDarkTheme = () => document.documentElement.getAttribute('data-theme') === 'dark';

    const el = {
        listScroll: $('#mailListScroll'),
        listTitle: $('#mailListTitle'),
        listMeta: $('#mailListMeta'),
        list: $('#mailList'),
        read: $('#mailRead'),
        search: $('#mailSearch'),
        searchWrap: $('#mailSearchWrap'),
        syncNote: $('#mailSyncNote'),
        menu: $('#mailMenu'),
    };

    /** Hesap kimliğinden hesap nesnesi. */
    const accountById = (id) => (CFG.accounts || []).find((a) => Number(a.id) === Number(id));

    /* ── 2b. Yalıtılmış gövde çerçevesi ─────────────────────────
       E-posta gövdesi güvenilmez içeriktir. Sunucudaki temizleyici script,
       olay öznitelikleri ve tehlikeli şemaları söküyor; buna EK OLARAK gövde
       yalıtılmış bir iframe içinde gösterilir.

       Çerçeveye allow-same-origin VERİLMEZ: içindeki hiçbir şey panelin
       DOM'una, çerezlerine ya da yerel depolamasına erişemez, üst pencereyi
       başka adrese götüremez. allow-scripts yalnızca yüksekliği bildiren
       küçük betik için açıktır ve ayrıksı (opaque) bir kaynakta çalışır —
       temizleyiciden kaçan bir betik bile oturuma ulaşamaz. */

    const FRAME_CSS = ''
        + 'html,body{margin:0;padding:0;background:transparent}'
        + 'body{font:13.5px/1.62 -apple-system,"Segoe UI",Inter,Roboto,sans-serif;color:#0f172a;'
        + 'padding:2px 2px 8px;word-break:break-word;overflow-x:auto}'
        + 'img{max-width:100%;height:auto}'
        + 'table{max-width:100%}'
        + 'a{color:#2563eb}'
        + '.mail-plain{white-space:pre-wrap;font-family:inherit}'
        + '.mail-blocked-img{min-width:18px;min-height:18px;background:#f1f5f9;'
        + 'border:1px dashed #cbd5e1;border-radius:4px}'
        + '.mail-frame-empty{color:#94a3b8}';

    const FRAME_CSS_DARK = ''
        + 'body{color:#e2e8f0}'
        + 'a{color:#93c5fd}'
        + '.mail-blocked-img{background:#1e293b;border-color:#475569}';

    /** Çerçeve içinden yüksekliği bildiren betik. */
    const FRAME_JS = '(function(){var last=0;function r(){'
        + 'var h=Math.max(document.documentElement.scrollHeight,document.body?document.body.scrollHeight:0);'
        + 'if(h!==last){last=h;parent.postMessage({source:"mail-frame",height:h},"*");}}'
        + 'r();window.addEventListener("load",r);'
        + 'if(window.ResizeObserver){new ResizeObserver(r).observe(document.documentElement);}'
        + 'setTimeout(r,300);setTimeout(r,1200);})();';

    function frameDocument(html, dark) {
        return '<!doctype html><html lang="tr"><head><meta charset="utf-8">'
            + '<meta name="referrer" content="no-referrer">'
            + '<base target="_blank">'
            + '<style>' + FRAME_CSS + (dark ? FRAME_CSS_DARK : '') + '</style></head><body>'
            + (html || '<p class="mail-frame-empty">(boş ileti)</p>')
            + '<script>' + FRAME_JS + '<' + '/script></body></html>';
    }

    /** Gövdeyi verilen kabın içine yalıtılmış çerçeve olarak yerleştirir. */
    function mountBody(host, html, dark) {
        if (!host) return null;

        const frame = document.createElement('iframe');
        frame.className = 'mail-body-frame';
        frame.setAttribute('sandbox', 'allow-scripts allow-popups allow-popups-to-escape-sandbox');
        frame.setAttribute('referrerpolicy', 'no-referrer');
        frame.setAttribute('title', 'İleti içeriği');

        host.textContent = '';
        host.appendChild(frame);

        // srcdoc DOM'a eklendikten sonra atanır: bazı tarayıcılar bağlanmamış
        // çerçevede belgeyi ayrıştırmaz ve yükseklik hiç bildirilmez.
        frame.srcdoc = frameDocument(html, dark);

        return frame;
    }

    // Çerçeveden gelen yükseklik bildirimi. Gönderen doğrulanır: sayfadaki
    // başka bir pencere kendini gövde çerçevesi gibi gösteremesin.
    window.addEventListener('message', (event) => {
        const data = event.data;
        if (!data || data.source !== 'mail-frame' || typeof data.height !== 'number') return;

        $$('iframe.mail-body-frame').forEach((frame) => {
            if (frame.contentWindow === event.source) {
                frame.style.height = Math.max(60, Math.min(24000, data.height)) + 'px';
            }
        });
    });

    /* ── 3. Mesaj listesi ───────────────────────────────────── */

    function scopeQuery() {
        const params = new URLSearchParams();
        if (state.scope.kind === 'folder') params.set('folder_id', state.scope.folderId);
        else params.set('unified', state.scope.unified);
        if (state.filter) params.set('filter', state.filter);
        if (state.search) params.set('q', state.search);
        params.set('page', state.page);
        return params.toString();
    }

    function skeleton(count) {
        let html = '<div class="mail-skeleton">';
        for (let i = 0; i < count; i++) {
            html += '<div class="mail-skel-row"><div class="mail-skel-dot"></div><div>'
                + '<div class="mail-skel-bar" style="width:38%;margin-bottom:8px"></div>'
                + '<div class="mail-skel-bar" style="width:72%;margin-bottom:8px"></div>'
                + '<div class="mail-skel-bar" style="width:90%"></div>'
                + '</div></div>';
        }
        return html + '</div>';
    }

    async function loadList(append) {
        if (!hasAccounts || state.loading) return;
        state.loading = true;

        if (!append) {
            state.page = 1;
            el.listScroll.innerHTML = skeleton(7);
        }

        try {
            const data = await api(CFG.routes.messages + '?' + scopeQuery());
            state.items = append ? state.items.concat(data.messages) : data.messages;
            state.total = data.total;
            state.hasMore = data.has_more;
            renderList();
        } catch (error) {
            el.listScroll.innerHTML = '<div class="mail-empty">'
                + '<div class="mail-empty-ic" style="color:#dc2626;background:rgba(239,68,68,.1)">'
                + '<i class="fas fa-triangle-exclamation"></i></div>'
                + '<h4>Liste yüklenemedi</h4><p>' + esc(error.message) + '</p></div>';
        } finally {
            state.loading = false;
        }
    }

    function renderList() {
        el.listTitle.textContent = state.scope.label;
        el.listMeta.textContent = state.total
            ? (state.selected.size ? state.selected.size + ' seçili' : state.total + ' ileti')
            : '';

        if (!state.items.length) {
            el.listScroll.innerHTML = '<div class="mail-empty">'
                + '<div class="mail-empty-ic"><i class="fas fa-inbox"></i></div>'
                + '<h4>' + (state.search ? 'Sonuç bulunamadı' : 'Bu klasör boş') + '</h4>'
                + '<p>' + (state.search
                    ? '“' + esc(state.search) + '” için eşleşen ileti yok.'
                    : 'Yeni postalar geldiğinde burada listelenir.') + '</p></div>';
            syncToolbar();
            return;
        }

        let html = '';
        let group = null;

        state.items.forEach((item) => {
            if (item.group_label !== group) {
                group = item.group_label;
                html += '<div class="mail-date-sep">' + esc(group) + '</div>';
            }
            html += renderRow(item);
        });

        if (state.hasMore) {
            html += '<div style="padding:14px;text-align:center">'
                + '<button type="button" class="mail-btn" id="mailLoadMore" '
                + 'style="border:1px solid var(--border-1,#e2e8f0)">'
                + '<i class="fas fa-arrow-down"></i><span>Daha fazla göster</span></button></div>';
        }

        el.listScroll.innerHTML = html;
        syncToolbar();
    }

    function renderRow(item) {
        const classes = ['mail-item'];
        if (!item.is_seen) classes.push('is-unread');
        if (item.id === state.activeId) classes.push('is-active');
        if (state.selected.has(item.id)) classes.push('is-checked');

        const showAccount = state.scope.kind === 'unified' && (CFG.accounts || []).length > 1;
        const tags = [];

        // Panelde bir kayda bağlı ileti — tanınan göndericiler de bunu taşır.
        // Sayaç sunucuda tek sorguyla geliyor, satır başına çözümleme yok.
        if (item.is_linked) tags.push('<i class="fas fa-diagram-project is-link" title="Panelde bir kayda bağlı"></i>');
        if (item.has_attachments) tags.push('<i class="fas fa-paperclip" title="Ek var"></i>');
        if (item.is_flagged) tags.push('<i class="fas fa-star is-flag" title="Yıldızlı"></i>');
        if (item.is_answered) tags.push('<i class="fas fa-reply" title="Yanıtlandı"></i>');
        if (item.priority === 1) tags.push('<i class="fas fa-circle-exclamation is-high" title="Yüksek öncelik"></i>');
        if (item.is_draft) tags.push('<span style="color:#dc2626;font-weight:700">Taslak</span>');
        if (showAccount) {
            tags.push('<span class="mail-acct-tag"><span class="mail-acct-dot" style="background:'
                + esc(item.account_color) + '"></span>' + esc(item.account_name || '') + '</span>');
        }

        return ''
            + '<article class="' + classes.join(' ') + '" data-id="' + item.id + '">'
            + '<div class="mail-check" data-check="' + item.id + '"><i class="fas fa-check"></i></div>'
            + '<div class="mail-avatar" style="background:' + avatarColor(item.party_email || item.party_name) + '">'
            + esc(item.initial) + '</div>'
            + '<div class="mail-item-body">'
            + '<div class="mail-item-top">'
            + '<span class="mail-from" title="' + esc(item.party_email) + '">' + esc(item.party_name) + '</span>'
            + '<span class="mail-time">' + esc(item.time_label) + '</span>'
            + '</div>'
            + '<div class="mail-subject">' + esc(item.subject) + '</div>'
            + '<div class="mail-snippet">' + esc(item.snippet) + '</div>'
            + (tags.length ? '<div class="mail-item-tags">' + tags.join('') + '</div>' : '')
            + '</div>'
            + '<div class="mail-item-actions">'
            + '<button type="button" class="act-flag' + (item.is_flagged ? ' is-on' : '') + '" data-row-act="flag" title="Yıldızla">'
            + '<i class="fa' + (item.is_flagged ? 's' : 'r') + ' fa-star"></i></button>'
            + '<button type="button" data-row-act="read" title="' + (item.is_seen ? 'Okunmadı yap' : 'Okundu yap') + '">'
            + '<i class="fas fa-envelope' + (item.is_seen ? '' : '-open') + '"></i></button>'
            + '<button type="button" class="act-trash" data-row-act="trash" title="Sil">'
            + '<i class="fas fa-trash-can"></i></button>'
            + '</div>'
            + '</article>';
    }

    /** Tek satırı yerinde günceller (tüm listeyi yeniden çizmeden). */
    function patchRow(id, changes) {
        const item = state.items.find((m) => m.id === id);
        if (!item) return;
        Object.assign(item, changes);

        const node = el.listScroll.querySelector('.mail-item[data-id="' + id + '"]');
        if (!node) return;

        const replacement = document.createElement('div');
        replacement.innerHTML = renderRow(item);
        node.replaceWith(replacement.firstElementChild);
    }

    function removeRows(ids) {
        ids.forEach((id) => {
            const node = el.listScroll.querySelector('.mail-item[data-id="' + id + '"]');
            if (node) node.remove();
            state.selected.delete(id);
        });
        state.items = state.items.filter((m) => ids.indexOf(m.id) === -1);
        state.total = Math.max(0, state.total - ids.length);

        if (ids.indexOf(state.activeId) !== -1) {
            state.activeId = null;
            state.active = null;
            renderEmptyReader();
        }
        if (!state.items.length) renderList();
        syncToolbar();
    }

    /* ── Kapsam (klasör) seçimi ─────────────────────────────── */

    function selectScope(button) {
        $$('.mail-folder').forEach((b) => b.classList.remove('is-active'));
        button.classList.add('is-active');

        const kind = button.dataset.scope;
        state.scope = {
            kind: kind,
            unified: button.dataset.unified || null,
            folderId: button.dataset.folderId ? Number(button.dataset.folderId) : null,
            folderType: button.dataset.folderType || button.dataset.unified || null,
            accountId: button.dataset.accountId ? Number(button.dataset.accountId) : null,
            label: button.dataset.label || 'Posta',
        };

        // Sık kullanılanlar sabit filtre taşır
        const forcedFilter = button.dataset.filter || '';
        state.filter = forcedFilter;
        $$('.mail-chip').forEach((c) => c.classList.toggle('is-on', (c.dataset.filter || '') === state.filter));

        state.selected.clear();
        state.activeId = null;
        state.active = null;
        el.list.classList.remove('is-selecting');
        renderEmptyReader();
        loadList(false);
    }

    /* ── 4. Okuma bölmesi ───────────────────────────────────── */

    function renderEmptyReader() {
        el.read.innerHTML = '<div class="mail-empty">'
            + '<div class="mail-empty-ic"><i class="fas fa-envelope-open"></i></div>'
            + '<h4>Okumak için bir posta seçin</h4>'
            + '<p>Soldaki listeden bir ileti seçtiğinizde içeriği burada görüntülenir.</p></div>';
        app.classList.remove('is-reading');
    }

    async function openMessage(id, opts) {
        // Yazma penceresi okuma bölmesindeyken seçilen ileti onun altında
        // kalırdı. Taslağı kapatmak yerine pencereyi yüzen hâle alıyoruz:
        // kullanıcı yazdıklarını kaybetmeden iletiyi okuyabilsin.
        if (compose.mode === 'dock' && compose.node.classList.contains('is-open')) {
            setComposeMode('float');
        }

        state.activeId = id;
        state.showImages = (opts && opts.showImages) || false;
        app.classList.add('is-reading');

        $$('.mail-item').forEach((n) => n.classList.toggle('is-active', Number(n.dataset.id) === id));

        el.read.innerHTML = '<div class="mail-empty"><div class="mail-empty-ic">'
            + '<i class="fas fa-spinner fa-spin"></i></div><h4>Yükleniyor…</h4>'
            + '<p>İleti sunucudan alınıyor.</p></div>';

        try {
            const params = state.showImages ? '?show_images=1' : '';
            const data = await api(CFG.routes.show + '/' + id + params);
            state.active = data;
            renderReader(data);

            if (!data.is_seen) patchRow(id, { is_seen: true });
            else patchRow(id, {});
            refreshCounts();
        } catch (error) {
            el.read.innerHTML = '<div class="mail-empty">'
                + '<div class="mail-empty-ic" style="color:#dc2626;background:rgba(239,68,68,.1)">'
                + '<i class="fas fa-triangle-exclamation"></i></div>'
                + '<h4>İleti açılamadı</h4><p>' + esc(error.message) + '</p></div>';
        }
        syncToolbar();
    }

    function addressLine(list, limit) {
        const shown = list.slice(0, limit || 3)
            .map((a) => esc(a.name && a.name !== a.email ? a.name : a.email)).join(', ');
        const rest = list.length - (limit || 3);
        return shown + (rest > 0 ? ' <b>+' + rest + '</b>' : '');
    }

    /* ── CRM köprüsü — ilişki şeridi ────────────────────────────
       Okuma başlığının altındaki şerit iletinin panel içindeki YERİNİ
       gösterir: hangi müşteri, satış, operasyon ya da tedarikçiyle ilgili.
       Bir ileti birden çok kayda bağlanabildiği için künye değil ÇİP listesi
       çizilir; her çip tipin kendi rengiyle işaretlidir, böylece şerit
       okunmadan da renk düzeninden anlaşılır.

       Zincirden otomatik türeyen bağlar (aynı yazışmaya gelen cevaplar)
       kesikli çerçeve + zincir ikonuyla ayrılır — renge bakmadan da elle
       kurulmuş bağdan ayırt edilir.

       İleti hiçbir kayda bağlı değilse ve karşı taraf tanınmıyorsa şerit
       "ilk kez yazan kişi" hâline geçer: müşteri / tedarikçi / kaynak kaydı
       şeridin içinde açılır, sayfa değişmez. */

    const CRM_TYPE_ICON = {
        customer: 'fa-user-tie', sale: 'fa-file-invoice-dollar',
        operation: 'fa-route', supplier: 'fa-building', resource: 'fa-bus',
    };

    /* Bağın nasıl kurulduğu — çipin görsel hâlini bu belirler. Üç durum var
       ve renk körlüğünde de ayırt edilebilsinler diye her biri kendi ikonunu
       ve çerçeve biçimini taşır. */
    const CRM_SOURCE = {
        manual: { cls: '', icon: '', hint: 'Elle ilişkilendirildi' },
        thread: { cls: ' is-auto', icon: 'fa-link', hint: 'Aynı yazışmadan otomatik bağlandı' },
        address: { cls: ' is-known', icon: 'fa-circle-check', hint: 'Gönderici adresinden tanındı' },
    };

    /** Bağlı tek kaydın çipi. */
    function crmChip(link) {
        const source = CRM_SOURCE[link.source] || (link.is_auto ? CRM_SOURCE.thread : CRM_SOURCE.manual);
        const icon = CRM_TYPE_ICON[link.type] || 'fa-link';

        // Adres eşleşmesinden gelen müşteri bağının (link_id yok) kaldırma
        // düğmesi olmaz: o bağ mail_links'te durmuyor, kartın adresinden doğuyor.
        const remove = link.link_id
            ? '<button type="button" class="mail-chip-x" data-read-act="linkRemove" data-link="' + link.link_id + '"'
              + ' title="İlişkiyi kaldır"><i class="fas fa-xmark"></i></button>'
            : '';

        return '<span class="mail-chip' + source.cls + '" style="--cc:' + esc(link.color) + '"'
            + ' title="' + esc(link.type_label + ' · ' + (link.meta || '') + ' · ' + source.hint) + '">'
            + '<i class="fas ' + icon + '"></i>'
            + '<a href="' + esc(link.url) + '" target="_blank" rel="noopener">' + esc(link.name) + '</a>'
            + (source.icon ? '<i class="fas ' + source.icon + ' mail-chip-auto"></i>' : '')
            + remove + '</span>';
    }

    /**
     * "Bu gönderici sistemde kayıtlı" rozeti.
     *
     * Şeridi kalabalıklaştırmamak için tek satır, tek renk, etkileşimsiz.
     * Bağ kurulabilen kaynaklarda (tedarikçi, kaynak) çip zaten var; rozet
     * o durumda ADRESİN kime ait olduğunu söyler — özellikle yetkili
     * eşleşmesinde çip "ABC Turizm", rozet "Yetkili · Ayşe Demir" der.
     * Bağ kurulamayan personelde ise tek görünür iz budur.
     */
    function crmSenderBadge(sender) {
        if (!sender) return '';

        return '<span class="mail-sender" style="--cc:' + esc(sender.color) + '">'
            + '<i class="fas fa-circle-check"></i>'
            + 'Kayıtlı gönderici<b>' + esc(sender.kind_label) + '</b>'
            + '<span>' + esc(sender.name) + (sender.meta ? ' · ' + esc(sender.meta) : '') + '</span>'
            + '</span>';
    }

    /** Tip sekmeleri + arama kutusu + sonuç listesi. */
    function crmPicker(types) {
        const tabs = (types || []).map((t, i) => ''
            + '<button type="button" class="mail-pick-tab' + (i === 0 ? ' on' : '') + '"'
            + ' data-read-act="linkType" data-type="' + esc(t.key) + '" style="--cc:' + esc(t.color) + '">'
            + '<i class="fas ' + esc(t.icon) + '"></i>' + esc(t.label) + '</button>').join('');

        return '<div class="mail-pick" id="mailLinkPicker" hidden>'
            + '<div class="mail-pick-tabs">' + tabs + '</div>'
            + '<div class="mail-pick-search">'
            + '<i class="fas fa-magnifying-glass"></i>'
            + '<input type="text" id="mailLinkQuery" placeholder="Ada, koda ya da tarihe göre arayın…" autocomplete="off">'
            + '</div>'
            + '<div class="mail-pick-list" id="mailLinkResults">'
            + '<div class="mail-pick-hint">Aramaya başlayın ya da son kayıtlardan seçin.</div>'
            + '</div></div>';
    }

    /** "İlk kez yazan kişi" — iletiden kayıt açma formu. */
    function crmCreateForm(crm) {
        const options = (crm.record_types || [])
            .map((t, i) => '<option value="' + esc(t.key) + '"' + (i === 0 ? ' selected' : '') + '>'
                + esc(t.label) + '</option>').join('');

        return '<form class="mail-crm-form" id="mailCrmForm" hidden>'
            + '  <div class="mail-crm-fields">'
            + '    <label class="is-type"><span>Kayıt</span><select name="type">' + options + '</select></label>'
            + '    <label><span>Ad</span><input type="text" name="first_name" value="' + esc(crm.first_name) + '" required></label>'
            + '    <label><span>Soyad</span><input type="text" name="last_name" value="' + esc(crm.last_name) + '"></label>'
            + '    <label><span>Telefon</span><input type="text" name="phone" placeholder="Opsiyonel"></label>'
            + '    <label data-for="customer supplier"><span>Firma</span>'
            + '      <input type="text" name="company_name" value="' + esc(crm.company_name || '') + '" placeholder="Opsiyonel"></label>'
            + '    <label class="is-type" data-for="customer"><span>Tip</span><select name="customer_type">'
            + '      <option value="individual">Kişi</option><option value="corporate">Şirket</option>'
            + '    </select></label>'
            + '    <label class="is-type" data-for="supplier"><span>Hizmet</span><select name="supplier_type">'
            + '      <option value="hotel">Otel</option><option value="transportation">Ulaşım</option>'
            + '      <option value="restaurant">Restoran</option><option value="insurance">Sigorta</option>'
            + '      <option value="visa">Vize</option><option value="airline">Havayolu</option>'
            + '    </select></label>'
            + '    <label class="is-type" data-for="resource"><span>Kaynak</span><select name="resource_type">'
            + '      <option value="guide">Rehber</option><option value="vehicle">Araç</option>'
            + '      <option value="captain">Kaptan</option>'
            + '    </select></label>'
            + '  </div>'
            + '  <div class="mail-crm-acts">'
            + '    <button type="submit" class="mail-btn mail-btn-primary"><i class="fas fa-check"></i><span>Kaydet</span></button>'
            + '    <button type="button" class="mail-btn" data-read-act="crmCancel"><span>Vazgeç</span></button>'
            + '  </div>'
            + '</form>';
    }

    function crmStrip(crm) {
        if (!crm) return '';

        const links = crm.links || [];
        const linked = links.length > 0;

        // Şerit her zaman çizilir: alıcısı müşteri olmayan bir ileti de bir
        // satışa ya da operasyona bağlanabilmeli. Son bağ kaldırıldığında
        // "İlişkilendir" düğmesi ekranda kalsın diye erken çıkış yok.
        let lead;
        if (linked) {
            lead = '<span class="mail-crm-ic"><i class="fas fa-diagram-project"></i></span>'
                + '<span class="mail-chips">' + links.map(crmChip).join('') + '</span>';
        } else if (crm.can_create) {
            lead = '<span class="mail-crm-ic is-new"><i class="fas fa-user-plus"></i></span>'
                + '<span class="mail-crm-txt"><b>Bu adres panelde kayıtlı değil</b>'
                + '<small>' + esc(crm.email) + '</small></span>'
                + '<button type="button" class="mail-crm-btn" data-read-act="crmOpen">'
                + '<i class="fas fa-plus"></i> Kayıt aç</button>';
        } else if (crm.sender) {
            // Tanınan ama bağlanamayan gönderici (personel): rozet tek başına
            lead = '<span class="mail-crm-ic"><i class="fas ' + esc(crm.sender.icon) + '"></i></span>'
                + '<span class="mail-crm-txt"><b>Gönderici sistemde kayıtlı</b>'
                + '<small>' + esc(crm.sender.kind_label + ' · ' + crm.sender.name) + '</small></span>';
        } else {
            lead = '<span class="mail-crm-ic"><i class="fas fa-diagram-project"></i></span>'
                + '<span class="mail-crm-txt"><b>Bu ileti henüz bir kayda bağlı değil</b>'
                + '<small>Satış, operasyon, müşteri ya da tedarikçiyle ilişkilendirebilirsiniz.</small></span>';
        }

        // Rozet çiplerin altına iner; lead zaten göndericiyi anlatıyorsa tekrar etmez
        const badge = linked ? crmSenderBadge(crm.sender) : '';

        return '<div class="mail-crm' + (linked ? ' is-linked' : '') + '" id="mailCrmStrip">'
            + lead
            + '<button type="button" class="mail-crm-btn is-ghost" data-read-act="linkOpen">'
            + '<i class="fas fa-link"></i> İlişkilendir</button>'
            + badge
            + crmPicker(crm.types)
            + (crm.can_create ? crmCreateForm(crm) : '')
            + '</div>';
    }

    /** Şeridi güncel CRM verisiyle yeniden çizer. */
    function crmRefresh(crm) {
        if (state.active) state.active.crm = crm;
        const strip = $('#mailCrmStrip');
        if (strip) strip.outerHTML = crmStrip(crm);
    }

    /* İlişkilendirme seçicisi — arama, seçim, kaldırma. */

    let linkType = 'customer';

    async function linkSearch() {
        const box = $('#mailLinkResults');
        const input = $('#mailLinkQuery');
        if (!box) return;

        box.innerHTML = '<div class="mail-pick-hint"><i class="fas fa-spinner fa-spin"></i> Aranıyor…</div>';

        try {
            const url = CFG.routes.linkSearch
                + '?type=' + encodeURIComponent(linkType)
                + '&q=' + encodeURIComponent(input ? input.value.trim() : '');

            const data = await api(url);
            const items = data.items || [];

            if (!items.length) {
                box.innerHTML = '<div class="mail-pick-hint">Eşleşen kayıt yok.</div>';
                return;
            }

            box.innerHTML = items.map((it) => ''
                + '<button type="button" class="mail-pick-i" style="--cc:' + esc(it.color) + '"'
                + ' data-read-act="linkPick" data-type="' + esc(it.type) + '" data-id="' + it.id + '">'
                + '<i class="fas ' + esc(it.icon) + '"></i>'
                + '<span><b>' + esc(it.name) + '</b><small>' + esc(it.meta) + '</small></span>'
                + '<i class="fas fa-plus mail-pick-go"></i></button>').join('');
        } catch (error) {
            box.innerHTML = '<div class="mail-pick-hint is-error">' + esc(error.message) + '</div>';
        }
    }

    const linkSearchDebounced = debounce(linkSearch, 260);

    /** Seçilen kaydı iletiye bağlar (zincirdeki geçmiş iletiler de bağlanır). */
    async function linkPick(type, id) {
        try {
            const data = await api(CFG.routes.crmStore + '/' + state.activeId + '/links', {
                method: 'POST',
                body: { type: type, id: id },
            });

            crmRefresh(data.crm);
            toast('success', data.message);
        } catch (error) {
            toast('error', 'İlişki kurulamadı', error.message);
        }
    }

    async function linkRemove(linkId) {
        try {
            const data = await api(CFG.routes.crmStore + '/' + state.activeId + '/links/' + linkId, {
                method: 'DELETE',
            });

            crmRefresh(data.crm);
            toast('success', data.message);
        } catch (error) {
            toast('error', 'İlişki kaldırılamadı', error.message);
        }
    }

    /**
     * Kayıt açma formunda seçilen tipe göre alanları gösterir/gizler.
     *
     * Alanlar tek formda durur; seçilmeyen tipe ait olanlar hem gizlenir hem
     * devre dışı bırakılır — devre dışı alanlar gönderilmediği için sunucuya
     * ilgisiz veri gitmez.
     */
    function crmFormFields(form) {
        const type = form.querySelector('select[name="type"]').value;

        form.querySelectorAll('[data-for]').forEach((label) => {
            const on = label.dataset.for.split(' ').indexOf(type) !== -1;
            label.hidden = !on;
            label.querySelectorAll('input, select').forEach((f) => { f.disabled = !on; });
        });
    }

    /** Şerit içindeki kayıt açma formunu gönderir. */
    async function submitCrmForm(form) {
        const submit = form.querySelector('button[type="submit"]');
        const original = submit.innerHTML;

        submit.disabled = true;
        submit.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>Kaydediliyor…</span>';

        try {
            const payload = {};
            new FormData(form).forEach((v, k) => { payload[k] = v; });

            const data = await api(CFG.routes.crmStore + '/' + state.activeId + '/records', {
                method: 'POST',
                body: payload,
            });

            crmRefresh(data.crm);
            toast('success', data.message);
        } catch (error) {
            toast('error', 'Kayıt açılamadı', error.message);
            submit.disabled = false;
            submit.innerHTML = original;
        }
    }

    function renderReader(data) {
        const isDraft = data.folder_type === 'drafts';

        let notices = '';
        if (data.error) {
            notices += '<div class="mail-notice is-error"><i class="fas fa-triangle-exclamation"></i>'
                + esc(data.error) + '</div>';
        }
        if (data.has_remote_images) {
            notices += '<div class="mail-notice"><i class="fas fa-image"></i>'
                + 'Gizliliğiniz için dış görseller engellendi.'
                + '<button type="button" id="mailShowImages">Görselleri göster</button></div>';
        }

        let attachments = '';
        if (data.attachments.length) {
            attachments = '<div class="mail-attachments">'
                + data.attachments.map((a) => ''
                    + '<a class="mail-attach" href="' + esc(a.download_url) + '" title="' + esc(a.filename) + '">'
                    + '<i class="fas ' + esc(a.icon) + '"></i>'
                    + '<div class="mail-attach-meta">'
                    + '<div class="mail-attach-name">' + esc(a.filename) + '</div>'
                    + '<div class="mail-attach-size">' + esc(a.size) + '</div>'
                    + '</div></a>').join('')
                + '</div>';
        }

        const cc = data.cc.length
            ? '<div class="mail-read-to"><b>Bilgi</b> ' + addressLine(data.cc, 4) + '</div>' : '';

        el.read.innerHTML = ''
            + '<div class="mail-read-head">'
            + '  <div class="mail-read-title-row">'
            + '    <h3 class="mail-read-subject">' + esc(data.subject) + '</h3>'
            + '    <div class="mail-read-tools">'
            + '      <button type="button" class="mail-icon-btn" data-read-act="reply" title="Yanıtla"><i class="fas fa-reply"></i></button>'
            + '      <button type="button" class="mail-icon-btn" data-read-act="replyAll" title="Tümünü yanıtla"><i class="fas fa-reply-all"></i></button>'
            + '      <button type="button" class="mail-icon-btn" data-read-act="forward" title="İlet"><i class="fas fa-share"></i></button>'
            + '      <button type="button" class="mail-icon-btn' + (data.is_flagged ? ' is-on' : '') + '" data-read-act="flag" title="Yıldızla"><i class="fa' + (data.is_flagged ? 's' : 'r') + ' fa-star"></i></button>'
            + (isDarkTheme()
                ? '      <button type="button" class="mail-icon-btn' + (state.paper ? '' : ' is-on') + '" data-read-act="paper" '
                  + 'title="' + (state.paper ? 'Koyu zeminde göster' : 'Kağıt görünümüne dön') + '">'
                  + '<i class="fas fa-' + (state.paper ? 'moon' : 'file-lines') + '"></i></button>'
                : '')
            + '      <button type="button" class="mail-icon-btn" data-read-act="print" title="Yazdır"><i class="fas fa-print"></i></button>'
            + '      <button type="button" class="mail-icon-btn" data-read-act="trash" title="Sil"><i class="fas fa-trash-can"></i></button>'
            + '      <button type="button" class="mail-icon-btn mail-close-read d-lg-none" data-read-act="close" title="Kapat"><i class="fas fa-xmark"></i></button>'
            + '    </div>'
            + '  </div>'
            + '  <div class="mail-read-sender">'
            + '    <div class="mail-avatar" style="background:' + avatarColor(data.from.email || data.from.name) + '">'
            + esc(data.initial) + '</div>'
            + '    <div class="mail-read-who">'
            + '      <div class="mail-read-name">' + esc(data.from.name || data.from.email)
            + '        <span class="mail-read-email">' + esc(data.from.email) + '</span>'
            + (data.priority === 1 ? ' <span class="mail-badge err">Yüksek Öncelik</span>' : '')
            + '      </div>'
            + '      <div class="mail-read-to"><b>Kime</b> ' + (data.to.length ? addressLine(data.to, 4) : '—') + '</div>'
            + cc
            + '    </div>'
            + '    <div class="mail-read-date">' + esc(data.date_full || '') + '<br>'
            + '<span style="font-size:11px;opacity:.75">' + esc(data.date_rel || '') + '</span></div>'
            + '  </div>'
            + crmStrip(data.crm)
            + '</div>'
            + notices
            + attachments
            + '<div class="mail-read-scroll">'
            // Gövde buraya yalıtılmış çerçeve olarak sonradan bağlanır
            + '  <div class="mail-body' + (state.paper ? ' is-paper' : '') + '" id="mailBodyHost"></div>'
            + (isDraft
                ? '<div class="mail-quickreply"><button type="button" data-read-act="editDraft">'
                  + '<i class="fas fa-pen"></i> Taslağı Düzenle</button></div>'
                : '<div class="mail-quickreply">'
                  + '<button type="button" data-read-act="reply"><i class="fas fa-reply"></i> Yanıtla</button>'
                  + '<button type="button" data-read-act="replyAll"><i class="fas fa-reply-all"></i> Tümünü Yanıtla</button>'
                  + '<button type="button" data-read-act="forward"><i class="fas fa-share"></i> İlet</button>'
                  + '</div>')
            + '</div>';

        // "Kağıt" görünümünde zemin her zaman açıktır; koyu metin ancak
        // gövdenin gerçekten koyu zemine çizildiği durumda gerekir.
        mountBody($('#mailBodyHost'), data.body_html, isDarkTheme() && !state.paper);
    }

    /* ── 5. Toplu işlemler ve araç çubuğu ───────────────────── */

    /** Üzerinde işlem yapılacak mesaj kimlikleri: seçim varsa o, yoksa açık ileti. */
    function targetIds() {
        if (state.selected.size) return Array.from(state.selected);
        return state.activeId ? [state.activeId] : [];
    }

    function syncToolbar() {
        const count = targetIds().length;
        $$('.mail-bar [data-act]').forEach((b) => { b.disabled = count === 0; });

        // Gereksiz klasöründeyken aynı düğme geri alma işlevi görür: ileti
        // gelen kutusuna döner ve göndericinin engeli kalkar.
        const spamBtn = $('.mail-bar [data-act="spam"]');
        if (spamBtn) {
            const inJunk = state.scope.folderType === 'junk';
            spamBtn.querySelector('[data-spam-label]').textContent = inJunk ? 'Spam Değil' : 'Spam';
            spamBtn.querySelector('i').className = inJunk ? 'fas fa-inbox' : 'fas fa-ban';
            spamBtn.title = inJunk
                ? 'Spam değil — gelen kutusuna al, gönderici engelini kaldır'
                : 'Spam olarak işaretle ve göndericiyi engelle';
        }

        el.listMeta.textContent = state.selected.size
            ? state.selected.size + ' seçili'
            : (state.total ? state.total + ' ileti' : '');

        el.list.classList.toggle('is-selecting', state.selected.size > 0);
    }

    function toggleSelect(id) {
        if (state.selected.has(id)) state.selected.delete(id);
        else state.selected.add(id);

        const node = el.listScroll.querySelector('.mail-item[data-id="' + id + '"]');
        if (node) node.classList.toggle('is-checked', state.selected.has(id));
        syncToolbar();
    }

    async function runAction(url, payload, options) {
        const ids = payload.ids || targetIds();
        if (!ids.length) return;

        try {
            const data = await api(url, { method: 'POST', body: Object.assign({ ids: ids }, payload) });
            if (options && options.remove) removeRows(ids);
            if (options && options.patch) ids.forEach((id) => patchRow(id, options.patch));
            if (data && data.folders) applyCounts(data.folders);
            if (options && options.toast) toast('success', options.toast);
            state.selected.clear();
            syncToolbar();
        } catch (error) {
            toast('error', 'İşlem başarısız', error.message);
        }
    }

    /* Sayaçlar */

    function applyCounts(counts) {
        if (!counts) return;

        Object.keys(counts.by_id || {}).forEach((id) => {
            // Aynı klasör iki yerde çizilmiş olabilir: kendi ağacında ve
            // sabitlenmişse "Sık kullanılanlar" şeridinde. İkisi de tazelenir.
            $$('[data-folder-count="' + id + '"]').forEach((node) => {
                const button = node.closest('.mail-folder');
                const type = button ? button.dataset.folderType : '';
                const value = (type === 'drafts' || type === 'sent')
                    ? counts.by_id[id].total
                    : counts.by_id[id].unread;
                node.textContent = value ? value : '';
                if (button) button.classList.toggle('has-unread', (counts.by_id[id].unread || 0) > 0
                    && type !== 'drafts' && type !== 'sent');
            });
        });

        Object.keys(counts.unified || {}).forEach((type) => {
            const node = document.querySelector('[data-unified-count="' + type + '"]');
            if (!node) return;
            const value = (type === 'drafts' || type === 'sent')
                ? counts.unified[type].total
                : counts.unified[type].unread;
            node.textContent = value ? value : '';
            const button = node.closest('.mail-folder');
            if (button) button.classList.toggle('has-unread', (counts.unified[type].unread || 0) > 0
                && type !== 'drafts' && type !== 'sent');
        });
    }

    async function refreshCounts() {
        try { applyCounts(await api(CFG.routes.counts)); } catch (e) { /* sessiz */ }
    }

    /* Senkron */

    /**
     * IMAP senkronu.
     *
     * Açık olan tek bir klasördeyken yalnızca o klasör eşitlenir: kullanıcının
     * beklediği iş budur ve saniyeler içinde biter. Birleşik görünümlerde
     * (Tüm Hesaplar / Gelen Kutusu) sunucu tüm klasörleri süre bütçesiyle
     * gezer; bütçe dolarsa kalan klasörler bir sonraki turda tamamlanır.
     */
    async function runSync(silent) {
        if (!hasAccounts || state.syncing) return;
        state.syncing = true;
        el.syncNote.innerHTML = '<i class="fas fa-rotate fa-spin"></i> Eşitleniyor…';

        const body = {};
        if (state.scope.kind === 'folder' && state.scope.folderId) {
            body.folder_id = state.scope.folderId;
        }

        try {
            const data = await api(CFG.routes.sync, { method: 'POST', body: body });
            applyCounts(data.folders);

            if (data.errors && data.errors.length) {
                el.syncNote.innerHTML = '<i class="fas fa-triangle-exclamation" style="color:#f59e0b"></i> Hata';
                if (!silent) toast('error', 'Bazı hesaplar eşitlenemedi', data.errors.join(' · '));
            } else if (data.partial) {
                // Süre bütçesi doldu: kalan klasörler sıradaki turda gelir
                el.syncNote.innerHTML = '<i class="fas fa-hourglass-half"></i> Kısmi eşitleme · ' + data.synced_at;
            } else {
                el.syncNote.textContent = 'Son eşitleme ' + data.synced_at;
            }

            if (data.new > 0) {
                loadList(false);
                if (!silent) toast('success', data.new + ' yeni ileti');
            }
        } catch (error) {
            el.syncNote.innerHTML = '<i class="fas fa-triangle-exclamation" style="color:#f59e0b"></i> Eşitlenemedi';
            if (!silent) toast('error', 'Eşitleme başarısız', error.message);
        } finally {
            state.syncing = false;
        }
    }

    /* Açılır menüler */

    function openMenu(anchor, html, onClick) {
        el.menu.innerHTML = html;
        el.menu.classList.add('is-open');

        const rect = anchor.getBoundingClientRect();
        el.menu.style.visibility = 'hidden';
        el.menu.style.left = '0px';
        el.menu.style.top = '0px';

        requestAnimationFrame(() => {
            const menuRect = el.menu.getBoundingClientRect();
            let left = rect.left;
            let top = rect.bottom + 6;
            if (left + menuRect.width > window.innerWidth - 12) left = window.innerWidth - menuRect.width - 12;
            if (top + menuRect.height > window.innerHeight - 12) top = Math.max(12, rect.top - menuRect.height - 6);
            el.menu.style.left = left + 'px';
            el.menu.style.top = top + 'px';
            el.menu.style.visibility = 'visible';
        });

        el.menu.onclick = (event) => {
            const item = event.target.closest('.mail-menu-item');
            if (!item) return;
            closeMenu();
            onClick(item.dataset);
        };
    }

    function closeMenu() {
        el.menu.classList.remove('is-open');
        el.menu.onclick = null;
    }

    document.addEventListener('mousedown', (event) => {
        if (el.menu && !el.menu.contains(event.target)) closeMenu();
    });

    /** "Taşı" menüsü — seçili iletilerin hesabına ait klasörler. */
    function openMoveMenu(anchor) {
        const ids = targetIds();
        if (!ids.length) return;

        const first = state.items.find((m) => m.id === ids[0]);
        const accountId = first ? first.account_id : state.scope.accountId;
        const folders = (CFG.folders && CFG.folders[accountId]) || [];

        if (!folders.length) {
            toast('info', 'Taşınacak klasör bulunamadı');
            return;
        }

        const html = '<div class="mail-menu-label">Klasöre taşı</div>'
            + folders.map((f) => '<button type="button" class="mail-menu-item" data-folder="' + f.id + '">'
                + '<i class="fas ' + esc(f.icon) + '"></i>' + esc(f.name) + '</button>').join('');

        openMenu(anchor, html, (data) => {
            runAction(CFG.routes.move, { folder_id: Number(data.folder) }, { remove: true, toast: 'Taşındı' });
        });
    }

    /** "Diğer" menüsü. */
    function openMoreMenu(anchor) {
        const inTrashOrJunk = ['trash', 'junk'].indexOf(state.scope.folderType) !== -1;

        const html = ''
            + '<button type="button" class="mail-menu-item" data-more="read"><i class="fas fa-envelope-open"></i>Okundu olarak işaretle</button>'
            + '<button type="button" class="mail-menu-item" data-more="unread"><i class="fas fa-envelope"></i>Okunmadı olarak işaretle</button>'
            + '<button type="button" class="mail-menu-item" data-more="unflag"><i class="far fa-star"></i>Yıldızı kaldır</button>'
            + '<div class="mail-menu-sep"></div>'
            + (state.scope.folderType === 'junk'
                ? '<button type="button" class="mail-menu-item" data-more="notjunk"><i class="fas fa-inbox"></i>Gereksiz değil</button>'
                : '<button type="button" class="mail-menu-item" data-more="junk"><i class="fas fa-ban"></i>Gereksiz e-postaya taşı</button>')
            // Engelleme taşımadan da yapılabilsin: kullanıcı iletiyi yerinde
            // bırakıp yalnızca bundan sonrasını kesmek isteyebilir.
            // Gönderilenler ve taslaklarda gönderen kullanıcının kendisidir —
            // orada engelleme sunmak kendi kutusunu kapatmasına yol açardı.
            + (['sent', 'drafts'].indexOf(state.scope.folderType) === -1
                ? '<button type="button" class="mail-menu-item" data-more="blockSender"><i class="fas fa-user-slash"></i>Göndericiyi engelle</button>'
                  + '<button type="button" class="mail-menu-item" data-more="blockDomain"><i class="fas fa-globe"></i>Alan adını engelle</button>'
                : '')
            + '<button type="button" class="mail-menu-item" data-more="print"><i class="fas fa-print"></i>Yazdır</button>'
            + '<div class="mail-menu-sep"></div>'
            + '<button type="button" class="mail-menu-item is-danger" data-more="delete"><i class="fas fa-fire"></i>Kalıcı olarak sil</button>'
            + (inTrashOrJunk && state.scope.kind === 'folder'
                ? '<button type="button" class="mail-menu-item is-danger" data-more="empty"><i class="fas fa-broom"></i>Klasörü boşalt</button>'
                : '');

        openMenu(anchor, html, async (data) => {
            switch (data.more) {
                case 'read':
                    return runAction(CFG.routes.read, { seen: true }, { patch: { is_seen: true } });
                case 'unread':
                    return runAction(CFG.routes.read, { seen: false }, { patch: { is_seen: false } });
                case 'unflag':
                    return runAction(CFG.routes.flag, { flagged: false }, { patch: { is_flagged: false } });
                case 'junk':
                    return runAction(CFG.routes.junk, {}, { remove: true, toast: 'Gereksiz e-postaya taşındı' });
                case 'notjunk':
                    return runAction(CFG.routes.junk, { not_junk: true }, { remove: true, toast: 'Gelen kutusuna taşındı' });
                case 'blockSender':
                    return blockSenders('email');
                case 'blockDomain':
                    return blockSenders('domain');
                case 'print':
                    if (state.activeId) window.open(CFG.routes.print + '/' + state.activeId + '/print', '_blank');
                    return;
                case 'delete': {
                    const ok = await confirmAsk('Kalıcı olarak silinsin mi?',
                        'Seçili iletiler sunucudan tamamen kaldırılacak. Bu işlem geri alınamaz.', 'Kalıcı Sil');
                    if (ok) runAction(CFG.routes.destroy, {}, { remove: true, toast: 'Kalıcı olarak silindi' });
                    return;
                }
                case 'empty': {
                    const ok = await confirmAsk('Klasör boşaltılsın mı?',
                        '“' + state.scope.label + '” klasöründeki tüm iletiler kalıcı olarak silinecek.', 'Boşalt');
                    if (!ok) return;
                    try {
                        const result = await api(CFG.routes.folderEmpty, {
                            method: 'POST', body: { folder_id: state.scope.folderId },
                        });
                        applyCounts(result.folders);
                        loadList(false);
                        toast('success', 'Klasör boşaltıldı');
                    } catch (error) {
                        toast('error', 'Boşaltılamadı', error.message);
                    }
                    return;
                }
            }
        });
    }

    /* ── Spam işaretleme ve gönderici engelleme ─────────────────
       "Spam", "gereksize taşı"dan farklıdır: iletiyi taşımanın yanında
       göndericiyi de engeller, böylece aynı adresten gelen sonraki postalar
       senkron sırasında kendiliğinden gereksize düşer.

       Engelleme sessizce yapılmaz. Kapsam (yalnızca adres mi, tüm alan adı
       mı) ve geçmişe uygulama işlemden önce sorulur — kullanıcı yalnızca
       taşımak istediğinde engellemeyi kapatabilsin. */

    /** İşlem yapılacak iletilerin liste kayıtları. */
    function targetRows() {
        return targetIds()
            .map((id) => state.items.find((m) => m.id === id))
            .filter(Boolean);
    }

    /**
     * Tek bir gönderici üzerinde mi çalışıyoruz?
     *
     * Gönderilenler ve taslaklarda "karşı taraf" alıcıdır, gönderen değil;
     * o klasörlerdeki satırlar adres tahmininden dışlanır — sunucu zaten
     * engeli her zaman iletinin gerçek From adresinden kurar.
     */
    function singleSender() {
        const list = targetRows()
            .filter((m) => m.folder_type !== 'sent' && m.folder_type !== 'drafts')
            .map((m) => String(m.party_email || '').toLowerCase())
            .filter(Boolean);

        const unique = list.filter((v, i) => list.indexOf(v) === i);
        return unique.length === 1 ? unique[0] : null;
    }

    const domainOf = (email) => (email && email.indexOf('@') !== -1
        ? email.slice(email.lastIndexOf('@') + 1)
        : '');

    /** İşlem öncesi seçenek kutusu. İptal edilirse null döner. */
    async function askSpamOptions(count) {
        const sender = singleSender();
        const domain = domainOf(sender);

        // Bildirim köprüsü yoksa en azından düz bir onay sorulur
        if (typeof window.appAlert !== 'function') {
            const ok = await confirmAsk(
                count > 1 ? count + ' ileti spam olarak işaretlensin mi?' : 'Spam olarak işaretlensin mi?',
                'İletiler Gereksiz E-posta klasörüne taşınır ve gönderici engellenir.',
                'Spama Taşı'
            );
            return ok ? { block: true, block_domain: false, apply_existing: true } : null;
        }

        const html = ''
            + '<div class="mail-spam-ask">'
            + (sender
                ? '<div class="mail-spam-from"><i class="fas fa-at"></i><span>' + esc(sender) + '</span></div>'
                : '<div class="mail-spam-from is-multi"><i class="fas fa-layer-group"></i>'
                  + '<span>Seçili iletilerin göndericileri</span></div>')
            + '<label class="mail-spam-opt"><input type="checkbox" data-spam-opt="block" checked>'
            + '<span><b>Bu göndericiyi engelle</b>'
            + '<em>Bundan sonra gelen iletileri doğrudan gereksize düşsün.</em></span></label>'
            + (domain
                ? '<label class="mail-spam-opt is-sub"><input type="checkbox" data-spam-opt="domain">'
                  + '<span><b>Alan adının tamamını engelle</b>'
                  + '<em>@' + esc(domain) + ' üzerinden yazan herkes engellenir.</em></span></label>'
                : '')
            + '<label class="mail-spam-opt is-sub"><input type="checkbox" data-spam-opt="history" checked>'
            + '<span><b>Eski iletileri de taşı</b>'
            + '<em>Gelen kutusunda bekleyen aynı göndericili iletiler de gereksize alınır.</em></span></label>'
            + '</div>';

        let root = null;

        const result = await window.appAlert({
            icon: 'warning',
            title: count > 1 ? count + ' ileti spam olarak işaretlensin mi?' : 'Spam olarak işaretlensin mi?',
            html: html,
            showCancelButton: true,
            confirmButtonText: 'Spama Taşı',
            cancelButtonText: 'Vazgeç',
            danger: true,
            didOpen: (modal) => {
                root = modal;

                // Engelleme kapatıldığında alt seçenekler anlamsız kalır
                const block = modal.querySelector('[data-spam-opt="block"]');
                const subs = $$('.mail-spam-opt.is-sub', modal);
                const sync = () => subs.forEach((row) => {
                    row.querySelector('input').disabled = !block.checked;
                    row.classList.toggle('is-off', !block.checked);
                });

                block.addEventListener('change', sync);
                sync();
            },
            preConfirm: () => {
                const read = (key) => {
                    const box = root && root.querySelector('[data-spam-opt="' + key + '"]');
                    return !!(box && box.checked && !box.disabled);
                };

                return { block: read('block'), block_domain: read('domain'), apply_existing: read('history') };
            },
        });

        return result && result.isConfirmed ? result.value : null;
    }

    /** Üst çubuktaki "Spam" / "Spam Değil" düğmesi. */
    async function markSpam() {
        if (!targetIds().length) return;

        if (state.scope.folderType === 'junk') {
            const ok = await confirmAsk('Spam değil olarak işaretlensin mi?',
                'İletiler gelen kutusuna alınır ve bu göndericinin engeli kaldırılır.',
                'Gelen Kutusuna Al');
            if (!ok) return;

            return sendSpamRequest({ not_spam: true }, 'Gelen kutusuna alındı');
        }

        const options = await askSpamOptions(targetIds().length);
        if (!options) return;

        return sendSpamRequest(options, 'Spam olarak işaretlendi');
    }

    /**
     * Spam ucu runAction ile paylaşılamıyor: sunucu kaç iletinin engellendiğini
     * ve geçmişten kaç iletinin taşındığını da döndürüyor, kullanıcı bunu
     * görmeden işlemin kapsamını bilemez.
     */
    async function sendSpamRequest(payload, title) {
        const ids = targetIds();

        try {
            const data = await api(CFG.routes.spam, {
                method: 'POST', body: Object.assign({ ids: ids }, payload),
            });

            removeRows(ids);
            applyCounts(data.folders);
            state.selected.clear();
            syncToolbar();
            blocked.loaded = false;   // engel listesi değişti, pencere tazelensin
            toast('success', title, data.message || '');

            // Geçmişe uygulama listeden başka satırları da kaldırmış olabilir
            if (data.history) loadList(false);
        } catch (error) {
            toast('error', 'İşlem başarısız', error.message);
        }
    }

    /** "Diğer işlemler" menüsünden engelleme — ileti bulunduğu yerde kalır. */
    async function blockSenders(kind) {
        const ids = targetIds();
        if (!ids.length) return;

        const sender = singleSender();
        const domain = domainOf(sender);
        const label = kind === 'domain'
            ? (domain ? '@' + domain : 'Bu alan adı')
            : (sender || 'Bu gönderici');

        const ok = await confirmAsk(
            kind === 'domain' ? 'Alan adı engellensin mi?' : 'Gönderici engellensin mi?',
            label + ' adresinden gelen yeni iletiler doğrudan Gereksiz E-posta klasörüne '
            + 'düşecek. Seçili iletiler bulundukları yerde kalır.',
            'Engelle'
        );
        if (!ok) return;

        try {
            const data = await api(CFG.routes.blocked, {
                method: 'POST', body: { ids: ids, match_kind: kind, apply_existing: false },
            });
            blocked.loaded = false;   // engel listesi değişti, pencere tazelensin
            toast('success', 'Engellendi', data.message || '');
        } catch (error) {
            toast('error', 'Engellenemedi', error.message);
        }
    }

    /* ── 6. Yazma penceresi ─────────────────────────────────── */

    const compose = {
        node: $('#mailCompose'),
        editor: null,
        signatureEditor: null,
        recipients: { to: [], cc: [], bcc: [] },
        files: [],
        keepAttachmentIds: [],
        priority: 3,
        replyToId: null,
        inReplyTo: null,
        references: [],
        signatureMark: null,   // "İmza" düğmesinin eklediği parça {index,length,text}
        dirty: false,
        loading: false,   // pencere kuruluyor: editör olayları "kirli" saymaz
        mode: 'float',    // float · min · dock (okuma bölmesinde)
        pos: null,        // sürüklenmiş konum {x, y}; null ise sağ alt köşe
    };

    /* ── Yazma penceresinin yerleşimi ───────────────────────────
       Üç hâl var ve hepsi birbirini dışlar:
         float — sağ altta yüzen pencere (varsayılan), başlıktan sürüklenir
         min   — yalnızca başlık çubuğu görünür
         dock  — okuma bölmesinin yerini alır; kenar çubuğu ve liste kalır

       Pencere DOM'da .mailapp'in dışında ve position:fixed olduğundan
       "dock" hâli için düğümü taşımıyoruz: okuma bölmesinin ekrandaki
       dikdörtgeni ölçülüp pencereye CSS değişkeni olarak veriliyor.
       Böylece Quill editörünün içeriği ve odağı hiç bozulmuyor. */

    const COMPOSE_POS_KEY = 'mailComposePos';
    const COMPOSE_HEAD_H = 44;
    const COMPOSE_KEEP_VISIBLE = 140;   // ekran kenarında görünür kalacak en az genişlik

    function readStoredPos() {
        try {
            const raw = localStorage.getItem(COMPOSE_POS_KEY);
            if (!raw) return null;
            const parsed = JSON.parse(raw);
            return (typeof parsed.x === 'number' && typeof parsed.y === 'number') ? parsed : null;
        } catch (e) { return null; }
    }

    function storePos(pos) {
        try {
            if (pos) localStorage.setItem(COMPOSE_POS_KEY, JSON.stringify(pos));
            else localStorage.removeItem(COMPOSE_POS_KEY);
        } catch (e) { /* depolama kapalıysa konum yalnızca oturumluk olur */ }
    }

    /** Başlık çubuğu daima erişilebilir kalsın diye konumu ekrana sıkıştırır. */
    function clampPos(pos) {
        const width = compose.node.offsetWidth || 700;
        return {
            x: Math.min(Math.max(pos.x, COMPOSE_KEEP_VISIBLE - width), window.innerWidth - COMPOSE_KEEP_VISIBLE),
            y: Math.min(Math.max(pos.y, 0), Math.max(0, window.innerHeight - COMPOSE_HEAD_H)),
        };
    }

    function applyPos() {
        const node = compose.node;

        if (!compose.pos || compose.mode === 'dock') {
            node.classList.remove('is-moved');
            return;
        }

        const pos = clampPos(compose.pos);
        compose.pos = pos;
        node.classList.add('is-moved');
        node.style.setProperty('--mail-compose-x', pos.x + 'px');
        node.style.setProperty('--mail-compose-y', pos.y + 'px');
    }

    /** Okuma bölmesinin ekrandaki yerini pencereye geçirir. */
    function syncDockGeometry() {
        if (compose.mode !== 'dock') return;

        const rect = el.read.getBoundingClientRect();

        // Dar ekranda okuma bölmesi gizli/tam ekran olduğundan yerleşik kip
        // anlamını yitirir — pencere yüzen hâline döner.
        if (rect.width < 320 || rect.height < 200) {
            setComposeMode('float');
            return;
        }

        const node = compose.node;
        node.style.setProperty('--mail-dock-top', rect.top + 'px');
        node.style.setProperty('--mail-dock-left', rect.left + 'px');
        node.style.setProperty('--mail-dock-width', rect.width + 'px');
        node.style.setProperty('--mail-dock-height', rect.height + 'px');
    }

    function setComposeMode(mode) {
        compose.mode = mode;

        const node = compose.node;
        node.classList.toggle('is-min', mode === 'min');
        node.classList.toggle('is-docked', mode === 'dock');

        applyPos();
        syncDockGeometry();

        const button = node.querySelector('[data-compose="max"]');
        if (button) {
            const docked = mode === 'dock';
            button.title = docked ? 'Yüzen pencereye dön' : 'Okuma bölmesinde aç';
            button.innerHTML = '<i class="fas fa-' + (docked
                ? 'down-left-and-up-right-to-center'
                : 'up-right-and-down-left-from-center') + '"></i>';
        }

        // Kip değişince odak kaybolabiliyor; kullanıcı yazmaya devam edebilsin.
        // Pencere kurulurken atlanır: odağı openCompose alıcı alanına verir.
        if (mode !== 'min' && compose.editor && !compose.loading) compose.editor.focus();
    }

    /** Başlık çubuğundan sürükleme — fare ve dokunmatik ortak (pointer olayları). */
    function initComposeDrag() {
        const head = compose.node.querySelector('.mail-compose-head');
        if (!head) return;

        let start = null;

        head.addEventListener('pointerdown', (event) => {
            if (event.button !== 0) return;
            if (event.target.closest('[data-compose]')) return;   // düğmeler sürüklemeyi başlatmaz
            if (compose.mode === 'dock') return;                  // yerleşik kipte pencere taşınmaz

            const rect = compose.node.getBoundingClientRect();
            start = {
                dx: event.clientX - rect.left,
                dy: event.clientY - rect.top,
                moved: false,
            };

            head.setPointerCapture(event.pointerId);
        });

        head.addEventListener('pointermove', (event) => {
            if (!start) return;

            const next = { x: event.clientX - start.dx, y: event.clientY - start.dy };

            // Küçük titremeler pencereyi yerinden oynatmasın; sıradan bir
            // tıklama da onu "taşınmış" duruma geçirmemeli (görünümü değişir).
            if (!start.moved) {
                const rect = compose.node.getBoundingClientRect();
                if (Math.abs(next.x - rect.left) < 3 && Math.abs(next.y - rect.top) < 3) return;
                start.moved = true;
                document.body.classList.add('mail-dragging');
            }

            compose.pos = next;
            applyPos();
        });

        const finish = (event) => {
            if (!start) return;

            const moved = start.moved;
            start = null;

            if (head.hasPointerCapture(event.pointerId)) head.releasePointerCapture(event.pointerId);
            document.body.classList.remove('mail-dragging');

            if (moved) storePos(compose.pos);
        };

        head.addEventListener('pointerup', finish);
        head.addEventListener('pointercancel', finish);

        // Başlığa çift tıklama okuma bölmesine yerleştirir / geri alır
        head.addEventListener('dblclick', (event) => {
            if (event.target.closest('[data-compose]')) return;
            setComposeMode(compose.mode === 'dock' ? 'float' : 'dock');
        });

        // Ekran boyutu ya da bölme genişliği değişince yerleşim yeniden hesaplanır
        window.addEventListener('resize', () => {
            applyPos();
            syncDockGeometry();
        });

        if (window.ResizeObserver && el.read) {
            new ResizeObserver(() => syncDockGeometry()).observe(el.read);
        }
    }

    function ensureEditor() {
        if (compose.editor || typeof window.Quill === 'undefined') return;

        compose.editor = new window.Quill('#mailEditor', {
            theme: 'snow',
            placeholder: 'İletinizi yazın…',
            modules: {
                toolbar: [
                    [{ header: [1, 2, 3, false] }],
                    ['bold', 'italic', 'underline', 'strike'],
                    [{ color: [] }, { background: [] }],
                    [{ list: 'ordered' }, { list: 'bullet' }],
                    [{ align: [] }],
                    ['link', 'blockquote', 'code-block'],
                    ['clean'],
                ],
            },
        });

        // Yalnızca kullanıcının yazdığı sayılır: pencere kurulurken alıntı/imza
        // programatik olarak yazıldığında bu olay da tetikleniyor ve sayfadan
        // ayrılma uyarısını haksız yere tetikliyordu.
        compose.editor.on('text-change', () => {
            if (compose.loading) return;
            compose.dirty = true;
            scheduleAutosave();
        });
    }

    /* "Kimden" seçicisi — hesaplar + gönderici profilleri ─────────
       Bir seçenek hem hesabı hem profili taşır: "12" hesabın kendi adı,
       "12:5" ise 12 no'lu hesabın 5 no'lu profili. Adres her iki durumda
       da aynıdır; değişen yalnızca alıcının göreceği gönderen adıdır. */

    /** Seçici değerini {accountId, identityId} olarak ayrıştırır. */
    function parseFromValue(value) {
        const parts = String(value || '').split(':');
        return {
            accountId: Number(parts[0]) || null,
            identityId: parts.length > 1 ? Number(parts[1]) || null : null,
        };
    }

    /** Hesabın açılışta seçilecek değeri: varsayılan profili varsa o. */
    function defaultFromValue(account) {
        if (!account) return '';
        const preferred = (account.identities || []).find((i) => i.is_default);
        return preferred ? account.id + ':' + preferred.id : String(account.id);
    }

    /** Yazma penceresindeki gönderen listesini CFG.accounts'tan kurar. */
    function renderFromSelect(preferred) {
        const select = $('#mailFrom');
        if (!select) return;

        const accounts = CFG.accounts || [];
        const grouped = accounts.length > 1;   // tek hesapta grup başlığı gürültü olur

        select.innerHTML = accounts.map((account) => {
            const option = (value, label) => '<option value="' + esc(value) + '">'
                + esc(label + ' <' + account.email + '>') + '</option>';

            const rows = [option(String(account.id), account.display_from || account.name || account.email)]
                .concat((account.identities || []).map((i) => option(account.id + ':' + i.id, i.name)));

            return grouped
                ? '<optgroup label="' + esc(account.email) + '">' + rows.join('') + '</optgroup>'
                : rows.join('');
        }).join('');

        // İstenen değer artık listede yoksa (profil silinmiş olabilir) varsayılana dön
        const fallback = defaultFromValue(accounts.find((a) => a.is_default) || accounts[0]);
        select.value = preferred || fallback;
        if (!select.value) select.value = fallback;
    }

    /** Yanıtla/İlet: ilgili hesap seçilsin, varsayılan profili varsa onunla. */
    function selectFromAccount(accountId) {
        const account = accountById(accountId);
        if (!account) return;
        $('#mailFrom').value = defaultFromValue(account);
    }

    /**
     * "İmza" düğmesiyle eklenmiş parçayı gövdeden çıkarır.
     *
     * Yalnızca eklendiği andaki metin harfi harfine yerinde duruyorsa silinir;
     * kullanıcı imzanın içine yazdıysa ya da araya metin soktuysa gövdeye
     * dokunulmaz — iki imza kalması, yazılanın kaybolmasından iyidir.
     */
    function removeInsertedSignature() {
        const mark = compose.signatureMark;
        compose.signatureMark = null;

        if (!mark || !compose.editor) return;
        if (mark.index + mark.length > compose.editor.getLength()) return;
        if (compose.editor.getText(mark.index, mark.length) !== mark.text) return;

        compose.editor.deleteText(mark.index, mark.length);
    }

    function openCompose(mode, source) {
        ensureEditor();
        renderFromSelect();

        compose.loading = true;   // kurulum sırasındaki editör değişiklikleri "kirli" saymasın
        compose.recipients = { to: [], cc: [], bcc: [] };
        compose.files = [];
        compose.keepAttachmentIds = [];
        compose.priority = 3;
        compose.replyToId = null;
        compose.inReplyTo = null;
        compose.references = [];
        compose.draftMessageId = null;
        compose.signatureMark = null;
        compose.dirty = false;

        // Otomatik kayıt zinciri her pencerede sıfırdan başlar
        cancelAutosave();
        autosave.key = null;
        autosave.accountId = null;
        autosave.running = false;
        autosave.again = false;
        autosave.failed = false;
        setDraftState('', '');

        $('#mailSubject').value = '';
        if (compose.editor) compose.editor.setContents([]);
        $('#mailPriorityBadge').style.display = 'none';
        $$('.mail-field[data-row="cc"], .mail-field[data-row="bcc"]').forEach((r) => { r.style.display = 'none'; });
        $$('[data-toggle-field]').forEach((b) => b.classList.remove('is-on'));

        // Rehber önceki iletiden açık kalmasın; hedef alan "Kime"ye döner
        toggleDirectory(false);
        dir.target = 'to';
        $$('#mailDirTarget button').forEach((b) => b.classList.toggle('is-on', b.dataset.dirTarget === 'to'));

        let title = 'Yeni İleti';

        if (source && source.reply) {
            const reply = source.reply;
            compose.replyToId = source.id;
            compose.inReplyTo = reply.in_reply_to;
            compose.references = reply.references || [];

            if (source.account_id) selectFromAccount(source.account_id);

            if (mode === 'forward') {
                title = 'İlet';
                $('#mailSubject').value = reply.subject_forward;
                compose.keepAttachmentIds = reply.attachment_ids || [];
            } else {
                title = mode === 'replyAll' ? 'Tümünü Yanıtla' : 'Yanıtla';
                $('#mailSubject').value = reply.subject_reply;
                compose.recipients.to = (reply.to || []).slice();
                if (mode === 'replyAll' && (reply.cc_all || []).length) {
                    compose.recipients.cc = reply.cc_all.slice();
                    $('.mail-field[data-row="cc"]').style.display = '';
                    $('[data-toggle-field="cc"]').classList.add('is-on');
                }
            }

            if (compose.editor) {
                compose.editor.clipboard.dangerouslyPasteHTML(
                    '<p><br></p><p><br></p>' + (reply.quote_html || '')
                );
                compose.editor.setSelection(0, 0);
            }
        }

        if (mode === 'draft' && source) {
            title = 'Taslağı Düzenle';
            compose.draftMessageId = source.id;
            $('#mailSubject').value = source.subject === '(konu yok)' ? '' : source.subject;
            compose.recipients.to = source.to || [];
            compose.recipients.cc = source.cc || [];
            if (compose.recipients.cc.length) {
                $('.mail-field[data-row="cc"]').style.display = '';
                $('[data-toggle-field="cc"]').classList.add('is-on');
            }
            if (compose.editor && source.body_html) {
                compose.editor.clipboard.dangerouslyPasteHTML(source.body_html);
            }
            if (source.account_id) selectFromAccount(source.account_id);
        }

        $('#mailComposeTitle').textContent = title;
        renderRecipients();
        renderAttachList();

        compose.node.classList.add('is-open');

        // Pencere en son bırakıldığı yerde açılsın; yerleşik kip her yeni
        // iletide sıfırlanır ki liste ve okuma bölmesi görünür kalsın.
        compose.pos = readStoredPos();
        setComposeMode('float');

        setTimeout(() => {
            const focusField = compose.recipients.to.length ? '#mailSubject' : '.mail-chips[data-field="to"] input';
            const target = $(focusField);
            if (target) target.focus();

            // Kurulum bitti; bundan sonraki her değişiklik kullanıcıya ait
            compose.loading = false;
            compose.dirty = false;
        }, 60);
    }

    /**
     * Pencereyi kapatır — taslak sunucuda KALIR.
     *
     * Otomatik kayıt devreye girdiğinden kapatmak artık veri kaybı değil;
     * onay yalnızca gerçekten kaydedilmemiş bir şey varsa (son kayıt başarısız
     * ya da değişiklik henüz yazılmadı) sorulur. İletiyi tümden atmak için
     * discardCompose() var.
     */
    function closeCompose(force) {
        if (!force && (compose.dirty || autosave.failed)) {
            confirmAsk('İleti kapatılsın mı?',
                autosave.key
                    ? 'Son değişiklikler henüz taslağa yazılmadı ve kaybolacak.'
                    : 'Yazdıklarınız kaydedilmedi ve kaybolacak.',
                'Kapat')
                .then((ok) => { if (ok) closeCompose(true); });
            return;
        }

        cancelAutosave();

        // Taslak silinmedi; kullanıcı nerede bulacağını bilsin
        if (autosave.key) toast('info', 'Taslak saklandı', 'İleti Taslaklar klasöründe duruyor.');
        autosave.key = null;
        autosave.accountId = null;

        compose.node.classList.remove('is-open', 'is-docked', 'is-min', 'is-moved');
        compose.mode = 'float';
        compose.dirty = false;
        setDraftState('', '');
    }

    /* Alıcı etiketleri */

    function renderRecipients() {
        ['to', 'cc', 'bcc'].forEach((field) => {
            const wrap = $('.mail-chips[data-field="' + field + '"]');
            if (!wrap) return;

            const input = wrap.querySelector('input');
            wrap.querySelectorAll('.mail-recipient').forEach((n) => n.remove());

            compose.recipients[field].forEach((person, index) => {
                const valid = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(person.email || '');
                const chip = document.createElement('span');
                chip.className = 'mail-recipient' + (valid ? '' : ' is-invalid');
                chip.title = person.email;
                chip.innerHTML = '<span>' + esc(person.name && person.name !== person.email ? person.name : person.email)
                    + '</span><button type="button" data-remove="' + field + ':' + index + '"><i class="fas fa-xmark"></i></button>';
                wrap.insertBefore(chip, input);
            });
        });
    }

    function addRecipient(field, raw) {
        const text = String(raw || '').trim().replace(/[,;]+$/, '');
        if (!text) return;

        // "Ad Soyad <a@b.com>" biçimini ayır
        const match = text.match(/^(.*)<([^>]+)>$/);
        const email = (match ? match[2] : text).trim().toLowerCase();
        const name = match ? match[1].trim().replace(/^["']|["']$/g, '') : '';

        if (!email) return;
        if (compose.recipients[field].some((p) => p.email === email)) return;

        compose.recipients[field].push({ name: name || email, email: email });
        compose.dirty = true;
        renderRecipients();

        // Alıcı etiketi oluşması "anlamlı değişiklik"tir: uzun uzun bekleme
        scheduleAutosave(AUTOSAVE_BLUR);
    }

    function serializeRecipients(field) {
        return compose.recipients[field]
            .map((p) => (p.name && p.name !== p.email ? '"' + p.name + '" <' + p.email + '>' : p.email))
            .join(', ');
    }

    /* Otomatik tamamlama */

    const suggestState = { field: null, items: [], cursor: -1 };

    const searchContacts = debounce(async (field, term, box) => {
        if (term.length < 2) { box.classList.remove('is-open'); return; }

        try {
            const results = await api(CFG.routes.contacts + '?q=' + encodeURIComponent(term));
            suggestState.field = field;
            suggestState.items = results;
            suggestState.cursor = -1;

            if (!results.length) { box.classList.remove('is-open'); return; }

            // Sonuçlar rehberden gelir: hangi kayıttan geldiği rozetle belirtilir
            box.innerHTML = results.map((c, i) => ''
                + '<div class="mail-suggest-item" data-index="' + i + '">'
                + '<div class="mail-avatar" style="background:' + (c.color || avatarColor(c.email)) + '">'
                + (c.icon ? '<i class="fas ' + esc(c.icon) + '"></i>' : esc((c.name || c.email).charAt(0).toUpperCase()))
                + '</div>'
                + '<div><div class="mail-suggest-name">' + esc(c.name)
                + (c.kind_label ? '<em class="mail-suggest-kind">' + esc(c.kind_label) + '</em>' : '')
                + '</div>'
                + '<div class="mail-suggest-mail">' + esc(c.email)
                + (c.meta ? ' · ' + esc(c.meta) : '') + '</div></div></div>').join('');
            box.classList.add('is-open');
        } catch (e) {
            box.classList.remove('is-open');
        }
    }, 220);

    /* ── Rehber ─────────────────────────────────────────────────
       Alıcı yalnızca elle yazılmaz: panelin kendi kayıtları (müşteri,
       tedarikçi, tedarikçi yetkilisi, kaynak) ve yazışmalardan biriken
       adres defteri buradan seçilir. Seçilen kayıt doğrudan alıcı
       etiketine döner; hedef alan (Kime/Bilgi/Gizli) üstten değiştirilir. */

    const dir = { open: false, kind: null, target: 'to', items: [], counts: {}, kinds: [], loaded: false };

    function toggleDirectory(force) {
        const panel = $('#mailDir');
        if (!panel) return;

        dir.open = force === undefined ? !dir.open : force;
        panel.hidden = !dir.open;
        $('#mailDirBtn')?.classList.toggle('is-on', dir.open);

        if (!dir.open) return;

        // Gizli alanlar seçim hedefi olabilsin diye açılır
        if (!dir.loaded) loadDirectory();
        else renderDirectory();

        setTimeout(() => $('#mailDirSearch')?.focus(), 30);
    }

    async function loadDirectory() {
        const list = $('#mailDirList');
        list.innerHTML = '<div class="mail-dir-empty"><i class="fas fa-spinner fa-spin"></i> Rehber yükleniyor…</div>';

        try {
            const params = new URLSearchParams();
            if (dir.kind) params.set('kind', dir.kind);
            const term = $('#mailDirSearch')?.value.trim();
            if (term) params.set('q', term);

            const data = await api(CFG.routes.directory + '?' + params.toString());
            dir.kinds = data.kinds || [];
            dir.counts = data.counts || {};
            dir.items = data.items || [];
            dir.loaded = true;
            renderDirectory();
        } catch (error) {
            list.innerHTML = '<div class="mail-dir-empty">Rehber açılamadı: ' + esc(error.message) + '</div>';
        }
    }

    const searchDirectory = debounce(loadDirectory, 240);

    function renderDirectory() {
        const kinds = $('#mailDirKinds');
        const list = $('#mailDirList');
        if (!kinds || !list) return;

        kinds.innerHTML = '<button type="button" class="mail-dir-kind' + (dir.kind ? '' : ' is-on') + '" data-dir-kind="">'
            + '<i class="fas fa-layer-group"></i> Tümü</button>'
            + dir.kinds.map((k) => ''
                + '<button type="button" class="mail-dir-kind' + (dir.kind === k.key ? ' is-on' : '') + '"'
                + ' data-dir-kind="' + esc(k.key) + '" style="--dk:' + esc(k.color) + '">'
                + '<i class="fas ' + esc(k.icon) + '"></i> ' + esc(k.label)
                + '<b>' + (dir.counts[k.key] ?? 0) + '</b></button>').join('');

        if (!dir.items.length) {
            list.innerHTML = '<div class="mail-dir-empty"><i class="fas fa-inbox"></i> Eşleşen kayıt yok.</div>';
            return;
        }

        // Zaten alıcı olarak eklenmiş adresler işaretli görünür
        const chosen = new Set(
            ['to', 'cc', 'bcc'].flatMap((f) => compose.recipients[f].map((p) => p.email))
        );

        list.innerHTML = dir.items.map((item, i) => ''
            + '<button type="button" class="mail-dir-item' + (chosen.has(item.email) ? ' is-added' : '') + '"'
            + ' data-dir-index="' + i + '" style="--dk:' + esc(item.color) + '">'
            + '<span class="mail-dir-ic"><i class="fas ' + esc(item.icon) + '"></i></span>'
            + '<span class="mail-dir-t">'
            + '<span class="mail-dir-n">' + esc(item.name)
            + '<em>' + esc(item.kind_label) + '</em></span>'
            + '<span class="mail-dir-m">' + esc(item.email) + ' · ' + esc(item.meta) + '</span>'
            + '</span>'
            + '<span class="mail-dir-add"><i class="fas fa-' + (chosen.has(item.email) ? 'check' : 'plus') + '"></i></span>'
            + '</button>').join('');
    }

    /** Rehberden seçilen kaydı hedef alana ekler; panel açık kalır (çoklu seçim). */
    function pickFromDirectory(index) {
        const item = dir.items[index];
        if (!item) return;

        // Gizli satır seçilmişse görünür olsun, yoksa etiket kaybolmuş görünür
        const row = $('.mail-field[data-row="' + dir.target + '"]');
        if (row && row.style.display === 'none') row.style.display = '';

        addRecipient(dir.target, '"' + item.name + '" <' + item.email + '>');
        renderDirectory();
    }

    /* Ekler */

    function renderAttachList() {
        const wrap = $('#mailAttachList');
        if (!wrap) return;

        const parts = compose.files.map((file, index) => ''
            + '<span class="mail-attach-pill"><i class="fas fa-paperclip"></i>'
            + '<span>' + esc(file.name) + '</span>'
            + '<span style="opacity:.6">' + humanSize(file.size) + '</span>'
            + '<button type="button" data-remove-file="' + index + '"><i class="fas fa-xmark"></i></button></span>');

        if (compose.keepAttachmentIds.length) {
            parts.unshift('<span class="mail-attach-pill"><i class="fas fa-share"></i>'
                + '<span>İletilen ' + compose.keepAttachmentIds.length + ' ek</span>'
                + '<button type="button" data-remove-kept="1"><i class="fas fa-xmark"></i></button></span>');
        }

        wrap.innerHTML = parts.join('');
    }

    function addFiles(fileList) {
        Array.from(fileList).forEach((file) => {
            if (file.size > 25 * 1024 * 1024) {
                toast('error', 'Dosya çok büyük', file.name + ' — en fazla 25 MB.');
                return;
            }
            compose.files.push(file);
        });
        compose.dirty = true;
        renderAttachList();
        scheduleAutosave(AUTOSAVE_BLUR);
    }

    /* Gönderme */

    function composeFormData() {
        const form = new FormData();
        const from = parseFromValue($('#mailFrom').value);

        form.append('account_id', String(from.accountId || ''));
        if (from.identityId) form.append('identity_id', String(from.identityId));
        form.append('to', serializeRecipients('to'));
        form.append('cc', serializeRecipients('cc'));
        form.append('bcc', serializeRecipients('bcc'));
        form.append('subject', $('#mailSubject').value);
        form.append('body_html', compose.editor ? compose.editor.root.innerHTML : '');
        form.append('priority', String(compose.priority));

        if (compose.inReplyTo) form.append('in_reply_to', compose.inReplyTo);
        if (compose.references.length) form.append('references', compose.references.join(' '));
        if (compose.replyToId) form.append('reply_to_id', String(compose.replyToId));
        if (compose.keepAttachmentIds.length) form.append('keep_attachment_ids', compose.keepAttachmentIds.join(','));

        // Otomatik kaydın taslağı: gönderim başarılı olursa sunucu bunu siler
        if (autosave.key) form.append('draft_message_id', autosave.key);

        compose.files.forEach((file) => form.append('attachments[]', file));
        return form;
    }

    /** Henüz etikete dönüşmemiş alıcı metnini de dahil eder. */
    function flushPendingRecipients() {
        ['to', 'cc', 'bcc'].forEach((field) => {
            const input = $('.mail-chips[data-field="' + field + '"] input');
            if (input && input.value.trim()) {
                addRecipient(field, input.value);
                input.value = '';
            }
        });
    }

    async function sendMail() {
        flushPendingRecipients();

        if (!compose.recipients.to.length) {
            toast('error', 'Alıcı gerekli', 'En az bir alıcı adresi girin.');
            return;
        }

        // Bekleyen otomatik kayıt gönderimle yarışmasın
        cancelAutosave();

        const button = $('#mailSendBtn');
        button.disabled = true;
        button.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>Gönderiliyor…</span>';

        try {
            const data = await api(CFG.routes.send, { method: 'POST', body: composeFormData() });
            applyCounts(data.folders);
            toast('success', 'Gönderildi', data.message);

            if (compose.replyToId) patchRow(compose.replyToId, { is_answered: true });
            await discardSourceDraft();

            // Taslağı sunucu gönderimin ardından kendisi sildi (draft_message_id)
            autosave.key = null;
            closeCompose(true);
        } catch (error) {
            toast('error', 'Gönderilemedi', error.message);
        } finally {
            button.disabled = false;
            button.innerHTML = '<i class="fas fa-paper-plane"></i><span>Gönder</span>';
        }
    }

    /**
     * Düzenlenen taslak sunucuya yeni bir kopya olarak yazıldığı için,
     * kaynak taslak gönderim/yeniden kaydetme sonrası kaldırılır.
     */
    async function discardSourceDraft() {
        if (!compose.draftMessageId) return;

        const id = compose.draftMessageId;
        compose.draftMessageId = null;

        try {
            await api(CFG.routes.destroy, { method: 'POST', body: { ids: [id] } });
            removeRows([id]);
        } catch (e) { /* taslak silinemezse kullanıcıyı meşgul etme */ }
    }

    /* Otomatik taslak kaydı ────────────────────────────────────────
       Sayfa yenilendiğinde yazılanlar kaybolmasın diye pencere kendiliğinden
       taslak kaydeder. IMAP'te var olan ileti güncellenemediği için her kayıt
       yeni bir ileti yazmak zorundadır; bu yüzden bir önceki sürümün
       Message-ID'si sunucuya geri gönderilir ve o sürüm kaldırılır. Taslaklar
       klasöründe her zaman tek satır kalır.

       Zamanlayıcı ortak debounce() yerine elle kuruluyor: gönderim ve "iletiyi
       at" akışlarında bekleyen kaydı İPTAL edebilmek gerekiyor. */

    const autosave = {
        key: null,        // sunucudaki taslağın Message-ID'si
        accountId: null,  // taslağın hangi kutuda durduğu
        timer: null,
        running: false,
        again: false,     // kayıt uçarken yeni değişiklik geldi
        failed: false,
    };

    const AUTOSAVE_IDLE = 2500;    // yazma duraklamasından sonra
    const AUTOSAVE_BLUR = 400;     // alan bırakıldığında / etiket eklendiğinde
    // Ekli dosya varsa her kayıt dosyaları yeniden yükler; sık kayıt
    // 20 MB'lık bir eki dakikada onlarca kez göndermek olurdu.
    const AUTOSAVE_FILES = 15000;

    function setDraftState(state, text) {
        const box = $('#mailDraftState');
        if (!box) return;

        box.className = 'mail-draft-state' + (state ? ' is-' + state : '');
        box.textContent = text || '';
        box.title = text || '';
    }

    function cancelAutosave() {
        clearTimeout(autosave.timer);
        autosave.timer = null;
    }

    function scheduleAutosave(delay) {
        cancelAutosave();
        if (compose.loading || !compose.node.classList.contains('is-open')) return;

        let wait = delay === undefined ? AUTOSAVE_IDLE : delay;
        if (compose.files.length) wait = Math.max(wait, AUTOSAVE_FILES);

        autosave.timer = setTimeout(() => persistDraft(true), wait);
    }

    /** Boş pencere için taslak açılmaz — Taslaklar klasörü çöplük olmasın. */
    function composeHasContent() {
        const body = compose.editor ? compose.editor.getText().trim() : '';

        return !!($('#mailSubject').value.trim() || body || compose.files.length
            || compose.recipients.to.length || compose.recipients.cc.length || compose.recipients.bcc.length);
    }

    function draftClock() {
        return new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
    }

    /**
     * Taslağı sunucuya yazar.
     *
     * @param {boolean} silent Otomatik kayıt mı? Sessizse bildirim çıkmaz;
     *                         durum yalnızca alt bardaki göstergeye yazılır.
     */
    async function persistDraft(silent) {
        cancelAutosave();

        if (!compose.node.classList.contains('is-open') || compose.loading) return;

        if (!composeHasContent()) {
            if (!silent) toast('info', 'Taslak boş', 'Kaydedilecek bir şey yok.');
            return;
        }

        // Uçuşta kayıt varsa üstüne binme; bittiğinde yeniden denenir
        if (autosave.running) { autosave.again = true; return; }

        autosave.running = true;
        compose.dirty = false;
        setDraftState('saving', 'Taslak kaydediliyor…');

        try {
            const form = composeFormData();
            if (autosave.key) form.append('replace_message_id', autosave.key);

            const data = await api(CFG.routes.draft, { method: 'POST', body: form });
            applyCounts(data.folders);

            autosave.key = (data.draft && data.draft.message_id) || autosave.key;
            autosave.accountId = parseFromValue($('#mailFrom').value).accountId;
            autosave.failed = false;

            // Kullanıcı var olan bir taslağı düzenliyorduysa kaynak bir kez temizlenir
            await discardSourceDraft();

            setDraftState('saved', 'Taslak kaydedildi · ' + draftClock());
            if (!silent) toast('success', 'Taslak kaydedildi');
        } catch (error) {
            // Kayıt hatası yazmayı KESMEZ: gösterge hataya döner, kullanıcı
            // "Taslak" düğmesiyle elle deneyebilir, değişiklikler kirli kalır.
            autosave.failed = true;
            compose.dirty = true;
            setDraftState('error', 'Taslak kaydedilemedi — “Taslak” ile yeniden deneyin');
            if (!silent) toast('error', 'Taslak kaydedilemedi', error.message);
        } finally {
            autosave.running = false;
            if (autosave.again) { autosave.again = false; scheduleAutosave(AUTOSAVE_BLUR); }
        }
    }

    /** Otomatik kaydedilmiş taslağı sunucudan siler ("iletiyi at" / gönderim). */
    async function dropAutosavedDraft() {
        const key = autosave.key;
        // Taslak yazıldığı kutuda durur — kullanıcı bu arada hesap değiştirmiş
        // olabilir, o yüzden seçicinin güncel değeri değil kayıttaki kutu.
        const accountId = autosave.accountId;

        autosave.key = null;
        autosave.accountId = null;
        if (!key || !accountId) return;

        try {
            const data = await api(CFG.routes.draftDiscard, {
                method: 'POST', body: { account_id: accountId, message_id: key },
            });
            applyCounts(data.folders);
        } catch (e) { /* kalıntı taslak elle silinebilir; kullanıcıyı meşgul etme */ }
    }

    async function saveDraft() {
        flushPendingRecipients();

        const button = $('#mailDraftBtn');
        button.disabled = true;

        try {
            await persistDraft(false);
        } finally {
            button.disabled = false;
        }
    }

    /** "İletiyi at": pencereyi kapatır ve otomatik kaydedilen taslağı siler. */
    async function discardCompose() {
        const saved = !!autosave.key;

        const ok = await confirmAsk('İleti atılsın mı?', saved
            ? 'Otomatik kaydedilen taslak da Taslaklar klasöründen silinecek.'
            : 'Yazdıklarınız kaybolacak.', 'At');
        if (!ok) return;

        cancelAutosave();
        await dropAutosavedDraft();
        closeCompose(true);
    }

    /* ── 7. Hesap yönetimi ──────────────────────────────────── */

    const PRESETS = {
        gmail: { imap_host: 'imap.gmail.com', imap_port: 993, imap_encryption: 'ssl', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_encryption: 'tls' },
        outlook: { imap_host: 'outlook.office365.com', imap_port: 993, imap_encryption: 'ssl', smtp_host: 'smtp.office365.com', smtp_port: 587, smtp_encryption: 'tls' },
        yandex: { imap_host: 'imap.yandex.com.tr', imap_port: 993, imap_encryption: 'ssl', smtp_host: 'smtp.yandex.com.tr', smtp_port: 465, smtp_encryption: 'ssl' },
        yaani: { imap_host: 'imap.yaani.com', imap_port: 993, imap_encryption: 'ssl', smtp_host: 'smtp.yaani.com', smtp_port: 465, smtp_encryption: 'ssl' },
        cpanel: { imap_port: 993, imap_encryption: 'ssl', smtp_port: 465, smtp_encryption: 'ssl' },
    };

    const accountsUI = {
        modal: null,
        editingId: null,
        identities: [],           // açık hesabın gönderici profilleri
        renamingId: null,         // satır içi yeniden adlandırılan profil
        signingId: null,          // imza paneli açık olan profil (aynı anda tek)
        identitySignature: null,  // profil imzası editörü (Quill)
        signHome: null,           // panelin liste dışındaki asıl yeri
        signAccountHtml: '',      // devralınan hesap imzası — soluk önizleme için
        signDraft: null,          // "hesabınkini kullan" açılınca saklanan taslak
    };

    function accountsModal() {
        if (!accountsUI.modal && window.bootstrap) {
            accountsUI.modal = new window.bootstrap.Modal($('#mailAccountsModal'));
        }
        return accountsUI.modal;
    }

    async function openAccounts() {
        showAccountsList();
        accountsModal() && accountsModal().show();

        const wrap = $('#mailAccountsCards');
        wrap.innerHTML = '<div style="padding:24px;text-align:center;color:#94a3b8">'
            + '<i class="fas fa-spinner fa-spin"></i></div>';

        try {
            const accounts = await api(CFG.routes.accounts);
            if (!accounts.length) {
                wrap.innerHTML = '<div style="padding:26px 10px;text-align:center;color:#94a3b8;font-size:12.5px">'
                    + 'Henüz bağlı hesap yok.</div>';
                return;
            }

            wrap.innerHTML = accounts.map((a) => {
                const badge = a.status === 'ok'
                    ? '<span class="mail-badge ok">Bağlı</span>'
                    : (a.status === 'error'
                        ? '<span class="mail-badge err" title="' + esc(a.last_error || '') + '">Hata</span>'
                        : '<span class="mail-badge wait">Bekliyor</span>');

                return '<div class="mail-acct-card">'
                    + '<div class="mail-avatar" style="background:' + esc(a.color) + '">'
                    + esc((a.name || a.email).charAt(0).toUpperCase()) + '</div>'
                    + '<div class="mail-acct-info">'
                    + '<div class="mail-acct-name">' + esc(a.name) + badge
                    + (a.is_default ? '<span class="mail-badge def">Varsayılan</span>' : '')
                    + (a.is_shared ? '<span class="mail-badge shared">Paylaşılan</span>' : '')
                    + '</div>'
                    + '<div class="mail-acct-mail">' + esc(a.email)
                    + ((a.identities || []).length ? ' · ' + a.identities.length + ' gönderici profili' : '')
                    + (a.last_synced_at ? ' · son eşitleme ' + esc(a.last_synced_at) : '') + '</div>'
                    + '</div>'
                    + '<button type="button" class="mail-icon-btn" data-acct-edit="' + a.id + '" title="Düzenle">'
                    + '<i class="fas fa-pen"></i></button>'
                    + '<button type="button" class="mail-icon-btn" data-acct-resync="' + a.id + '" '
                    + 'title="Postaları baştan indir (sunucuya dokunmaz)">'
                    + '<i class="fas fa-arrows-rotate"></i></button>'
                    + '<button type="button" class="mail-icon-btn" data-acct-default="' + a.id + '" title="Varsayılan yap">'
                    + '<i class="fas fa-thumbtack"></i></button>'
                    + '<button type="button" class="mail-icon-btn" data-acct-delete="' + a.id + '" title="Kaldır" '
                    + 'style="color:#dc2626"><i class="fas fa-trash-can"></i></button>'
                    + '</div>';
            }).join('');
        } catch (error) {
            wrap.innerHTML = '<div style="padding:20px;color:#dc2626;font-size:12.5px">' + esc(error.message) + '</div>';
        }
    }

    function showAccountsList() {
        accountsUI.editingId = null;
        $('#mailAccountsList').style.display = '';
        $('#mailAccountForm').style.display = 'none';
        $('#mailAccountBack').style.display = 'none';
        $('#mailAccountSave').style.display = 'none';
        $('#mailAccountTest').style.display = 'none';
        $('#mailAccountsClose').style.display = '';
        $('#mailAccountsModalTitle').textContent = 'E-posta Hesapları';
        clearTestResult();
    }

    /** Hesap ve profil imzası aynı editör motorunu paylaşır — tek kurulum noktası. */
    function makeSignatureEditor(selector) {
        if (typeof window.Quill === 'undefined') return null;
        return new window.Quill(selector, {
            theme: 'snow',
            placeholder: 'Ad Soyad · Unvan · Telefon',
            modules: {
                toolbar: [['bold', 'italic', 'underline'], [{ color: [] }], ['link'], ['clean']],
            },
        });
    }

    function ensureSignatureEditor() {
        if (!accountsUI.signature) accountsUI.signature = makeSignatureEditor('#mailSignatureEditor');
    }

    function ensureIdentitySignatureEditor() {
        if (!accountsUI.identitySignature) {
            accountsUI.identitySignature = makeSignatureEditor('#mailIdentitySignEditor');
        }
    }

    function showAccountForm(account) {
        ensureSignatureEditor();

        const form = $('#mailAccountForm');
        form.reset();
        accountsUI.editingId = account ? account.id : null;

        $('#mailAccountsList').style.display = 'none';
        form.style.display = '';
        $('#mailAccountBack').style.display = '';
        $('#mailAccountSave').style.display = '';
        $('#mailAccountTest').style.display = '';
        $('#mailAccountsClose').style.display = 'none';
        clearTestResult();
        $('#mailAccountsModalTitle').textContent = account ? 'Hesabı Düzenle' : 'Yeni Hesap Bağla';

        $$('[data-edit-only]').forEach((n) => { n.style.display = account ? '' : 'none'; });
        $$('[data-new-only]').forEach((n) => { n.style.display = account ? 'none' : ''; });
        $$('[data-required-star]').forEach((n) => { n.style.display = account ? 'none' : ''; });
        form.querySelector('[name="imap_password"]').required = !account;

        // Profiller hesaba bağlı kaydedildiğinden yeni hesapta liste boş kalır
        $('#mailIdentityInput').value = '';
        applyIdentities(account ? (account.identities || []) : []);

        if (accountsUI.signature) accountsUI.signature.setContents([]);

        if (account) {
            form.querySelector('[name="id"]').value = account.id;
            form.querySelector('[name="name"]').value = account.name || '';
            form.querySelector('[name="email"]').value = account.email || '';
            form.querySelector('[name="from_name"]').value = account.from_name || '';
            form.querySelector('[name="color"]').value = account.color || '#2563eb';
            form.querySelector('[name="sync_days"]').value = account.sync_days || 90;
            form.querySelector('[name="is_shared"]').checked = !!account.is_shared;

            form.querySelector('[name="imap_host"]').value = account.imap.host || '';
            form.querySelector('[name="imap_port"]').value = account.imap.port || 993;
            form.querySelector('[name="imap_encryption"]').value = account.imap.encryption || 'ssl';
            form.querySelector('[name="imap_username"]').value = account.imap.username || '';
            form.querySelector('[name="imap_validate_cert"]').checked = account.imap.validate_cert !== false;

            form.querySelector('[name="smtp_host"]').value = account.smtp.host || '';
            form.querySelector('[name="smtp_port"]').value = account.smtp.port || 587;
            form.querySelector('[name="smtp_encryption"]').value = account.smtp.encryption || 'tls';
            form.querySelector('[name="smtp_username"]').value = account.smtp.username || '';
            form.querySelector('[name="smtp_validate_cert"]').checked = account.smtp.validate_cert !== false;

            if (accountsUI.signature && account.signature_html) {
                accountsUI.signature.clipboard.dangerouslyPasteHTML(account.signature_html);
            }
        } else {
            form.querySelector('[name="color"]').value = AVATAR_COLORS[Math.floor(Math.random() * AVATAR_COLORS.length)];
            form.querySelector('[name="imap_validate_cert"]').checked = true;
            form.querySelector('[name="smtp_validate_cert"]').checked = true;
        }
    }

    /* Gönderici profilleri — hesap formundaki bölüm ────────────────
       Profiller hesap formundan bağımsız kaydedilir: her satır anında
       sunucuya yazılır, "Kaydet" düğmesini beklemez. Böylece bağlantı
       ayarları yarım kalsa da profil listesi tutarlı kalır. */

    function identityUrl(path) {
        return CFG.routes.accounts + path;
    }

    function renderIdentities() {
        const wrap = $('#mailIdentityList');
        if (!wrap) return;

        // Panel liste içindeyse önce evine alınır: aşağıdaki innerHTML yazımı
        // düğümü — dolayısıyla Quill örneğini — yok ederdi.
        detachSignaturePanel();

        const list = accountsUI.identities || [];

        if (!accountsUI.editingId) {
            wrap.innerHTML = '';
            return;
        }

        if (!list.length) {
            wrap.innerHTML = '<div class="mail-ident-empty">'
                + 'Henüz profil yok — iletiler hesabın kendi gönderen adıyla gider.</div>';
            return;
        }

        wrap.innerHTML = list.map((identity) => {
            if (accountsUI.renamingId === identity.id) {
                return '<div class="mail-ident-row">'
                    + '<input type="text" class="form-control" maxlength="120" value="' + esc(identity.name) + '" '
                    + 'data-ident-input="' + identity.id + '">'
                    + '<button type="button" class="mail-icon-btn" data-ident-save="' + identity.id + '" title="Kaydet">'
                    + '<i class="fas fa-check"></i></button>'
                    + '<button type="button" class="mail-icon-btn" data-ident-cancel="1" title="Vazgeç">'
                    + '<i class="fas fa-xmark"></i></button>'
                    + '</div>';
            }

            const isSigning = accountsUI.signingId === identity.id;

            return '<div class="mail-ident-row' + (isSigning ? ' is-signing' : '') + '">'
                + '<i class="fas fa-at"></i>'
                + '<div class="mail-ident-name"><span>' + esc(identity.name) + '</span>'
                + (identity.is_default ? '<span class="mail-badge def">Varsayılan</span>' : '')
                + (identity.has_signature ? '<span class="mail-ident-tag">kendi imzası</span>' : '')
                + '</div>'
                + '<button type="button" class="mail-icon-btn mail-ident-sign-btn'
                    + (identity.has_signature ? ' is-on' : '') + (isSigning ? ' is-open' : '') + '" '
                    + 'data-ident-sign="' + identity.id + '" title="' + esc(signButtonTitle(identity)) + '">'
                + '<i class="fas fa-signature"></i></button>'
                + (identity.is_default ? ''
                    : '<button type="button" class="mail-icon-btn" data-ident-default="' + identity.id + '" '
                        + 'title="Yazma penceresinde varsayılan olsun"><i class="fas fa-thumbtack"></i></button>')
                + '<button type="button" class="mail-icon-btn" data-ident-rename="' + identity.id + '" title="Adı değiştir">'
                + '<i class="fas fa-pen"></i></button>'
                + '<button type="button" class="mail-icon-btn" data-ident-delete="' + identity.id + '" title="Kaldır" '
                + 'style="color:#dc2626"><i class="fas fa-trash-can"></i></button>'
                + '</div>'
                // Panel satırın ALTINA açılır; yuva boş gelir, editör buraya taşınır
                + (isSigning ? '<div class="mail-ident-sign-slot" data-ident-slot="' + identity.id + '"></div>' : '');
        }).join('');

        placeSignaturePanel();
    }

    function signButtonTitle(identity) {
        if (identity.has_signature) return 'Bu profilin kendi imzası var — düzenle';
        if (identity.inherits_signature === false) return 'Bu profilde imza kullanılmıyor — düzenle';
        return 'İmza hesaptan devralınıyor — profile özel imza tanımla';
    }

    /* Profil imzası paneli ─────────────────────────────────────────
       Editör listenin dışında yaşar, açılan satırın altındaki yuvaya
       taşınır: liste her işlemde yeniden çizildiği için panel listenin
       içinde dursaydı Quill örneği her seferinde çöpe giderdi. */

    function detachSignaturePanel() {
        const panel = $('#mailIdentitySignature');
        if (!panel) return;

        if (!accountsUI.signHome) accountsUI.signHome = panel.parentNode;

        panel.style.display = 'none';
        if (panel.parentNode !== accountsUI.signHome) accountsUI.signHome.appendChild(panel);
    }

    function placeSignaturePanel() {
        const panel = $('#mailIdentitySignature');
        const list = $('#mailIdentityList');
        if (!panel || !list || !accountsUI.signingId) return;

        const slot = list.querySelector('[data-ident-slot="' + accountsUI.signingId + '"]');

        // Yuva yoksa profil bu arada silinmiş demektir; panel evinde kalır
        if (!slot) { accountsUI.signingId = null; return; }

        slot.appendChild(panel);
        panel.style.display = '';
    }

    function closeIdentitySignature() {
        accountsUI.signingId = null;
        accountsUI.signDraft = null;
        renderIdentities();
    }

    /** Editörün gövdesi; Quill'in boş belge çıktısı imza sayılmaz. */
    function identitySignatureHtml() {
        const editor = accountsUI.identitySignature;
        if (!editor) return '';
        const html = editor.root.innerHTML;

        return html === '<p><br></p>' ? '' : html;
    }

    function setIdentitySignatureHtml(html) {
        const editor = accountsUI.identitySignature;
        if (!editor) return;
        editor.setContents([]);
        if (html) editor.clipboard.dangerouslyPasteHTML(html);
    }

    /**
     * Devralma durumunu görünür kılar.
     *
     * "Hesabın imzasını kullan" işaretliyken editör hesabın imzasını SOLUK
     * önizleme olarak gösterir ve yazmaya kapanır: kullanıcı iletinin altında
     * ne göreceğini görür, ama kaydedene kadar profile hiçbir şey yazılmaz.
     */
    function syncIdentitySignatureState() {
        const inherit = $('#mailIdentitySignInherit').checked;
        const badge = $('#mailIdentitySignState');
        const note = $('#mailIdentitySignNote');

        $('#mailIdentitySignRte').classList.toggle('is-inherited', inherit);
        if (accountsUI.identitySignature) accountsUI.identitySignature.enable(!inherit);

        if (inherit) {
            badge.textContent = 'Hesabın imzası';
            badge.className = 'mail-badge def';
            note.textContent = accountsUI.signAccountHtml
                ? 'Bu profil hesabın imzasını devralıyor — hesap imzası değişirse burası da değişir.'
                : 'Hesabın da imzası yok; bu profille yazılan iletiler imzasız gider.';
        } else {
            badge.textContent = 'Profile özel';
            badge.className = 'mail-badge shared';
            note.textContent = 'Boş bırakırsanız bu profille yazılan iletilere imza eklenmez.';
        }
    }

    async function openIdentitySignature(id) {
        if (accountsUI.signingId === id) { closeIdentitySignature(); return; }

        ensureIdentitySignatureEditor();

        // Aynı anda tek panel; satır içi yeniden adlandırma da kapanır
        accountsUI.signingId = id;
        accountsUI.renamingId = null;
        accountsUI.signDraft = null;
        renderIdentities();

        const identity = (accountsUI.identities || []).find((i) => i.id === id);
        const panel = $('#mailIdentitySignature');
        $('#mailIdentitySignName').textContent = identity ? identity.name : 'Profil imzası';
        panel.classList.add('is-busy');
        $('#mailIdentitySignNote').textContent = 'İmza yükleniyor…';

        try {
            const detail = await api(identityUrl('/identities/' + id));
            if (accountsUI.signingId !== id) return;   // panel bu arada kapandı

            accountsUI.signAccountHtml = detail.account_signature_html || '';
            $('#mailIdentitySignInherit').checked = !!detail.inherits_signature;
            setIdentitySignatureHtml(detail.inherits_signature
                ? accountsUI.signAccountHtml
                : (detail.signature_html || ''));
            syncIdentitySignatureState();
        } catch (error) {
            toast('error', 'İmza açılamadı', error.message);
            closeIdentitySignature();
        } finally {
            panel.classList.remove('is-busy');
        }
    }

    async function saveIdentitySignature() {
        const id = accountsUI.signingId;
        const identity = (accountsUI.identities || []).find((i) => i.id === id);
        if (!identity) return;

        // null → hesabın imzası devralınsın, boş dize → bu profilde imza olmasın.
        // İkisi sunucuda farklı anlam taşıdığı için burada da ayrı gönderilir.
        const value = $('#mailIdentitySignInherit').checked ? null : identitySignatureHtml();

        await identityRequest(
            identityUrl('/identities/' + id),
            { method: 'PUT', body: { name: identity.name, signature_html: value } },
            'İmza kaydedildi'
        );
    }

    /**
     * Sunucudan dönen profil listesini hem panele hem CFG.accounts'a işler —
     * yazma penceresi sayfa yenilenmeden güncel listeyi görsün diye.
     */
    function applyIdentities(list) {
        accountsUI.identities = list || [];
        accountsUI.renamingId = null;
        // Sunucudan taze liste geldi: açık imza paneli kapanır — kaydedilen
        // değer artık listede, açık kalsaydı bayat gövde gösterirdi.
        accountsUI.signingId = null;
        accountsUI.signDraft = null;
        renderIdentities();

        const account = accountById(accountsUI.editingId);
        if (account) account.identities = accountsUI.identities;
    }

    async function identityRequest(url, options, successTitle) {
        try {
            const result = await api(url, options);
            applyIdentities(result.identities);
            if (result.message) toast('success', successTitle, result.message);
            return true;
        } catch (error) {
            toast('error', 'İşlem tamamlanamadı', error.message);
            return false;
        }
    }

    async function addIdentity() {
        const input = $('#mailIdentityInput');
        const name = input.value.trim();
        if (!name || !accountsUI.editingId) return;

        const ok = await identityRequest(
            identityUrl('/' + accountsUI.editingId + '/identities'),
            { method: 'POST', body: { name: name } },
            'Profil eklendi'
        );

        if (ok) {
            input.value = '';
            input.focus();
        }
    }

    function accountFormData() {
        const form = $('#mailAccountForm');
        const data = new FormData(form);

        // Anahtarlar yalnızca işaretliyse gelir; sunucu boolean bekliyor
        ['is_shared', 'imap_validate_cert', 'smtp_validate_cert'].forEach((key) => {
            data.set(key, form.querySelector('[name="' + key + '"]').checked ? '1' : '0');
        });

        const signature = accountsUI.signature ? accountsUI.signature.root.innerHTML : '';
        data.set('signature_html', signature === '<p><br></p>' ? '' : signature);

        return data;
    }

    /* Bağlantı testi ─────────────────────────────────────────────
       Sonuç şeridi modal gövdesinin dibinde sticky duruyor (bkz. mail.css);
       kullanıcı formun neresinde olursa olsun görüyor. Burada ayrıca üç şey
       yapılıyor: düğme anında yükleniyor durumuna geçiyor (test birkaç saniye
       sürebilir, şikâyetin yarısı bu sessizlikti), sonuç görüş alanına
       kaydırılıyor ve hata durumunda ek olarak bildirim veriliyor. */

    /** Test sonucuna dokunan alanlar — değişirse eski sonuç yanıltıcı olur. */
    const TEST_FIELDS = ['email',
        'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password', 'imap_validate_cert',
        'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password', 'smtp_validate_cert'];

    function testResultLine(label, part) {
        if (!part) return '';
        return '<div class="mail-test-line ' + (part.ok ? 'ok' : 'fail') + '">'
            + '<i class="fas fa-circle-' + (part.ok ? 'check' : 'xmark') + '"></i>'
            + '<b>' + label + '</b><span>' + esc(part.message) + '</span></div>';
    }

    function showTestResult(html, state, scroll) {
        const box = $('#mailTestResult');
        if (!box) return;

        box.className = 'mail-test-result is-visible' + (state ? ' ' + state : '');
        box.innerHTML = html;

        if (scroll === false) return;

        // Sticky şerit çoğu zaman zaten görünür; yine de kullanıcı tam dipteyken
        // ya da tarayıcı sticky'yi uygulayamıyorsa güvence olsun.
        const motion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
        box.scrollIntoView({ block: 'nearest', behavior: motion ? 'auto' : 'smooth' });
    }

    function clearTestResult() {
        const box = $('#mailTestResult');
        if (box) { box.className = 'mail-test-result'; box.innerHTML = ''; }
    }

    /** Ayarlar değiştiğinde eski "✓ Ayarlar doğru" sonucu yalan söylemesin. */
    function staleTestResult() {
        const box = $('#mailTestResult');
        if (!box || !box.classList.contains('is-visible')) return;
        if (box.classList.contains('is-stale') || box.classList.contains('is-busy')) return;

        showTestResult('<div class="mail-test-line warn"><i class="fas fa-triangle-exclamation"></i>'
            + '<b>Uyarı</b><span>Ayarlar değişti — sonuç artık geçerli değil, yeniden test edin.</span></div>',
        'is-stale', false);
    }

    async function testAccount() {
        const button = $('#mailAccountTest');
        const idle = '<i class="fas fa-plug-circle-check"></i><span>Bağlantıyı Test Et</span>';

        button.disabled = true;
        button.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>Test ediliyor…</span>';

        showTestResult('<div class="mail-test-line"><i class="fas fa-spinner fa-spin"></i>'
            + '<b>Bekleyin</b><span>Gelen ve giden sunucuya bağlanılıyor…</span></div>', 'is-busy');

        try {
            const result = await api(CFG.routes.accountTest, { method: 'POST', body: accountFormData() });

            showTestResult(''
                + testResultLine('IMAP', result.imap)
                + testResultLine('SMTP', result.smtp)
                + (result.ok ? '<div class="mail-test-line ok"><i class="fas fa-circle-check"></i>'
                    + '<b></b><span>Ayarlar doğru — hesabı kaydedebilirsiniz.</span></div>' : ''),
            result.ok ? 'is-ok' : 'is-fail');

            // Başarıda bildirim gereksiz; şerit zaten görünür. Hata sessiz kalmasın:
            // yanlış parola / sertifika / port kullanıcının kesinlikle görmesi gereken şey.
            if (!result.ok) {
                const failed = [result.imap, result.smtp].find((p) => p && !p.ok);
                toast('error', 'Bağlantı kurulamadı', failed ? failed.message : 'Ayarları gözden geçirin.');
            }
        } catch (error) {
            showTestResult('<div class="mail-test-line fail"><i class="fas fa-circle-xmark"></i>'
                + '<b>Hata</b><span>' + esc(error.message) + '</span></div>', 'is-fail');
            toast('error', 'Bağlantı test edilemedi', error.message);
        } finally {
            button.disabled = false;
            button.innerHTML = idle;
        }
    }

    async function saveAccount() {
        const form = $('#mailAccountForm');
        if (!form.reportValidity()) return;

        const button = $('#mailAccountSave');
        button.disabled = true;
        button.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>Kaydediliyor…</span>';

        const data = accountFormData();
        let url = CFG.routes.accounts;

        if (accountsUI.editingId) {
            url += '/' + accountsUI.editingId;
            data.set('_method', 'PUT');
        }

        try {
            const result = await api(url, { method: 'POST', body: data });
            toast('success', result.message);
            if (result.warning) toast('error', 'Uyarı', result.warning);
            window.location.reload();
        } catch (error) {
            toast('error', 'Kaydedilemedi', error.message);
            button.disabled = false;
            button.innerHTML = '<i class="fas fa-check"></i><span>Kaydet</span>';
        }
    }

    /* ── 8. Olay bağlantıları ───────────────────────────────── */

    // Klasör seçimi ve grup açma/kapama
    document.addEventListener('click', (event) => {
        const groupHead = event.target.closest('[data-toggle-group]');
        if (groupHead) {
            groupHead.closest('.mail-group').classList.toggle('is-collapsed');
            return;
        }

        const folder = event.target.closest('.mail-folder[data-scope]');
        if (folder) { selectScope(folder); return; }
    });

    // Liste etkileşimleri
    if (el.listScroll) {
        el.listScroll.addEventListener('click', (event) => {
            const loadMore = event.target.closest('#mailLoadMore');
            if (loadMore) { state.page += 1; loadList(true); return; }

            const check = event.target.closest('.mail-check');
            if (check) {
                event.stopPropagation();
                toggleSelect(Number(check.dataset.check));
                return;
            }

            const rowAction = event.target.closest('[data-row-act]');
            if (rowAction) {
                event.stopPropagation();
                const id = Number(rowAction.closest('.mail-item').dataset.id);
                const item = state.items.find((m) => m.id === id);
                if (!item) return;

                if (rowAction.dataset.rowAct === 'flag') {
                    runAction(CFG.routes.flag, { ids: [id], flagged: !item.is_flagged },
                        { patch: { is_flagged: !item.is_flagged } });
                } else if (rowAction.dataset.rowAct === 'read') {
                    runAction(CFG.routes.read, { ids: [id], seen: !item.is_seen },
                        { patch: { is_seen: !item.is_seen } });
                } else if (rowAction.dataset.rowAct === 'trash') {
                    runAction(CFG.routes.trash, { ids: [id] }, { remove: true, toast: 'Silindi' });
                }
                return;
            }

            const row = event.target.closest('.mail-item');
            if (!row) return;

            const id = Number(row.dataset.id);

            // Ctrl/Shift ile çoklu seçim
            if (event.ctrlKey || event.metaKey || event.shiftKey || state.selected.size) {
                toggleSelect(id);
                return;
            }
            openMessage(id);
        });

        // Sağ tık → hızlı menü
        el.listScroll.addEventListener('contextmenu', (event) => {
            const row = event.target.closest('.mail-item');
            if (!row) return;
            event.preventDefault();

            const id = Number(row.dataset.id);
            if (!state.selected.size) { state.activeId = id; }
            openMoreMenu(row);
        });

        // Sona yaklaşınca sonraki sayfa
        el.listScroll.addEventListener('scroll', () => {
            if (state.loading || !state.hasMore) return;
            const nearEnd = el.listScroll.scrollTop + el.listScroll.clientHeight
                >= el.listScroll.scrollHeight - 220;
            if (nearEnd) { state.page += 1; loadList(true); }
        });
    }

    // Hızlı filtre şeridi
    $$('.mail-chip').forEach((chip) => {
        chip.addEventListener('click', () => {
            $$('.mail-chip').forEach((c) => c.classList.remove('is-on'));
            chip.classList.add('is-on');
            state.filter = chip.dataset.filter || '';
            loadList(false);
        });
    });

    // Okuma bölmesi düğmeleri
    if (el.read) {
        el.read.addEventListener('click', (event) => {
            if (event.target.closest('#mailShowImages')) {
                openMessage(state.activeId, { showImages: true });
                return;
            }

            const action = event.target.closest('[data-read-act]');
            if (!action || !state.active) return;

            switch (action.dataset.readAct) {
                case 'reply': openCompose('reply', state.active); break;
                case 'replyAll': openCompose('replyAll', state.active); break;
                case 'forward': openCompose('forward', state.active); break;
                case 'editDraft': openCompose('draft', state.active); break;
                case 'flag':
                    runAction(CFG.routes.flag, { ids: [state.active.id], flagged: !state.active.is_flagged },
                        { patch: { is_flagged: !state.active.is_flagged } });
                    state.active.is_flagged = !state.active.is_flagged;
                    action.classList.toggle('is-on', state.active.is_flagged);
                    action.innerHTML = '<i class="fa' + (state.active.is_flagged ? 's' : 'r') + ' fa-star"></i>';
                    break;
                case 'paper':
                    state.paper = !state.paper;
                    try { localStorage.setItem('mailPaper', state.paper ? '1' : '0'); } catch (e) { /* yoksay */ }
                    renderReader(state.active);
                    break;
                case 'print':
                    window.open(CFG.routes.print + '/' + state.active.id + '/print', '_blank');
                    break;
                case 'trash':
                    runAction(CFG.routes.trash, { ids: [state.active.id] }, { remove: true, toast: 'Silindi' });
                    break;
                case 'close':
                    app.classList.remove('is-reading');
                    break;
                case 'crmOpen': {
                    // Düğme formun yerini alır: şerit yüksekliği bir kez artar
                    const strip = action.closest('.mail-crm');
                    const form = strip.querySelector('.mail-crm-form');
                    action.hidden = true;
                    form.hidden = false;
                    strip.classList.add('is-open');
                    crmFormFields(form);
                    strip.querySelector('input[name="first_name"]').focus();
                    break;
                }
                case 'crmCancel': {
                    const strip = action.closest('.mail-crm');
                    strip.classList.remove('is-open');
                    strip.querySelector('.mail-crm-form').hidden = true;
                    const open = strip.querySelector('[data-read-act="crmOpen"]');
                    if (open) open.hidden = false;
                    break;
                }
                case 'linkOpen': {
                    // Seçici şeridin altına iner; ilk açılışta son kayıtlar gelir
                    const strip = action.closest('.mail-crm');
                    const picker = strip.querySelector('.mail-pick');
                    if (!picker) break;

                    picker.hidden = !picker.hidden;
                    strip.classList.toggle('is-open', !picker.hidden);
                    action.classList.toggle('is-on', !picker.hidden);

                    if (!picker.hidden) {
                        const first = picker.querySelector('.mail-pick-tab.on');
                        linkType = first ? first.dataset.type : 'customer';
                        const input = picker.querySelector('#mailLinkQuery');
                        if (input) { input.value = ''; input.focus(); }
                        linkSearch();
                    }
                    break;
                }
                case 'linkType': {
                    const picker = action.closest('.mail-pick');
                    picker.querySelectorAll('.mail-pick-tab').forEach((t) => t.classList.remove('on'));
                    action.classList.add('on');
                    linkType = action.dataset.type;
                    linkSearch();
                    break;
                }
                case 'linkPick':
                    linkPick(action.dataset.type, Number(action.dataset.id));
                    break;
                case 'linkRemove':
                    linkRemove(Number(action.dataset.link));
                    break;
            }
        });

        // CRM formu okuma bölmesinin içinde doğar; olay burada yakalanır
        el.read.addEventListener('submit', (event) => {
            const form = event.target.closest('#mailCrmForm');
            if (!form) return;
            event.preventDefault();
            submitCrmForm(form);
        });

        // İlişki seçicisindeki arama kutusu da sonradan doğar
        el.read.addEventListener('input', (event) => {
            if (event.target.id === 'mailLinkQuery') linkSearchDebounced();
        });

        // Kayıt tipi değişince forma ait alanlar tazelenir
        el.read.addEventListener('change', (event) => {
            const select = event.target.closest('#mailCrmForm select[name="type"]');
            if (select) crmFormFields(select.form);
        });

        // Seçicide Enter formu göndermeye çalışmasın
        el.read.addEventListener('keydown', (event) => {
            if (event.target.id === 'mailLinkQuery' && event.key === 'Enter') {
                event.preventDefault();
                linkSearch();
            }
        });
    }

    // Üst araç çubuğu
    $$('.mail-bar [data-act]').forEach((button) => {
        button.addEventListener('click', () => {
            switch (button.dataset.act) {
                case 'trash':
                    return runAction(CFG.routes.trash, {}, { remove: true, toast: 'Silindi' });
                case 'unread':
                    return runAction(CFG.routes.read, { seen: false }, { patch: { is_seen: false } });
                case 'archive':
                    return runAction(CFG.routes.archive, {}, { remove: true, toast: 'Arşivlendi' });
                case 'spam':
                    return markSpam();
                case 'flag':
                    return runAction(CFG.routes.flag, { flagged: true }, { patch: { is_flagged: true } });
                case 'move':
                    return openMoveMenu(button);
                case 'more':
                    return openMoreMenu(button);
            }
        });
    });

    const refreshBtn = $('#mailRefresh');
    if (refreshBtn) refreshBtn.addEventListener('click', () => runSync(false));

    // Tümünü seç
    const selectAllBtn = $('#mailSelectAll');
    if (selectAllBtn) {
        selectAllBtn.addEventListener('click', () => {
            if (state.selected.size === state.items.length) state.selected.clear();
            else state.items.forEach((m) => state.selected.add(m.id));

            $$('.mail-item').forEach((n) => {
                n.classList.toggle('is-checked', state.selected.has(Number(n.dataset.id)));
            });
            selectAllBtn.classList.toggle('is-on', state.selected.size > 0);
            syncToolbar();
        });
    }

    // Arama
    if (el.search) {
        const runSearch = debounce(() => {
            state.search = el.search.value.trim();
            el.searchWrap.classList.toggle('has-value', state.search.length > 0);
            loadList(false);
        }, 320);

        el.search.addEventListener('input', runSearch);
        el.search.addEventListener('keydown', (event) => {
            if (event.key === 'Escape') { el.search.value = ''; runSearch(); el.search.blur(); }
        });

        $('#mailSearchClear').addEventListener('click', () => {
            el.search.value = '';
            state.search = '';
            el.searchWrap.classList.remove('has-value');
            loadList(false);
        });
    }

    // Yeni klasör
    const newFolderBtn = $('#mailNewFolderBtn');
    if (newFolderBtn) {
        newFolderBtn.addEventListener('click', () => {
            const accounts = CFG.accounts || [];
            if (!accounts.length) return;

            // Hangi hesapta açılacağı belliyse doğrudan sor; değilse önce hesabı seçtir
            if (state.scope.accountId || accounts.length === 1) {
                createFolder(state.scope.accountId || accounts[0].id);
                return;
            }

            openMenu(newFolderBtn, '<div class="mail-menu-label">Hangi hesapta?</div>'
                + accounts.map((a) => '<button type="button" class="mail-menu-item" data-account="' + a.id + '">'
                    + '<span class="mail-acct-dot" style="background:' + esc(a.color) + '"></span>'
                    + esc(a.name) + '</button>').join(''),
                (data) => createFolder(Number(data.account)));
        });
    }

    // Klasöre sağ tık → yönetim menüsü
    document.addEventListener('contextmenu', (event) => {
        const node = event.target.closest('.mail-folder[data-folder-id]');
        if (!node) return;
        event.preventDefault();

        const id = Number(node.dataset.folderId);
        const accountId = Number(node.dataset.accountId);
        const type = node.dataset.folderType;
        const label = node.dataset.label;
        const isCustom = type === 'custom';
        const pinned = node.dataset.pinned === '1';

        const html = '<div class="mail-menu-label">' + esc(label) + '</div>'
            + '<button type="button" class="mail-menu-item" data-folder-act="pin">'
            + '<i class="fas fa-thumbtack"></i>'
            + (pinned ? 'Sabitlemeyi kaldır' : 'Sık kullanılanlara sabitle') + '</button>'
            + '<button type="button" class="mail-menu-item" data-folder-act="child">'
            + '<i class="fas fa-folder-plus"></i>Yeni alt klasör</button>'
            + (isCustom
                ? '<button type="button" class="mail-menu-item" data-folder-act="rename">'
                  + '<i class="fas fa-pen"></i>Yeniden adlandır</button>'
                : '')
            + (type === 'trash' || type === 'junk'
                ? '<button type="button" class="mail-menu-item is-danger" data-folder-act="empty">'
                  + '<i class="fas fa-broom"></i>Klasörü boşalt</button>'
                : '')
            + (isCustom
                ? '<div class="mail-menu-sep"></div>'
                  + '<button type="button" class="mail-menu-item is-danger" data-folder-act="delete">'
                  + '<i class="fas fa-trash-can"></i>Klasörü sil</button>'
                : '');

        openMenu(node, html, async (data) => {
            switch (data.folderAct) {
                case 'pin':
                    try {
                        const result = await api(CFG.routes.folders + '/' + id + '/pin', { method: 'POST', body: {} });
                        toast('success', result.message);
                        // Sık kullanılanlar şeridi sunucuda çiziliyor
                        window.location.reload();
                    } catch (error) {
                        toast('error', 'Sabitlenemedi', error.message);
                    }
                    return;

                case 'child':
                    return createFolder(accountId, id, label);

                case 'rename': {
                    const name = await promptAsk({
                        title: 'Klasörü Yeniden Adlandır',
                        value: label,
                        placeholder: 'Klasör adı',
                        confirmText: 'Kaydet',
                    });
                    if (!name || name === label) return;

                    try {
                        // _method sahtelemesi yalnızca form verisinde çalışır, JSON gövdede değil
                        const form = new FormData();
                        form.set('_method', 'PUT');
                        form.set('name', name);
                        await api(CFG.routes.folders + '/' + id, { method: 'POST', body: form });
                        toast('success', 'Klasör yeniden adlandırıldı');
                        window.location.reload();
                    } catch (error) {
                        toast('error', 'Yeniden adlandırılamadı', error.message);
                    }
                    return;
                }

                case 'empty': {
                    const ok = await confirmAsk('Klasör boşaltılsın mı?',
                        '“' + label + '” içindeki tüm iletiler sunucudan kalıcı olarak silinecek.', 'Boşalt');
                    if (!ok) return;

                    try {
                        const result = await api(CFG.routes.folderEmpty, { method: 'POST', body: { folder_id: id } });
                        applyCounts(result.folders);
                        if (state.scope.folderId === id) loadList(false);
                        toast('success', 'Klasör boşaltıldı');
                    } catch (error) {
                        toast('error', 'Boşaltılamadı', error.message);
                    }
                    return;
                }

                case 'delete': {
                    const ok = await confirmAsk('Klasör silinsin mi?',
                        '“' + label + '” klasörü ve içindeki iletiler e-posta sunucunuzdan kaldırılacak. '
                        + 'Bu işlem geri alınamaz.', 'Sil');
                    if (!ok) return;

                    try {
                        const form = new FormData();
                        form.set('_method', 'DELETE');
                        await api(CFG.routes.folders + '/' + id, { method: 'POST', body: form });
                        toast('success', 'Klasör silindi');
                        window.location.reload();
                    } catch (error) {
                        toast('error', 'Silinemedi', error.message);
                    }
                }
            }
        });
    });

    async function createFolder(accountId, parentId, parentLabel) {
        const account = accountById(accountId);

        const name = await promptAsk({
            title: parentId ? 'Yeni Alt Klasör' : 'Yeni Klasör',
            text: parentId
                ? '“' + parentLabel + '” altında oluşturulacak.'
                : (account ? account.email + ' hesabında oluşturulacak.' : ''),
            placeholder: 'Örn. Grup Rezervasyonları',
            confirmText: 'Oluştur',
        });
        if (!name) return;

        try {
            await api(CFG.routes.folderStore, {
                method: 'POST',
                body: { account_id: accountId, name: name, parent_id: parentId || null },
            });
            toast('success', 'Klasör oluşturuldu', name);
            window.location.reload();
        } catch (error) {
            toast('error', 'Klasör oluşturulamadı', error.message);
        }
    }

    /* Yazma penceresi olayları */

    ['#mailComposeBtn', '#mailOnboardBtn'].forEach((selector) => {
        const button = $(selector);
        if (!button) return;
        button.addEventListener('click', () => {
            if (selector === '#mailOnboardBtn') openAccounts();
            else openCompose('new');
        });
    });

    if (compose.node) {
        initComposeDrag();

        compose.node.addEventListener('click', (event) => {
            // ── Rehber ──────────────────────────────────────────
            if (event.target.closest('#mailDirBtn')) { toggleDirectory(); return; }
            if (event.target.closest('#mailDirClose')) { toggleDirectory(false); return; }

            const dirTarget = event.target.closest('[data-dir-target]');
            if (dirTarget) {
                dir.target = dirTarget.dataset.dirTarget;
                $$('#mailDirTarget button').forEach((b) => b.classList.toggle('is-on', b === dirTarget));
                return;
            }

            const dirKind = event.target.closest('[data-dir-kind]');
            if (dirKind) {
                dir.kind = dirKind.dataset.dirKind || null;
                loadDirectory();
                return;
            }

            const dirItem = event.target.closest('[data-dir-index]');
            if (dirItem) {
                pickFromDirectory(Number(dirItem.dataset.dirIndex));
                return;
            }

            const control = event.target.closest('[data-compose]');
            if (control) {
                const mode = control.dataset.compose;
                if (mode === 'close') closeCompose(false);
                if (mode === 'min') setComposeMode(compose.mode === 'min' ? 'float' : 'min');
                if (mode === 'max') setComposeMode(compose.mode === 'dock' ? 'float' : 'dock');
                return;
            }

            const toggleField = event.target.closest('[data-toggle-field]');
            if (toggleField) {
                const field = toggleField.dataset.toggleField;
                const row = $('.mail-field[data-row="' + field + '"]');
                const visible = row.style.display !== 'none';
                row.style.display = visible ? 'none' : '';
                toggleField.classList.toggle('is-on', !visible);
                if (!visible) row.querySelector('input').focus();
                return;
            }

            const remove = event.target.closest('[data-remove]');
            if (remove) {
                const parts = remove.dataset.remove.split(':');
                compose.recipients[parts[0]].splice(Number(parts[1]), 1);
                compose.dirty = true;
                renderRecipients();
                scheduleAutosave(AUTOSAVE_BLUR);
                return;
            }

            const removeFile = event.target.closest('[data-remove-file]');
            if (removeFile) {
                compose.files.splice(Number(removeFile.dataset.removeFile), 1);
                renderAttachList();
                return;
            }

            if (event.target.closest('[data-remove-kept]')) {
                compose.keepAttachmentIds = [];
                renderAttachList();
                return;
            }

            const suggestItem = event.target.closest('.mail-suggest-item');
            if (suggestItem) {
                const box = suggestItem.closest('.mail-suggest');
                const person = suggestState.items[Number(suggestItem.dataset.index)];
                const field = box.closest('.mail-field').dataset.row;
                if (person) {
                    compose.recipients[field].push({ name: person.name, email: person.email });
                    renderRecipients();
                }
                box.classList.remove('is-open');
                box.closest('.mail-field').querySelector('input').value = '';
                return;
            }
        });

        // Alıcı girdisi
        compose.node.addEventListener('keydown', (event) => {
            const input = event.target;
            if (!input.matches('.mail-chips input')) return;

            const field = input.closest('.mail-chips').dataset.field;
            const box = input.closest('.mail-field').querySelector('.mail-suggest');

            if (box && box.classList.contains('is-open')
                && ['ArrowDown', 'ArrowUp', 'Enter', 'Tab'].indexOf(event.key) !== -1) {
                const nodes = $$('.mail-suggest-item', box);

                if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
                    event.preventDefault();
                    suggestState.cursor += event.key === 'ArrowDown' ? 1 : -1;
                    if (suggestState.cursor < 0) suggestState.cursor = nodes.length - 1;
                    if (suggestState.cursor >= nodes.length) suggestState.cursor = 0;
                    nodes.forEach((n, i) => n.classList.toggle('is-cursor', i === suggestState.cursor));
                    return;
                }

                if (suggestState.cursor >= 0) {
                    event.preventDefault();
                    nodes[suggestState.cursor].click();
                    return;
                }
            }

            if ([',', ';', 'Enter', 'Tab'].indexOf(event.key) !== -1 && input.value.trim()) {
                event.preventDefault();
                addRecipient(field, input.value);
                input.value = '';
                if (box) box.classList.remove('is-open');
                return;
            }

            if (event.key === 'Backspace' && !input.value && compose.recipients[field].length) {
                compose.recipients[field].pop();
                renderRecipients();
            }
        });

        compose.node.addEventListener('input', (event) => {
            const input = event.target;

            if (input === $('#mailDirSearch')) { searchDirectory(); return; }
            if (!input.matches('.mail-chips input')) return;

            const field = input.closest('.mail-chips').dataset.field;
            const box = input.closest('.mail-field').querySelector('.mail-suggest');
            if (box) searchContacts(field, input.value.trim(), box);
        });

        compose.node.addEventListener('blur', (event) => {
            const input = event.target;
            if (!input.matches || !input.matches('.mail-chips input')) return;

            const field = input.closest('.mail-chips').dataset.field;
            setTimeout(() => {
                if (input.value.trim()) { addRecipient(field, input.value); input.value = ''; }
                const box = input.closest('.mail-field').querySelector('.mail-suggest');
                if (box) box.classList.remove('is-open');
            }, 180);
        }, true);

        // Ekler
        $('#mailAttachBtn').addEventListener('click', () => $('#mailFileInput').click());
        $('#mailFileInput').addEventListener('change', (event) => {
            addFiles(event.target.files);
            event.target.value = '';
        });

        ['dragenter', 'dragover'].forEach((type) => {
            compose.node.addEventListener(type, (event) => {
                event.preventDefault();
                compose.node.classList.add('is-dropping');
            });
        });
        ['dragleave', 'drop'].forEach((type) => {
            compose.node.addEventListener(type, (event) => {
                event.preventDefault();
                if (type === 'dragleave' && compose.node.contains(event.relatedTarget)) return;
                compose.node.classList.remove('is-dropping');
                if (type === 'drop' && event.dataTransfer && event.dataTransfer.files.length) {
                    addFiles(event.dataTransfer.files);
                }
            });
        });

        // Önem derecesi
        $('#mailPriorityBtn').addEventListener('click', (event) => {
            openMenu(event.currentTarget, ''
                + '<button type="button" class="mail-menu-item" data-priority="1"><i class="fas fa-angles-up" style="color:#dc2626"></i>Yüksek</button>'
                + '<button type="button" class="mail-menu-item" data-priority="3"><i class="fas fa-minus"></i>Normal</button>'
                + '<button type="button" class="mail-menu-item" data-priority="5"><i class="fas fa-angles-down" style="color:#0891b2"></i>Düşük</button>',
                (data) => {
                    compose.priority = Number(data.priority);
                    const badge = $('#mailPriorityBadge');
                    badge.style.display = compose.priority === 3 ? 'none' : '';
                    badge.textContent = compose.priority === 1 ? 'Yüksek Öncelik' : 'Düşük Öncelik';
                    badge.className = 'mail-badge ' + (compose.priority === 1 ? 'err' : 'def');
                });
        });

        // İmza — seçili gönderici profiline göre çözülür
        //
        // Gövdeye eklenen imza "Kimden" değiştiğinde KENDİLİĞİNDEN
        // güncellenmez, bu bilinçli bir tercih: Quill yapıştırılan HTML'i
        // Delta'ya çevirirken sarmalayıcı div ve sınıfları eler, yani imza
        // bloğunu gövdede güvenilir biçimde işaretlemenin yolu yok. Kör bir
        // arama-değiştirme kullanıcının elle yazdığı metni bozabilirdi.
        // Bunun yerine düğmeye tekrar basıldığında, bu düğümün eklediği
        // parça hâlâ olduğu gibi duruyorsa değiştirilir; kullanıcı imzaya
        // dokunmuşsa dokunulmaz.
        $('#mailSignatureBtn').addEventListener('click', async () => {
            const from = parseFromValue($('#mailFrom').value);
            const account = accountById(from.accountId);
            if (!account || !compose.editor) return;

            const identity = from.identityId
                ? (account.identities || []).find((i) => i.id === from.identityId)
                : null;

            // Profilin kendi imzası yoksa hesabınkine düşülür; profil imzayı
            // açıkça kaldırmışsa (ne kendi imzası var ne de devralıyor) yok.
            const available = identity
                ? (identity.has_signature || (identity.inherits_signature !== false && account.has_signature))
                : account.has_signature;

            if (!available) {
                toast('info', 'İmza tanımlı değil', identity
                    ? '“' + identity.name + '” profili için hesap ayarlarından imza ekleyebilirsiniz.'
                    : 'Hesap ayarlarından imza ekleyebilirsiniz.');
                return;
            }

            try {
                const html = identity
                    ? (await api(CFG.routes.accounts + '/identities/' + identity.id)).resolved_signature_html
                    : (await api(CFG.routes.accounts + '/' + account.id)).signature_html;

                if (!html) return;

                removeInsertedSignature();

                const index = compose.editor.getLength();
                compose.editor.clipboard.dangerouslyPasteHTML(index, '<p><br></p>' + html);
                compose.signatureMark = {
                    index: index,
                    length: compose.editor.getLength() - index,
                    text: compose.editor.getText(index, compose.editor.getLength() - index),
                };
                compose.dirty = true;
            } catch (error) {
                toast('error', 'İmza eklenemedi', error.message);
            }
        });

        $('#mailSendBtn').addEventListener('click', sendMail);
        $('#mailDraftBtn').addEventListener('click', saveDraft);
        $('#mailDiscardBtn').addEventListener('click', discardCompose);

        $('#mailSubject').addEventListener('input', () => {
            compose.dirty = true;
            scheduleAutosave();
        });

        // Hesap değişirse eski taslak yanlış kutuda kalırdı: kaldırılır ve
        // bir sonraki kayıt yeni hesabın Taslaklar klasöründe açılır.
        // Gövdeye eklenmiş imzaya bilerek dokunulmaz (bkz. İmza düğmesi).
        $('#mailFrom').addEventListener('change', () => {
            const next = parseFromValue($('#mailFrom').value).accountId;
            if (autosave.key && autosave.accountId && next !== autosave.accountId) {
                dropAutosavedDraft();
            }
            compose.dirty = true;
            scheduleAutosave(AUTOSAVE_BLUR);
        });

        // Alan bırakıldığında beklemeye gerek yok — kullanıcı o alanı bitirdi
        $('#mailSubject').addEventListener('blur', () => {
            if (compose.dirty) scheduleAutosave(AUTOSAVE_BLUR);
        });

        $$('.mail-chips input').forEach((input) => {
            input.addEventListener('blur', () => {
                if (compose.dirty) scheduleAutosave(AUTOSAVE_BLUR);
            });
        });
    }

    /* Hesap kipi olayları */

    ['#mailAccountsBtn', '#mailManageAccounts'].forEach((selector) => {
        const button = $(selector);
        if (button) button.addEventListener('click', openAccounts);
    });

    const addAccountBtn = $('#mailAddAccountBtn');
    if (addAccountBtn) addAccountBtn.addEventListener('click', () => showAccountForm(null));

    const backBtn = $('#mailAccountBack');
    if (backBtn) backBtn.addEventListener('click', openAccounts);

    const testBtn = $('#mailAccountTest');
    if (testBtn) testBtn.addEventListener('click', testAccount);

    // Bağlantı alanlarına dokunulduğu anda eski sonuç "bayat" durumuna geçer
    const accountFormEl = $('#mailAccountForm');
    if (accountFormEl) {
        ['input', 'change'].forEach((type) => {
            accountFormEl.addEventListener(type, (event) => {
                if (TEST_FIELDS.indexOf(event.target.name) !== -1) staleTestResult();
            });
        });
    }

    const saveBtn = $('#mailAccountSave');
    if (saveBtn) saveBtn.addEventListener('click', saveAccount);

    /* Gönderici profilleri */

    const identityAddBtn = $('#mailIdentityAdd');
    if (identityAddBtn) identityAddBtn.addEventListener('click', addIdentity);

    const identityInput = $('#mailIdentityInput');
    if (identityInput) {
        // Enter formu göndermesin: hesap formunun içindeyiz
        identityInput.addEventListener('keydown', (event) => {
            if (event.key !== 'Enter') return;
            event.preventDefault();
            addIdentity();
        });
    }

    const identityList = $('#mailIdentityList');
    if (identityList) {
        identityList.addEventListener('click', async (event) => {
            // İmza paneli listenin içine taşındığı için kendi düğmeleri de
            // buraya kadar kabarır; onları panelin kendi dinleyicisi karşılar.
            if (event.target.closest('#mailIdentitySignature')) return;

            const sign = event.target.closest('[data-ident-sign]');
            if (sign) {
                await openIdentitySignature(Number(sign.dataset.identSign));
                return;
            }

            const rename = event.target.closest('[data-ident-rename]');
            if (rename) {
                accountsUI.renamingId = Number(rename.dataset.identRename);
                renderIdentities();
                const field = identityList.querySelector('[data-ident-input]');
                if (field) { field.focus(); field.select(); }
                return;
            }

            if (event.target.closest('[data-ident-cancel]')) {
                accountsUI.renamingId = null;
                renderIdentities();
                return;
            }

            const save = event.target.closest('[data-ident-save]');
            if (save) {
                const id = Number(save.dataset.identSave);
                const name = identityList.querySelector('[data-ident-input]').value.trim();
                if (!name) return;
                await identityRequest(identityUrl('/identities/' + id),
                    { method: 'PUT', body: { name: name } }, 'Profil güncellendi');
                return;
            }

            const makeDefault = event.target.closest('[data-ident-default]');
            if (makeDefault) {
                await identityRequest(identityUrl('/identities/' + makeDefault.dataset.identDefault + '/default'),
                    { method: 'POST' }, 'Varsayılan profil');
                return;
            }

            const remove = event.target.closest('[data-ident-delete]');
            if (remove) {
                const ok = await confirmAsk('Profil kaldırılsın mı?',
                    'Yalnızca gönderen adı listeden çıkar; gönderilmiş iletiler etkilenmez.', 'Kaldır');
                if (!ok) return;

                await identityRequest(identityUrl('/identities/' + remove.dataset.identDelete),
                    { method: 'DELETE' }, 'Profil kaldırıldı');
            }
        });

        // Yeniden adlandırmada Enter kaydeder, Esc vazgeçer
        identityList.addEventListener('keydown', (event) => {
            if (event.target.closest('#mailIdentitySignature')) return;
            if (!event.target.matches('[data-ident-input]')) return;

            if (event.key === 'Enter') {
                event.preventDefault();
                identityList.querySelector('[data-ident-save]').click();
            } else if (event.key === 'Escape') {
                event.preventDefault();
                accountsUI.renamingId = null;
                renderIdentities();
            }
        });
    }

    /* Profil imza paneli — düğmeleri panel taşınsa da bağlı kalsın diye
       doğrudan panele bağlanır (liste innerHTML ile yeniden çiziliyor). */
    const signPanel = $('#mailIdentitySignature');
    if (signPanel) {
        signPanel.addEventListener('click', (event) => {
            if (event.target.closest('[data-ident-sign-cancel]')) { closeIdentitySignature(); return; }
            if (event.target.closest('[data-ident-sign-save]')) saveIdentitySignature();
        });

        $('#mailIdentitySignInherit').addEventListener('change', (event) => {
            if (event.target.checked) {
                // Kullanıcının yazdıkları kaybolmasın: devralmaya geçerken sakla
                accountsUI.signDraft = identitySignatureHtml();
                setIdentitySignatureHtml(accountsUI.signAccountHtml);
            } else {
                // Devralmadan çıkarken hesabın imzası başlangıç metni olur —
                // çoğu profil onun küçük bir türevini kullanıyor.
                setIdentitySignatureHtml(
                    accountsUI.signDraft !== null && accountsUI.signDraft !== ''
                        ? accountsUI.signDraft
                        : accountsUI.signAccountHtml
                );
            }
            syncIdentitySignatureState();
        });
    }

    const cardsWrap = $('#mailAccountsCards');
    if (cardsWrap) {
        cardsWrap.addEventListener('click', async (event) => {
            const edit = event.target.closest('[data-acct-edit]');
            if (edit) {
                try {
                    showAccountForm(await api(CFG.routes.accounts + '/' + edit.dataset.acctEdit));
                } catch (error) {
                    toast('error', 'Hesap açılamadı', error.message);
                }
                return;
            }

            const resync = event.target.closest('[data-acct-resync]');
            if (resync) {
                const ok = await confirmAsk('Postalar baştan indirilsin mi?',
                    'Paneldeki yerel kopya silinip sunucudan yeniden çekilir. '
                    + 'E-posta sunucunuzdaki iletilere dokunulmaz. Bu işlem birkaç dakika sürebilir.',
                    'Yeniden İndir');
                if (!ok) return;

                const icon = resync.querySelector('i');
                icon.className = 'fas fa-spinner fa-spin';
                resync.disabled = true;

                try {
                    const result = await api(CFG.routes.accounts + '/' + resync.dataset.acctResync + '/resync',
                        { method: 'POST' });
                    toast('success', 'Tamamlandı', result.message);
                    window.location.reload();
                } catch (error) {
                    toast('error', 'Yeniden indirilemedi', error.message);
                    icon.className = 'fas fa-arrows-rotate';
                    resync.disabled = false;
                }
                return;
            }

            const setDefault = event.target.closest('[data-acct-default]');
            if (setDefault) {
                try {
                    await api(CFG.routes.accounts + '/' + setDefault.dataset.acctDefault + '/default', { method: 'POST' });
                    toast('success', 'Varsayılan hesap güncellendi');
                    openAccounts();
                } catch (error) {
                    toast('error', 'Güncellenemedi', error.message);
                }
                return;
            }

            const remove = event.target.closest('[data-acct-delete]');
            if (remove) {
                const ok = await confirmAsk('Hesap kaldırılsın mı?',
                    'Panelden kaldırılır; e-posta sunucunuzdaki iletilere dokunulmaz.', 'Kaldır');
                if (!ok) return;

                try {
                    await api(CFG.routes.accounts + '/' + remove.dataset.acctDelete, { method: 'DELETE' });
                    toast('success', 'Hesap kaldırıldı');
                    window.location.reload();
                } catch (error) {
                    toast('error', 'Kaldırılamadı', error.message);
                }
            }
        });
    }

    /* Klavye kısayolları */

    document.addEventListener('keydown', (event) => {
        const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(event.target.tagName)
            || event.target.isContentEditable;

        // Yazma penceresi kısayolları
        if (compose.node && compose.node.classList.contains('is-open')) {
            if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
                event.preventDefault();
                sendMail();
                return;
            }
            if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
                event.preventDefault();
                saveDraft();
                return;
            }
            if (event.key === 'Escape' && !typing) { closeCompose(false); return; }
        }

        if (typing) return;

        // Modal açıkken (hesap ayarları, onay kutusu) tek harf kısayolları çalışmaz
        if (document.querySelector('.modal.show, .apx-modal')) return;

        // Yeni ileti
        if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'n') {
            event.preventDefault();
            openCompose('new');
            return;
        }

        // Buradan sonrası tek harf kısayolları. Ctrl/Cmd/Alt basılıysa tarayıcının
        // kendi kısayolu çalışmalı — Ctrl+R yenileme, Ctrl+A tümünü seç, Ctrl+F bul…
        // (Aksi hâlde Ctrl+R "yanıtla" penceresini açıyordu.)
        if (event.ctrlKey || event.metaKey || event.altKey) return;

        switch (event.key) {
            case '/':
                event.preventDefault();
                if (el.search) el.search.focus();
                break;
            case 'Delete':
                if (targetIds().length) runAction(CFG.routes.trash, {}, { remove: true, toast: 'Silindi' });
                break;
            case 'c':
                openCompose('new');
                break;
            case 'r':
                if (state.active) openCompose('reply', state.active);
                break;
            case 'a':
                if (state.active) openCompose('replyAll', state.active);
                break;
            case 'f':
                if (state.active) openCompose('forward', state.active);
                break;
            case 'u':
                if (targetIds().length) runAction(CFG.routes.read, { seen: false }, { patch: { is_seen: false } });
                break;
            case 'e':
                if (targetIds().length) runAction(CFG.routes.archive, {}, { remove: true, toast: 'Arşivlendi' });
                break;
            case 'Escape':
                closeMenu();
                state.selected.clear();
                $$('.mail-item').forEach((n) => n.classList.remove('is-checked'));
                syncToolbar();
                break;
            // F5 tarayıcının yenilemesidir, ele geçirilmez. Eşitleme F9'da
            // (Outlook'taki gönder/al tuşu) ve araç çubuğundaki Yenile düğmesinde.
            case 'F9':
                event.preventDefault();
                runSync(false);
                break;
            case 'ArrowDown':
            case 'ArrowUp': {
                if (!state.items.length) return;
                event.preventDefault();
                const index = state.items.findIndex((m) => m.id === state.activeId);
                const next = event.key === 'ArrowDown'
                    ? Math.min(state.items.length - 1, index + 1)
                    : Math.max(0, index - 1);
                if (state.items[next]) {
                    openMessage(state.items[next].id);
                    const node = el.listScroll.querySelector('.mail-item[data-id="' + state.items[next].id + '"]');
                    if (node) node.scrollIntoView({ block: 'nearest' });
                }
                break;
            }
        }
    });

    /**
     * Sayfadan ayrılma uyarısı — yalnızca gerçekten veri kaybı riski varken.
     *
     * Yazma penceresi artık kendiliğinden taslak kaydettiği için yenileme
     * çoğu durumda güvenli: kullanıcı Taslaklar'dan devam eder ve her
     * yenilemede uyarı görmek gereksiz korku yaratıyordu. Uyarı yalnızca son
     * otomatik kayıt BAŞARISIZ olduysa ya da kayıt hâlâ uçuştaysa çıkar.
     *
     * NOT: Bu diyalog tarayıcının kendi diyaloğudur; metni de görünümü de
     * değiştirilemez, yerine uygulama içi bir kip gösterilemez. Tarayıcı
     * güvenlik kısıtı — panelin kendi onayları (pencereyi kapatma, iletiyi
     * atma) zaten appConfirm ile çalışıyor.
     */
    window.addEventListener('beforeunload', (event) => {
        const open = compose.node && compose.node.classList.contains('is-open');
        const atRisk = autosave.failed || autosave.running || (compose.dirty && !autosave.key);

        if (open && atRisk) {
            event.preventDefault();
            event.returnValue = '';
        }
    });

    /* ── Kurallar (otomatik filtreler) ──────────────────────────
       Kural motoru senkronun içinde çalışır; buradaki pencere yalnızca
       tanımları yönetir. Alan, işleç ve hedef klasör listeleri sunucudan
       gelir — yeni bir koşul alanı eklendiğinde arayüz kendiliğinden öğrenir. */

    const rules = {
        items: [], accounts: [], targets: [], fields: [], operators: [],
        editingId: null, loaded: false, tab: 'rules',
    };

    function rulesModal() {
        const node = $('#mailRulesModal');
        if (!node || typeof bootstrap === 'undefined') return null;
        return bootstrap.Modal.getOrCreateInstance(node);
    }

    async function openRules() {
        const modal = rulesModal();
        if (modal) modal.show();

        showRulesList();

        const wrap = $('#mailRulesCards');
        wrap.innerHTML = '<div class="mail-rule-empty"><i class="fas fa-spinner fa-spin"></i> Yükleniyor…</div>';

        try {
            const data = await api(CFG.routes.rules);
            rules.items = data.rules || [];
            rules.accounts = data.accounts || [];
            rules.targets = data.targets || [];
            rules.fields = data.fields || [];
            rules.operators = data.operators || [];
            rules.loaded = true;
            renderRules();
        } catch (error) {
            wrap.innerHTML = '<div class="mail-rule-empty is-error">' + esc(error.message) + '</div>';
        }
    }

    function renderRules() {
        const wrap = $('#mailRulesCards');
        setTabCount('rules', rules.items.length);

        if (!rules.items.length) {
            wrap.innerHTML = '<div class="mail-rule-empty">'
                + '<i class="fas fa-filter"></i>'
                + 'Henüz kural yok. Gelen postayı klasöre ayırmak, okundu işaretlemek '
                + 'ya da gereksizi ayıklamak için bir kural ekleyin.</div>';
            return;
        }

        wrap.innerHTML = rules.items.map((rule) => {
            const account = rules.accounts.find((a) => Number(a.id) === Number(rule.account_id));
            const scope = account
                ? '<span class="mail-rule-acct" style="--acc:' + esc(account.color) + '">' + esc(account.name) + '</span>'
                : '<span class="mail-rule-acct is-all">Tüm hesaplar</span>';

            return '<div class="mail-rule-card' + (rule.is_active ? '' : ' is-off') + '" data-rule-id="' + rule.id + '">'
                + '<div class="mail-rule-main">'
                + '  <div class="mail-rule-name">' + esc(rule.name)
                + (rule.stop_processing ? ' <i class="fas fa-hand" title="Bu kuraldan sonra durur"></i>' : '')
                + '  </div>'
                + '  <div class="mail-rule-sum">' + esc(rule.summary) + '</div>'
                + '  <div class="mail-rule-meta">' + scope
                + (rule.is_active ? '' : '<span class="mail-rule-off">durduruldu</span>') + '</div>'
                + '</div>'
                + '<div class="mail-rule-tools">'
                + '  <button type="button" class="mail-icon-btn" data-rule-run="' + rule.id + '" title="Mevcut iletilere uygula">'
                + '<i class="fas fa-play"></i></button>'
                + '  <button type="button" class="mail-icon-btn" data-rule-toggle="' + rule.id + '" '
                + 'title="' + (rule.is_active ? 'Durdur' : 'Etkinleştir') + '">'
                + '<i class="fas fa-' + (rule.is_active ? 'pause' : 'play-circle') + '"></i></button>'
                + '  <button type="button" class="mail-icon-btn" data-rule-edit="' + rule.id + '" title="Düzenle">'
                + '<i class="fas fa-pen"></i></button>'
                + '  <button type="button" class="mail-icon-btn is-danger" data-rule-delete="' + rule.id + '" title="Sil">'
                + '<i class="fas fa-trash-can"></i></button>'
                + '</div>'
                + '</div>';
        }).join('');
    }

    function showRulesList() {
        rules.editingId = null;
        $('#mailRulesTabs').style.display = '';
        $('#mailRuleForm').style.display = 'none';
        $('#mailRuleBack').style.display = 'none';
        $('#mailRuleSave').style.display = 'none';
        $('#mailRulesClose').style.display = '';

        // Görünen sekme korunur: kullanıcı engel listesinden bir kural
        // düzenleyip geri döndüğünde kurallara atlamak şaşırtıcı olurdu
        setRulesTab(rules.tab || 'rules');
    }

    function showRuleForm(rule) {
        const form = $('#mailRuleForm');
        form.reset();
        rules.editingId = rule ? rule.id : null;

        $('#mailRulesTabs').style.display = 'none';
        $('#mailBlockedView').style.display = 'none';
        $('#mailRulesList').style.display = 'none';
        form.style.display = '';
        $('#mailRuleBack').style.display = '';
        $('#mailRuleSave').style.display = '';
        $('#mailRulesClose').style.display = 'none';
        $('#mailRulesTitle').textContent = rule ? 'Kuralı Düzenle' : 'Yeni Kural';

        $('#mailRuleAccount').innerHTML = '<option value="">Tüm hesaplar</option>'
            + rules.accounts.map((a) => '<option value="' + a.id + '">' + esc(a.name) + ' — ' + esc(a.email) + '</option>').join('');

        $('#mailRuleTarget').innerHTML = '<option value="">Taşıma</option>' + targetOptions();

        form.querySelector('[name="id"]').value = rule ? rule.id : '';
        form.querySelector('[name="name"]').value = rule ? rule.name : '';
        form.querySelector('[name="account_id"]').value = rule && rule.account_id ? rule.account_id : '';
        form.querySelector('[name="match_type"][value="' + ((rule && rule.match_type) || 'all') + '"]').checked = true;
        form.querySelector('[name="move_to"]').value = rule ? (rule.actions.move_to || '') : '';
        form.querySelector('[name="mark_read"]').checked = !!(rule && rule.actions.mark_read);
        form.querySelector('[name="flag"]').checked = !!(rule && rule.actions.flag);
        form.querySelector('[name="delete"]').checked = !!(rule && rule.actions.delete);
        form.querySelector('[name="stop_processing"]').checked = !!(rule && rule.stop_processing);

        const conditions = (rule && rule.conditions.length) ? rule.conditions : [{ field: 'from', op: 'contains', value: '' }];
        $('#mailRuleConditions').innerHTML = conditions.map(conditionRow).join('');
    }

    /** Hedef listesi kaynağına göre gruplanır (standart klasörler, hesaplar). */
    function targetOptions() {
        const groups = [];
        rules.targets.forEach((t) => {
            let group = groups.find((g) => g.name === t.group);
            if (!group) { group = { name: t.group, items: [] }; groups.push(group); }
            group.items.push(t);
        });

        return groups.map((g) => '<optgroup label="' + esc(g.name) + '">'
            + g.items.map((t) => '<option value="' + esc(t.value) + '">' + esc(t.label) + '</option>').join('')
            + '</optgroup>').join('');
    }

    function conditionRow(condition) {
        const c = condition || { field: 'from', op: 'contains', value: '' };

        return '<div class="mail-rule-cond">'
            + '<select data-cond="field">'
            + rules.fields.map((f) => '<option value="' + esc(f.key) + '"'
                + (f.key === c.field ? ' selected' : '') + '>' + esc(f.label) + '</option>').join('')
            + '</select>'
            + '<select data-cond="op">'
            + rules.operators.map((o) => '<option value="' + esc(o.key) + '"'
                + (o.key === c.op ? ' selected' : '') + '>' + esc(o.label) + '</option>').join('')
            + '</select>'
            + '<input type="text" data-cond="value" value="' + esc(c.value || '') + '" placeholder="Aranan metin">'
            + '<button type="button" class="mail-icon-btn is-danger" data-cond-remove title="Koşulu kaldır">'
            + '<i class="fas fa-xmark"></i></button>'
            + '</div>';
    }

    function rulePayload() {
        const form = $('#mailRuleForm');

        const conditions = $$('.mail-rule-cond', $('#mailRuleConditions')).map((row) => ({
            field: row.querySelector('[data-cond="field"]').value,
            op: row.querySelector('[data-cond="op"]').value,
            value: row.querySelector('[data-cond="value"]').value.trim(),
        })).filter((c) => c.value !== '');

        return {
            name: form.querySelector('[name="name"]').value.trim(),
            account_id: form.querySelector('[name="account_id"]').value || null,
            is_active: true,
            match_type: form.querySelector('[name="match_type"]:checked').value,
            stop_processing: form.querySelector('[name="stop_processing"]').checked,
            conditions: conditions,
            actions: {
                move_to: form.querySelector('[name="move_to"]').value,
                mark_read: form.querySelector('[name="mark_read"]').checked,
                flag: form.querySelector('[name="flag"]').checked,
                delete: form.querySelector('[name="delete"]').checked,
            },
        };
    }

    async function saveRule() {
        const payload = rulePayload();

        if (!payload.name) return toast('error', 'Kural adı gerekli');
        if (!payload.conditions.length) return toast('error', 'En az bir koşul girin');

        const actions = payload.actions;
        if (!actions.move_to && !actions.mark_read && !actions.flag && !actions.delete) {
            return toast('error', 'En az bir eylem seçin');
        }

        // Düzenlemede kuralın etkinlik durumu korunur
        const editing = rules.items.find((r) => r.id === rules.editingId);
        if (editing) payload.is_active = editing.is_active;

        const button = $('#mailRuleSave');
        button.disabled = true;

        try {
            // JSON gövdede _method sahtelemesi çalışmaz; yöntem doğrudan verilir
            await (rules.editingId
                ? api(CFG.routes.rules + '/' + rules.editingId, { method: 'PUT', body: payload })
                : api(CFG.routes.rules, { method: 'POST', body: payload }));

            toast('success', rules.editingId ? 'Kural güncellendi' : 'Kural eklendi');
            await openRules();
        } catch (error) {
            toast('error', 'Kaydedilemedi', error.message);
        } finally {
            button.disabled = false;
        }
    }

    const manageRulesBtn = $('#mailManageRules');
    if (manageRulesBtn) manageRulesBtn.addEventListener('click', openRules);

    const addRuleBtn = $('#mailAddRuleBtn');
    if (addRuleBtn) addRuleBtn.addEventListener('click', () => showRuleForm(null));

    const ruleBackBtn = $('#mailRuleBack');
    if (ruleBackBtn) ruleBackBtn.addEventListener('click', showRulesList);

    const ruleSaveBtn = $('#mailRuleSave');
    if (ruleSaveBtn) ruleSaveBtn.addEventListener('click', saveRule);

    const ruleAddCond = $('#mailRuleAddCondition');
    if (ruleAddCond) {
        ruleAddCond.addEventListener('click', () => {
            const wrap = $('#mailRuleConditions');
            if ($$('.mail-rule-cond', wrap).length >= 8) return toast('error', 'En çok 8 koşul tanımlanabilir');
            wrap.insertAdjacentHTML('beforeend', conditionRow(null));
        });
    }

    const rulesModalNode = $('#mailRulesModal');
    if (rulesModalNode) {
        rulesModalNode.addEventListener('click', async (event) => {
            const removeCond = event.target.closest('[data-cond-remove]');
            if (removeCond) {
                const wrap = $('#mailRuleConditions');
                if ($$('.mail-rule-cond', wrap).length <= 1) return toast('error', 'En az bir koşul gerekli');
                removeCond.closest('.mail-rule-cond').remove();
                return;
            }

            const edit = event.target.closest('[data-rule-edit]');
            if (edit) {
                const rule = rules.items.find((r) => r.id === Number(edit.dataset.ruleEdit));
                if (rule) showRuleForm(rule);
                return;
            }

            const toggle = event.target.closest('[data-rule-toggle]');
            if (toggle) {
                try {
                    const result = await api(CFG.routes.rules + '/' + toggle.dataset.ruleToggle + '/toggle',
                        { method: 'POST', body: {} });
                    toast('success', result.message);
                    await openRules();
                } catch (error) {
                    toast('error', 'Değiştirilemedi', error.message);
                }
                return;
            }

            const run = event.target.closest('[data-rule-run]');
            if (run) {
                const ok = await confirmAsk('Kural mevcut iletilere uygulansın mı?',
                    'Gelen kutusundaki son iletiler taranır; kurala uyanlar taşınır ya da işaretlenir.',
                    'Uygula');
                if (!ok) return;

                const icon = run.querySelector('i');
                icon.className = 'fas fa-spinner fa-spin';
                run.disabled = true;

                try {
                    const result = await api(CFG.routes.rules + '/' + run.dataset.ruleRun + '/run',
                        { method: 'POST', body: {} });
                    toast('success', result.message);
                    refreshCounts();
                    loadList(false);
                } catch (error) {
                    toast('error', 'Uygulanamadı', error.message);
                } finally {
                    icon.className = 'fas fa-play';
                    run.disabled = false;
                }
                return;
            }

            const remove = event.target.closest('[data-rule-delete]');
            if (remove) {
                const ok = await confirmAsk('Kural silinsin mi?',
                    'Bu filtre bundan sonra gelen postaya uygulanmayacak. Daha önce taşınmış iletiler yerinde kalır.',
                    'Sil');
                if (!ok) return;

                try {
                    await api(CFG.routes.rules + '/' + remove.dataset.ruleDelete, { method: 'DELETE' });
                    toast('success', 'Kural silindi');
                    await openRules();
                } catch (error) {
                    toast('error', 'Silinemedi', error.message);
                }
            }
        });
    }

    /* ── Engellenen göndericiler (kurallar penceresinin 2. sekmesi) ──
       Engeller kural olarak saklanmıyor — her spam işaretlemesi bir kural
       yaratsaydı kurallar listesi tek satırlık kayıtlarla dolar, kullanılamaz
       hâle gelirdi. Ama kullanıcıdan da gizlenmiyor: hangi adresin ne zaman
       ve kaç kez engellendiği buradan görülür, gerekirse kaldırılır. */

    const blocked = { items: [], accounts: [], loaded: false };

    function setTabCount(tab, value) {
        const node = $('[data-tab-count="' + tab + '"]');
        if (node) node.textContent = value ? value : '';
    }

    function setRulesTab(tab) {
        rules.tab = tab;

        $$('#mailRulesTabs .mail-tab').forEach((b) => {
            b.classList.toggle('is-on', b.dataset.rulesTab === tab);
        });

        $('#mailRulesList').style.display = tab === 'rules' ? '' : 'none';
        $('#mailBlockedView').style.display = tab === 'blocked' ? '' : 'none';
        $('#mailRulesTitle').textContent = tab === 'blocked' ? 'Engellenen Göndericiler' : 'Kurallar';

        // Liste ilk açılışta çekilir; sonraki geçişlerde bellekten çizilir
        if (tab === 'blocked' && !blocked.loaded) loadBlocked();
    }

    async function loadBlocked() {
        const wrap = $('#mailBlockedRows');
        wrap.innerHTML = '<div class="mail-rule-empty"><i class="fas fa-spinner fa-spin"></i> Yükleniyor…</div>';

        try {
            const data = await api(CFG.routes.blocked);
            blocked.items = data.blocked || [];
            blocked.accounts = data.accounts || [];
            blocked.loaded = true;

            $('#mailBlockAccount').innerHTML = '<option value="">Tüm hesaplar</option>'
                + blocked.accounts.map((a) => '<option value="' + a.id + '">' + esc(a.name) + '</option>').join('');

            renderBlocked();
        } catch (error) {
            wrap.innerHTML = '<div class="mail-rule-empty is-error">' + esc(error.message) + '</div>';
        }
    }

    function renderBlocked() {
        const wrap = $('#mailBlockedRows');
        setTabCount('blocked', blocked.items.length);

        if (!blocked.items.length) {
            wrap.innerHTML = '<div class="mail-rule-empty">'
                + '<i class="fas fa-user-slash"></i>'
                + 'Engellenen gönderici yok. Bir iletiyi spam olarak işaretlediğinizde '
                + 'göndericisi burada listelenir.</div>';
            return;
        }

        wrap.innerHTML = blocked.items.map((b) => {
            const isDomain = b.match_kind === 'domain';

            const scope = b.account_id
                ? '<span class="mail-rule-acct" style="--acc:' + esc(b.account_color || '#2563eb') + '">'
                  + esc(b.account_name || 'Hesap') + '</span>'
                : '<span class="mail-rule-acct is-all">Tüm hesaplar</span>';

            // Sayaç kozmetik değil: engelin gerçekten iş görüp görmediği
            // ancak buradan anlaşılır
            const hits = b.hits > 0
                ? '<span class="mail-block-hits is-on">' + b.hits + ' ileti</span>'
                : '<span class="mail-block-hits">henüz uygulanmadı</span>';

            return '<div class="mail-block-row' + (isDomain ? ' is-domain' : '') + '">'
                + '<span class="mail-block-kind" title="' + (isDomain ? 'Alan adı engeli' : 'Adres engeli') + '">'
                + '<i class="fas fa-' + (isDomain ? 'globe' : 'at') + '"></i></span>'
                + '<div class="mail-block-main">'
                + '  <div class="mail-block-pattern">' + (isDomain ? '@' : '') + esc(b.pattern) + '</div>'
                + '  <div class="mail-block-meta">' + scope + hits
                + (b.last_hit_at ? '<span class="mail-block-date">son: ' + esc(b.last_hit_at) + '</span>' : '')
                + '  </div>'
                + '</div>'
                + '<button type="button" class="mail-icon-btn is-danger" data-block-remove="' + b.id + '" '
                + 'title="Engeli kaldır"><i class="fas fa-xmark"></i></button>'
                + '</div>';
        }).join('');
    }

    async function addBlocked() {
        const input = $('#mailBlockPattern');
        const kind = $('#mailBlockKind').value;
        const pattern = input.value.trim();

        if (!pattern) return toast('error', kind === 'domain' ? 'Alan adı girin' : 'E-posta adresi girin');

        const button = $('#mailBlockAdd');
        button.disabled = true;

        try {
            const data = await api(CFG.routes.blocked, {
                method: 'POST',
                body: {
                    pattern: pattern,
                    match_kind: kind,
                    account_id: $('#mailBlockAccount').value || null,
                    apply_existing: $('#mailBlockApplyExisting').checked,
                },
            });

            blocked.items = data.blocked || [];
            renderBlocked();
            input.value = '';
            toast('success', 'Engellendi', data.message || '');

            // Geçmişe uygulandıysa açık liste artık eskimiş olur
            if (data.affected) { refreshCounts(); loadList(false); }
        } catch (error) {
            toast('error', 'Engellenemedi', error.message);
        } finally {
            button.disabled = false;
        }
    }

    const blockedView = $('#mailBlockedView');
    if (blockedView) {
        const tabs = $('#mailRulesTabs');
        if (tabs) {
            tabs.addEventListener('click', (event) => {
                const tab = event.target.closest('[data-rules-tab]');
                if (tab) setRulesTab(tab.dataset.rulesTab);
            });
        }

        // Tür değişince örnek metin de değişsin — kullanıcı ne yazacağını
        // biçimden anlasın
        const kindSelect = $('#mailBlockKind');
        kindSelect.addEventListener('change', () => {
            $('#mailBlockPattern').placeholder = kindSelect.value === 'domain' ? 'ornek.com' : 'bilgi@ornek.com';
        });

        $('#mailBlockAdd').addEventListener('click', addBlocked);
        $('#mailBlockPattern').addEventListener('keydown', (event) => {
            if (event.key === 'Enter') { event.preventDefault(); addBlocked(); }
        });

        blockedView.addEventListener('click', async (event) => {
            const remove = event.target.closest('[data-block-remove]');
            if (!remove) return;

            const ok = await confirmAsk('Engel kaldırılsın mı?',
                'Bu göndericiden gelen yeni iletiler yeniden gelen kutusuna düşecek. '
                + 'Daha önce taşınmış iletiler gereksizde kalır.', 'Kaldır');
            if (!ok) return;

            try {
                const data = await api(CFG.routes.blocked + '/' + remove.dataset.blockRemove, { method: 'DELETE' });
                blocked.items = data.blocked || [];
                renderBlocked();
                toast('success', 'Engel kaldırıldı');
            } catch (error) {
                toast('error', 'Kaldırılamadı', error.message);
            }
        });
    }

    /* ── Yedekleme & dışa aktarma ───────────────────────────────
       Dışa aktarılan her biçim veritabanındaki ham MIME yedeğinden üretilir;
       pencere önce yedek durumunu gösterir, eksikse tamamlamayı önerir.
       Biçim listesi sunucudan gelir — yeni bir biçim eklendiğinde arayüz
       kendiliğinden öğrenir. */

    const xp = { format: null, formats: [], backup: null, busy: false, loaded: false };

    function xpModal() {
        const node = $('#mailExportModal');
        if (!node || typeof bootstrap === 'undefined') return null;
        return bootstrap.Modal.getOrCreateInstance(node);
    }

    async function openExport() {
        const modal = xpModal();
        if (!modal) return;

        // Kapsam düğmeleri o anki görünüme göre etiketlenir
        const account = accountById(state.scope.accountId);
        $('#mailXpSelCount').textContent = state.selected.size;
        $('#mailXpFolderName').textContent = state.scope.label || '—';
        $('#mailXpAccountName').textContent = account ? account.name : 'Tümü';
        $('#mailXpAllCount').textContent = 'Tüm hesaplar';

        // Seçim yoksa varsayılan kapsam açık klasör olsun
        selectXpScope(state.selected.size ? 'selection' : 'folder');

        modal.show();

        if (!xp.loaded) {
            try {
                const data = await api(CFG.routes.exportOptions);
                xp.formats = data.formats || [];
                xp.loaded = true;
                renderXpFormats();
                applyXpBackup(data.backup);
            } catch (error) {
                toast('error', 'Dışa aktarma seçenekleri alınamadı', error.message);
            }
        } else {
            refreshXpBackup();
        }
    }

    function renderXpFormats() {
        const wrap = $('#mailXpFormats');
        if (!wrap) return;

        wrap.innerHTML = xp.formats.map((f) => ''
            + '<button type="button" class="mail-xp-fmt" data-format="' + esc(f.key) + '">'
            + '<i class="fas ' + esc(f.icon) + '"></i>'
            + '<span class="mail-xp-fmt-n">' + esc(f.label) + '<em>.' + esc(f.ext) + '</em></span>'
            + '<span class="mail-xp-fmt-d">' + esc(f.note) + '</span>'
            + '<span class="mail-xp-fmt-max">en çok ' + Number(f.max).toLocaleString('tr-TR') + ' ileti</span>'
            + '</button>').join('');
    }

    function applyXpBackup(backup) {
        if (!backup) return;
        xp.backup = backup;

        const done = backup.messages ? Math.round((backup.archived / backup.messages) * 100) : 100;
        const fill = $('#mailXpBackupFill');
        const strip = $('#mailXpBackup');

        if (fill) fill.style.width = Math.min(100, done) + '%';
        strip.classList.toggle('is-done', backup.pending === 0);

        $('#mailXpBackupTitle').textContent = backup.pending === 0
            ? 'Tüm postalar yedekli (' + backup.archived + ' ileti)'
            : backup.archived + ' / ' + backup.messages + ' ileti yedekli';

        $('#mailXpBackupNote').textContent = backup.pending === 0
            ? 'Veritabanındaki yedek boyutu ' + humanBytes(backup.bytes)
            : backup.pending + ' iletinin ham hâli henüz indirilmedi — dışa aktarırken tamamlanır.';

        const btn = $('#mailXpBackupBtn');
        btn.disabled = backup.pending === 0 || xp.busy;
    }

    async function refreshXpBackup() {
        try {
            const data = await api(CFG.routes.exportOptions);
            applyXpBackup(data.backup);
        } catch (error) { /* sessiz: pencere yine kullanılabilir */ }
    }

    function humanBytes(n) {
        if (!n) return '0 B';
        if (n < 1024) return n + ' B';
        if (n < 1048576) return Math.round(n / 1024) + ' KB';
        if (n < 1073741824) return (n / 1048576).toFixed(1) + ' MB';
        return (n / 1073741824).toFixed(2) + ' GB';
    }

    function selectXpScope(scope) {
        $$('#mailXpScopes .mail-xp-scope').forEach((b) => b.classList.toggle('is-on', b.dataset.scope === scope));
    }

    function currentXpScope() {
        return ($('#mailXpScopes .mail-xp-scope.is-on') || {}).dataset?.scope || 'folder';
    }

    /** Pencerede seçilen kapsam + süzgeçleri istek gövdesine çevirir. */
    function xpPayload() {
        const scope = currentXpScope();
        const body = {
            scope: scope,
            from_date: $('#mailXpFrom').value || '',
            to_date: $('#mailXpTo').value || '',
            q: $('#mailXpQuery').value.trim(),
        };

        if (scope === 'selection') body.ids = Array.from(state.selected);
        if (scope === 'folder') {
            if (state.scope.kind === 'folder') body.folder_id = state.scope.folderId;
            else { body.scope = 'all'; body.unified = state.scope.unified; }
        }
        if (scope === 'account') {
            if (!state.scope.accountId) body.scope = 'all';
            else body.account_id = state.scope.accountId;
        }

        return body;
    }

    async function runBackup() {
        if (xp.busy) return;
        xp.busy = true;

        const btn = $('#mailXpBackupBtn');
        const original = btn.innerHTML;
        btn.disabled = true;
        btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>Yedekleniyor…</span>';

        try {
            // Uzun sürebilir: her turda 500 ileti, bitene kadar devam
            let guard = 0;
            let data;
            do {
                data = await api(CFG.routes.exportBackup, {
                    method: 'POST',
                    body: Object.assign(xpPayload(), { scope: 'all', limit: 500 }),
                });
                applyXpBackup(data.backup);
                guard++;
            } while (data.has_more && guard < 20);

            toast('success', 'Yedekleme tamamlandı', data.message);
        } catch (error) {
            toast('error', 'Yedekleme başarısız', error.message);
        } finally {
            xp.busy = false;
            btn.innerHTML = original;
            applyXpBackup(xp.backup);
        }
    }

    /**
     * Dışa aktarma. Dosya indirmesi olduğu için api() değil form gönderimi
     * kullanılır — tarayıcı indirmeyi kendi yönetir, büyük arşivlerde bellek
     * tüketilmez.
     */
    function runExport() {
        if (!xp.format || xp.busy) return;

        const payload = xpPayload();
        payload.format = xp.format;

        const form = document.createElement('form');
        form.method = 'POST';
        form.action = CFG.routes.exportRun;
        form.target = '_blank';
        form.style.display = 'none';

        const add = (name, value) => {
            const input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            input.value = value;
            form.appendChild(input);
        };

        add('_token', CFG.csrf);
        Object.keys(payload).forEach((key) => {
            const value = payload[key];
            if (value === '' || value == null) return;
            if (Array.isArray(value)) value.forEach((v) => add(key + '[]', v));
            else add(key, value);
        });

        document.body.appendChild(form);
        form.submit();
        setTimeout(() => form.remove(), 1000);

        toast('success', 'Dışa aktarma başlatıldı', 'Dosya hazırlanınca indirilecek.');
        xpModal()?.hide();
    }

    $('#mailExportBtn')?.addEventListener('click', openExport);
    $('#mailXpBackupBtn')?.addEventListener('click', runBackup);
    $('#mailXpRun')?.addEventListener('click', runExport);

    $('#mailXpScopes')?.addEventListener('click', (event) => {
        const btn = event.target.closest('.mail-xp-scope');
        if (btn) selectXpScope(btn.dataset.scope);
    });

    $('#mailXpFormats')?.addEventListener('click', (event) => {
        const btn = event.target.closest('.mail-xp-fmt');
        if (!btn) return;

        xp.format = btn.dataset.format;
        $$('#mailXpFormats .mail-xp-fmt').forEach((b) => b.classList.toggle('is-on', b === btn));

        const meta = xp.formats.find((f) => f.key === xp.format);
        // Sınır biçime göre değişir: ham metni akıtan biçimler çok, tüm belgeyi
        // belleğe kuranlar (PDF, HTML) az ileti taşır.
        $('#mailXpHint').textContent = meta
            ? meta.note + ' · en çok ' + Number(meta.max).toLocaleString('tr-TR') + ' ileti'
            : '';
        $('#mailXpRun').disabled = false;
    });

    /* ── Başlangıç ──────────────────────────────────────────── */

    if (hasAccounts) {
        // Açılışta varsayılan hesabın gelen kutusu seçilir; yoksa ilk hesabınki
        const preferred = (CFG.accounts || []).find((a) => a.is_default);
        const firstFolder =
            (preferred && $('.mail-folder[data-scope="folder"][data-folder-type="inbox"][data-account-id="' + preferred.id + '"]'))
            || $('.mail-folder[data-scope="folder"][data-folder-type="inbox"]')
            || $('.mail-folder[data-scope="unified"][data-unified="inbox"]');

        if (firstFolder) selectScope(firstFolder);
        else loadList(false);

        runSync(true);
        setInterval(() => runSync(true), 180000);   // 3 dakikada bir sessiz eşitleme
    } else {
        // Hesap yoksa doğrudan kurulum kipini aç
        setTimeout(openAccounts, 400);
    }
})();
