HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/resources/js/app.js
import './bootstrap';
import Alpine from 'alpinejs';
import collapse from '@alpinejs/collapse';
import '@hotwired/turbo';

Alpine.plugin(collapse);
window.Alpine = Alpine;

const SIDEBAR_SCROLL_STORAGE_KEY = 'mailpurse.sidebar.scrollTop';

const getSidebarNav = (root = document) => root.querySelector('aside[data-sidebar="app"] nav');

const saveSidebarScrollPosition = () => {
    const sidebarNav = getSidebarNav();

    if (!sidebarNav) {
        return;
    }

    window.sessionStorage.setItem(SIDEBAR_SCROLL_STORAGE_KEY, String(sidebarNav.scrollTop || 0));
};

const applySidebarScrollPosition = (sidebarNav) => {
    const savedScrollTop = window.sessionStorage.getItem(SIDEBAR_SCROLL_STORAGE_KEY);

    if (!sidebarNav || savedScrollTop === null) {
        return;
    }

    sidebarNav.scrollTop = Number.parseInt(savedScrollTop, 10) || 0;
};

const restoreSidebarScrollPosition = (root = document) => {
    const sidebarNav = getSidebarNav(root);

    if (!sidebarNav) {
        return;
    }

    applySidebarScrollPosition(sidebarNav);

    window.requestAnimationFrame(() => {
        applySidebarScrollPosition(sidebarNav);
    });
};

document.addEventListener('scroll', (event) => {
    if (event.target instanceof HTMLElement && event.target.matches('aside[data-sidebar="app"] nav')) {
        saveSidebarScrollPosition();
    }
}, true);

document.addEventListener('DOMContentLoaded', () => {
    restoreSidebarScrollPosition(document);
});
document.addEventListener('turbo:before-render', (event) => {
    if (event.detail && event.detail.newBody instanceof HTMLElement) {
        restoreSidebarScrollPosition(event.detail.newBody);
    }
});
document.addEventListener('turbo:load', () => {
    restoreSidebarScrollPosition(document);
});

if (window.Turbo && window.Turbo.session) {
    window.Turbo.session.drive = true;
}

if (
    window.Turbo
    && window.Turbo.config
    && typeof window.Turbo.config.drive === 'object'
    && window.Turbo.config.drive !== null
) {
    window.Turbo.config.drive.progressBarDelay = 0;
} else if (window.Turbo && typeof window.Turbo.setProgressBarDelay === 'function') {
    window.Turbo.setProgressBarDelay(0);
}

// Force a full page reload when navigating to/from the campaign create or
// edit pages. The Plate-based email content editor is loaded via @push('scripts')
// and its module DOMContentLoaded listener does not re-fire on Turbo visits,
// so the editor would never boot. A hard navigation guarantees a fresh boot.
(function forceReloadForCampaignWizard() {
    const isCampaignWizardPath = (urlString) => {
        if (!urlString) {
            return false;
        }
        try {
            const url = new URL(urlString, window.location.origin);
            // matches /customer/campaigns/create and /customer/campaigns/{id}/edit
            return /\/customer\/campaigns\/(?:create|[^/]+\/edit)\/?$/.test(url.pathname);
        } catch (e) {
            return false;
        }
    };

    document.addEventListener('turbo:before-visit', (event) => {
        const target = event && event.detail ? event.detail.url : null;
        const leavingWizard = isCampaignWizardPath(window.location.href);
        const enteringWizard = isCampaignWizardPath(target);

        if (enteringWizard || leavingWizard) {
            try {
                event.preventDefault();
            } catch (_) {
            }
            if (target) {
                window.location.href = target;
            }
        }
    });
})();

Alpine.data('templateImportModal', (opts = {}) => ({
    importOpen: false,
    canImport: !!opts.canImport,
    canAi: !!opts.canAi,
    tab: opts.initialTab || 'templates',
    loading: false,
    previewLoading: false,
    importing: false,
    error: '',
    gallery: [],
    categories: [],
    activeCategory: 'all',
    selected: null,
    preview: null,
    previewModalOpen: false,
    previewModalItem: null,
    previewModalLoading: false,
    previewModalData: null,
    ai: {
        provider: 'chatgpt',
        model: '',
        prompt: '',
        loading: false,
        error: '',
    },

    init() {
        if (this.$watch) {
            this.$watch('ai.provider', () => {
                const allowed = this.aiModelsForProvider();
                if (this.ai.model && !allowed.includes(this.ai.model)) {
                    this.ai.model = '';
                }
            });
        }
    },

    aiModelsForProvider() {
        const provider = (this.ai && typeof this.ai.provider === 'string') ? this.ai.provider : 'chatgpt';

        if (provider === 'gemini') {
            return ['gemini-2.0-flash', 'gemini-2.0-pro', 'gemini-1.5-flash-latest', 'gemini-1.5-pro-latest', 'gemini-1.0-pro-latest'];
        }

        if (provider === 'claude') {
            return ['claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229'];
        }

        return ['gpt-5', 'gpt-5-mini', 'gpt-5.2', 'gpt-5-nano', 'gpt-4.1'];
    },

    open() {
        this.importOpen = true;
        this.error = '';
        this.selected = null;
        this.preview = null;
        this.activeCategory = 'all';
        this.previewModalOpen = false;
        this.previewModalItem = null;
        this.previewModalData = null;
        this.ai.error = '';

        if (this.tab !== 'ai' && !this.canImport) {
            this.tab = 'ai';
        }

        if (this.tab !== 'ai' && this.canImport) {
            this.loadGallery();
        }
    },

    close() {
        this.importOpen = false;
        this.error = '';
    },

    setTab(tab) {
        if (tab === 'templates' && !this.canImport) {
            this.tab = 'ai';
            return;
        }
        if (tab === 'ai' && !this.canAi) {
            return;
        }

        this.tab = tab;
        this.error = '';
        this.selected = null;
        this.preview = null;
        this.activeCategory = 'all';
        this.ai.error = '';

        if (this.tab !== 'ai' && this.canImport) {
            this.loadGallery();
        }
    },

    setCategory(categoryId) {
        this.activeCategory = categoryId;
    },

    get filteredGallery() {
        if (this.activeCategory === 'all') {
            return this.gallery;
        }
        return this.gallery.filter(item => item.category === this.activeCategory);
    },

    get visibleCategories() {
        const usedCategories = new Set(this.gallery.map(item => item.category || 'other'));
        return this.categories.filter(cat => cat.id === 'all' || usedCategories.has(cat.id));
    },

    currentBuilder() {
        const el = document.getElementById('builder-type');
        const value = el ? String(el.value || '').trim() : '';
        if (value === 'text' || value === 'unlayer' || value === 'grapesjs' || value === 'custom') {
            return value;
        }
        return opts.builder || 'grapesjs';
    },

    loadGallery() {
        if (!this.canImport || !opts.galleryUrl) {
            this.gallery = [];
            return;
        }

        this.loading = true;
        this.error = '';

        const tabParam = 'templates';
        const builderParam = this.currentBuilder();
        const url = (opts.galleryUrl || '') + '?tab=' + encodeURIComponent(tabParam) + '&builder=' + encodeURIComponent(builderParam);
        fetch(url, { headers: { 'Accept': 'application/json' } })
            .then((r) => r.ok ? r.json() : Promise.reject(r))
            .then((data) => {
                this.gallery = Array.isArray(data.templates) ? data.templates : [];
                this.categories = Array.isArray(data.categories) ? data.categories : [];
            })
            .catch(() => {
                this.error = 'Failed to load templates.';
            })
            .finally(() => {
                this.loading = false;
            });
    },

    openPreviewModal(item) {
        if (!item) return;
        this.previewModalItem = item;
        this.previewModalOpen = true;
        this.previewModalLoading = true;
        this.previewModalData = null;

        const url = (item && typeof item.content_url === 'string' && item.content_url)
            ? item.content_url
            : ((opts.contentUrlBase || '') + '/' + item.id + '/content');

        fetch(url, { headers: { 'Accept': 'application/json' } })
            .then((r) => r.ok ? r.json() : Promise.reject(r))
            .then((data) => {
                this.previewModalData = data;
            })
            .catch(() => {
                this.error = 'Failed to load template preview.';
            })
            .finally(() => {
                this.previewModalLoading = false;
            });
    },

    closePreviewModal() {
        this.previewModalOpen = false;
        this.previewModalItem = null;
        this.previewModalData = null;
    },

    insertFromPreviewModal() {
        if (!this.previewModalData) return;
        this.preview = this.previewModalData;
        this.closePreviewModal();
        this.doImport();
    },

    insertTemplate(item) {
        if (!item) return;
        this.previewLoading = true;
        this.error = '';

        const url = (item && typeof item.content_url === 'string' && item.content_url)
            ? item.content_url
            : ((opts.contentUrlBase || '') + '/' + item.id + '/content');

        fetch(url, { headers: { 'Accept': 'application/json' } })
            .then((r) => r.ok ? r.json() : Promise.reject(r))
            .then((data) => {
                this.preview = data;
                this.doImport();
            })
            .catch(() => {
                this.error = 'Failed to load template.';
            })
            .finally(() => {
                this.previewLoading = false;
            });
    },

    select(item) {
        if (!this.canImport || !opts.contentUrlBase) {
            return;
        }

        this.selected = item;
        this.previewLoading = true;
        this.error = '';

        const url = (item && typeof item.content_url === 'string' && item.content_url)
            ? item.content_url
            : ((opts.contentUrlBase || '') + '/' + item.id + '/content');

        fetch(url, { headers: { 'Accept': 'application/json' } })
            .then((r) => r.ok ? r.json() : Promise.reject(r))
            .then((data) => {
                this.preview = data;
            })
            .catch(() => {
                this.error = 'Failed to load template preview.';
            })
            .finally(() => {
                this.previewLoading = false;
            });
    },

    doImport() {
        if (!this.preview) {
            return;
        }
        this.importing = true;
        this.error = '';

        const builder = this.preview.builder || null;

        const originInput = document.querySelector('input[name="settings[origin]"]');
        if (originInput) {
            originInput.value = this.tab === 'ai' ? 'ai' : '';
        }

        const effectiveBuilder = this.currentBuilder();

        if (effectiveBuilder === 'text' && (builder === 'text' || !builder)) {
            try {
                const mountEl = document.querySelector('[data-outreach-rich-editor][data-template-editor="1"]');
                if (!mountEl) {
                    throw new Error('Text composer is not ready yet.');
                }
                const html = String(this.preview.html_content || '');
                mountEl.dataset.value = html;
                this.importOpen = false;
            } catch (e) {
                const msg = (e && e.message) ? e.message : 'Failed to import this template.';
                this.error = msg;
            } finally {
                this.importing = false;
            }
            return;
        }

        if (opts.builder === 'custom') {
            try {
                if (window.__mailpurseCustomTemplateBuilder && typeof window.__mailpurseCustomTemplateBuilder.importTemplate === 'function') {
                    const imported = window.__mailpurseCustomTemplateBuilder.importTemplate(this.preview);
                    if (!imported) {
                        throw new Error('This template is not compatible with the current builder.');
                    }
                    this.importOpen = false;
                    this.importing = false;
                    return;
                }

                throw new Error('Custom builder is not ready yet.');
            } catch (e) {
                const msg = (e && e.message) ? e.message : 'Failed to import this template.';
                this.error = msg;
                this.importing = false;
                return;
            }
        }

        if (opts.builder === 'unlayer' && builder === 'unlayer' && this.preview.builder_data) {
            const waitForUnlayer = (timeoutMs = 20000) => new Promise((resolve, reject) => {
                const start = Date.now();
                const tick = () => {
                    if (window.__mailpurseUnlayerReady === true) {
                        resolve();
                        return;
                    }
                    if (Date.now() - start >= timeoutMs) {
                        reject(new Error('Unlayer editor is not ready yet.'));
                        return;
                    }
                    setTimeout(tick, 75);
                };
                tick();
            });

            Promise.resolve()
                .then(() => ensureUnlayerScriptLoaded())
                .then(() => {
                    try {
                        setupUnlayerEditors();
                    } catch (e) {
                    }
                })
                .then(() => {
                    const rawDesign = this.preview.builder_data;
                    const unwrapped = (window.Alpine && typeof window.Alpine.raw === 'function')
                        ? window.Alpine.raw(rawDesign)
                        : rawDesign;

                    let design = unwrapped;

                    if (design && typeof design === 'object' && design.builder === 'unlayer' && design.unlayer && typeof design.unlayer === 'object') {
                        design = design.unlayer;
                    }

                    if (design && typeof design === 'object' && design.design && typeof design.design === 'object') {
                        design = design.design;
                    }

                    if (design && typeof design === 'object' && design.unlayer && typeof design.unlayer === 'object' && design.unlayer.body) {
                        design = design.unlayer;
                    }

                    if (typeof design === 'string') {
                        const trimmed = design.trim();
                        if (trimmed) {
                            try {
                                design = JSON.parse(trimmed);
                            } catch (e) {
                                throw new Error('Template design is not valid JSON.');
                            }
                        }
                    }

                    if (!design || typeof design !== 'object') {
                        throw new Error('Template design is invalid.');
                    }

                    if (!design.body && (design.rows || design.values)) {
                        design = { counters: {}, body: design };
                    }

                    if (!design.body || typeof design.body !== 'object') {
                        throw new Error('Template design is missing body.');
                    }

                    try {
                        if (typeof structuredClone === 'function') {
                            design = structuredClone(design);
                        } else {
                            design = JSON.parse(JSON.stringify(design));
                        }
                    } catch (e) {
                        try {
                            design = JSON.parse(JSON.stringify(design));
                        } catch (e2) {
                            const msg = (e2 && e2.message) ? e2.message : ((e && e.message) ? e.message : 'Unknown error');
                            throw new Error('Template design could not be cloned: ' + msg);
                        }
                    }

                    if (window.__mailpurseUnlayerReady === true && window.unlayer && typeof window.unlayer.loadDesign === 'function') {
                        window.unlayer.loadDesign(design);
                        this.importOpen = false;
                        return;
                    }

                    window.__mailpurseUnlayerPendingDesign = design;
                    return waitForUnlayer().then(() => {
                        if (window.unlayer && typeof window.unlayer.loadDesign === 'function') {
                            window.unlayer.loadDesign(design);
                        }
                        this.importOpen = false;
                    });
                })
                .catch((e) => {
                    const msg = (e && e.message) ? e.message : null;
                    this.error = msg ? ('Failed to import this template: ' + msg) : 'Failed to import this template.';
                })
                .finally(() => {
                    this.importing = false;
                });
            return;
        }

        if (opts.builder === 'grapesjs' && builder === 'grapesjs' && window.__mailpurseGrapesEditor) {
            try {
                const editor = window.__mailpurseGrapesEditor;
                const builderData = this.preview.builder_data || null;
                const components = builderData && builderData.components ? builderData.components : null;
                const styles = builderData && builderData.styles ? builderData.styles : null;
                const html = this.preview.html_content || null;

                if (components) {
                    editor.setComponents(components);
                } else if (html) {
                    editor.setComponents(html);
                }
                if (styles) {
                    editor.setStyle(styles);
                }

                this.importOpen = false;
            } catch (e) {
                this.error = 'Failed to import this template.';
            } finally {
                this.importing = false;
            }
            return;
        }

        this.importing = false;
        this.error = 'This template is not compatible with the current builder.';
    },

    importSelected() {
        this.doImport();
    },

    aiGenerate() {
        this.ai.loading = true;
        this.ai.error = '';
        this.error = '';

        fetch(opts.aiUrl, {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': opts.csrfToken,
            },
            body: JSON.stringify({
                provider: this.ai.provider,
                model: this.ai.model || null,
                prompt: this.ai.prompt,
                builder: opts.builder || 'grapesjs',
            }),
        })
            .then(async (r) => {
                if (!r.ok) {
                    const payload = await r.json().catch(() => null);
                    const message = payload && payload.message ? payload.message : 'Failed to generate template.';
                    throw new Error(message);
                }
                return r.json();
            })
            .then((data) => {
                this.preview = data;
            })
            .catch((e) => {
                this.ai.error = (e && e.message) ? e.message : 'Failed to generate template.';
            })
            .finally(() => {
                this.ai.loading = false;
            });
    },
}));

Alpine.data('headerSearch', (config = {}) => ({
    query: config.initialQuery || '',
    open: false,
    loading: false,
    items: [],
    activeIndex: -1,
    debounceMs: config.debounceMs || 200,
    minChars: config.minChars || 2,
    suggestUrl: config.suggestUrl || '',
    searchUrl: config.searchUrl || '',
    variant: config.variant || 'admin',
    _debounceTimer: null,
    _abortController: null,

    get hasItems() {
        return Array.isArray(this.items) && this.items.length > 0;
    },

    get searchUrlWithQuery() {
        if (!this.searchUrl) {
            return '#';
        }
        const q = (this.query || '').trim();
        return q ? `${this.searchUrl}?q=${encodeURIComponent(q)}` : this.searchUrl;
    },

    get dropdownBgClass() {
        return this.variant === 'customer' ? 'bg-admin-sidebar border-gray-500' : 'bg-admin-sidebar border-gray-500';
    },

    get itemHoverClass() {
        return this.variant === 'customer' ? 'hover:bg-white/5' : 'hover:bg-white/5';
    },

    reset() {
        this.items = [];
        this.activeIndex = -1;
        this.loading = false;
    },

    close() {
        this.open = false;
        this.activeIndex = -1;
    },

    onFocus() {
        const q = (this.query || '').trim();
        if (q.length >= this.minChars) {
            if (this.hasItems) {
                this.open = true;
            } else {
                this.fetchSuggestions();
            }
        }
    },

    onInput() {
        clearTimeout(this._debounceTimer);
        this._debounceTimer = setTimeout(() => {
            this.fetchSuggestions();
        }, this.debounceMs);
    },

    async fetchSuggestions() {
        const q = (this.query || '').trim();

        if (!this.suggestUrl || q.length < this.minChars) {
            this.reset();
            this.open = false;
            return;
        }

        if (this._abortController) {
            this._abortController.abort();
        }

        this._abortController = new AbortController();
        this.loading = true;
        this.open = true;

        try {
            const res = await window.axios.get(this.suggestUrl, {
                params: { q },
                signal: this._abortController.signal,
            });
            this.items = res?.data?.items || [];
            this.activeIndex = this.items.length ? 0 : -1;
        } catch (e) {
            if (e?.name !== 'CanceledError' && e?.code !== 'ERR_CANCELED') {
                this.items = [];
                this.activeIndex = -1;
            }
        } finally {
            this.loading = false;
        }
    },

    onKeydown(e) {
        if (!this.open) {
            return;
        }

        if (e.key === 'Escape') {
            e.preventDefault();
            this.close();
            return;
        }

        if (e.key === 'ArrowDown') {
            e.preventDefault();
            if (!this.hasItems) {
                return;
            }
            this.activeIndex = Math.min(this.items.length - 1, this.activeIndex + 1);
            return;
        }

        if (e.key === 'ArrowUp') {
            e.preventDefault();
            if (!this.hasItems) {
                return;
            }
            this.activeIndex = Math.max(0, this.activeIndex - 1);
            return;
        }

        if (e.key === 'Enter') {
            if (this.activeIndex >= 0 && this.items[this.activeIndex]?.url) {
                e.preventDefault();
                window.location.href = this.items[this.activeIndex].url;
            }
        }
    },

    select(item) {
        if (item?.url) {
            window.location.href = item.url;
        }
    },
}));

Alpine.start();

function escapeHtml(unsafe) {
    if (unsafe === null || unsafe === undefined) {
        return '';
    }
    return String(unsafe)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

function getNotificationsUiVariant(feedUrl) {
    if (!feedUrl) {
        return 'admin';
    }
    return feedUrl.includes('/customer/') ? 'customer' : 'admin';
}

function getNotificationsUiTokens(variant) {
    if (variant === 'customer') {
        return {
            emptyTextClass: 'text-gray-500 dark:text-admin-text-secondary',
            itemTitleClass: 'text-sm font-semibold text-gray-900 dark:text-admin-text-primary',
            itemMessageClass: 'mt-1 text-sm text-gray-600 dark:text-admin-text-secondary',
            itemTimeClass: 'mt-1.5 text-xs text-gray-400',
            iconWrapClass: 'w-10 h-10 rounded-full bg-primary-100 text-primary-600 flex items-center justify-center flex-shrink-0',
            unreadDotClass: 'mt-2 w-2 h-2 rounded-full bg-gray-900 dark:bg-white flex-shrink-0',
            hoverClass: 'hover:bg-gray-50 dark:hover:bg-white/5',
        };
    }

    return {
        emptyTextClass: 'text-admin-text-secondary',
        itemTitleClass: 'text-sm font-semibold text-admin-text-primary',
        itemMessageClass: 'mt-1 text-sm text-admin-text-secondary',
        itemTimeClass: 'mt-1.5 text-xs text-admin-text-secondary/80',
        iconWrapClass: 'w-10 h-10 rounded-full border !border-pink-300 bg-pink-100 flex items-center justify-center flex-shrink-0 color-pink-300',
        unreadDotClass: 'mt-2 w-2 h-2 rounded-full bg-white flex-shrink-0',
        hoverClass: 'hover:bg-white/5',
    };
}

const NOTIFICATIONS_BELL_SVG = '<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6 6 0 10-12 0v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>';

let currentNotificationsTab = 'all';

function renderNotificationsList(listEl, notifications, variant, tab = 'all') {
    if (!listEl) {
        return;
    }

    const t = getNotificationsUiTokens(variant);
    const unreadCount = Array.isArray(notifications)
        ? notifications.filter((n) => !n?.is_read).length
        : 0;

    listEl.dataset.notificationsUnreadCount = String(unreadCount);

    if (tab === 'unread') {
        if (unreadCount <= 0) {
            listEl.innerHTML = `
                <div class="px-6 py-10 text-center text-sm ${t.emptyTextClass}" data-notifications-empty-unread>
                    NO unread notificaitons
                </div>
            `;
            return;
        }

        const unreadNotifications = notifications.filter((n) => !n?.is_read);

        listEl.innerHTML = unreadNotifications
            .map((n) => {
                const title = escapeHtml(n.title || 'Notification');
                const message = n.message ? `<p class="${t.itemMessageClass}">${escapeHtml(n.message)}</p>` : '';
                const time = escapeHtml(n.created_at_human || '');
                const markReadUrl = n && typeof n === 'object' && typeof n.mark_read_url === 'string'
                    ? n.mark_read_url
                    : null;

                const url = (n && typeof n === 'object' && n.data && typeof n.data === 'object' && typeof n.data.url === 'string')
                    ? n.data.url
                    : null;

                const contentClass = `min-w-0 flex-1 ${url ? t.hoverClass : ''}`;
                const contentOpen = url
                    ? `<a href="${escapeHtml(url)}" class="${contentClass}" data-notifications-status="unread">`
                    : `<div class="${contentClass}" data-notifications-status="unread">`;
                const contentClose = url ? '</a>' : '</div>';
                const markReadButton = markReadUrl
                    ? `<button type="button" class="ml-3 shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-primary-600 hover:bg-primary-50 hover:text-primary-700 dark:text-primary-300 dark:hover:bg-white/5" data-notification-mark-read data-notification-mark-read-url="${escapeHtml(markReadUrl)}" aria-label="Mark notification as read">Mark read</button>`
                    : '';

                return `
                    <div class="px-6 py-4 flex items-start gap-3">
                        ${contentOpen}
                            <p class="${t.itemTitleClass}">${title}</p>
                            ${message}
                            <p class="${t.itemTimeClass}">${time}</p>
                        ${contentClose}
                        <span class="mt-2 w-2 h-2 rounded-full ${variant === 'customer' ? 'bg-gray-900 dark:bg-white' : 'bg-white'} flex-shrink-0"></span>
                        ${markReadButton}
                    </div>
                `;
            })
            .join('');
        return;
    }

    if (!Array.isArray(notifications) || notifications.length === 0) {
        listEl.innerHTML = `
            <div class="px-6 py-10 text-center text-sm ${t.emptyTextClass}" data-notifications-empty-all>
                No notifications yet.
            </div>
        `;
        return;
    }

    listEl.innerHTML = notifications
        .map((n) => {
            const title = escapeHtml(n.title || 'Notification');
            const message = n.message ? `<p class="${t.itemMessageClass}">${escapeHtml(n.message)}</p>` : '';
            const time = escapeHtml(n.created_at_human || '');
            const isRead = !!n.is_read;
            const status = isRead ? 'read' : 'unread';
            const dot = isRead ? '' : `<span class="${t.unreadDotClass}"></span>`;
            const markReadUrl = n && typeof n === 'object' && typeof n.mark_read_url === 'string'
                ? n.mark_read_url
                : null;

            const url = (n && typeof n === 'object' && n.data && typeof n.data === 'object' && typeof n.data.url === 'string')
                ? n.data.url
                : null;

            const contentClass = `min-w-0 flex-1 ${url ? t.hoverClass : ''}`;
            const contentOpen = url
                ? `<a href="${escapeHtml(url)}" class="${contentClass}" data-notifications-status="${status}">`
                : `<div class="${contentClass}" data-notifications-status="${status}">`;
            const contentClose = url ? '</a>' : '</div>';
            const markReadButton = !isRead && markReadUrl
                ? `<button type="button" class="ml-3 shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-primary-600 hover:bg-primary-50 hover:text-primary-700 dark:text-primary-300 dark:hover:bg-white/5" data-notification-mark-read data-notification-mark-read-url="${escapeHtml(markReadUrl)}" aria-label="Mark notification as read">Mark read</button>`
                : '';
            const iconMarkup = `<div class="${t.iconWrapClass}" data-notification-icon>${NOTIFICATIONS_BELL_SVG}</div>`;

            return `
                <div class="px-6 py-4 flex items-start gap-3">
                    ${iconMarkup}
                    ${contentOpen}
                        <p class="${t.itemTitleClass}">${title}</p>
                        ${message}
                        <p class="${t.itemTimeClass}">${time}</p>
                    ${contentClose}
                    ${dot}
                    ${markReadButton}
                </div>
            `;
        })
        .join('');
}

function updateNotificationsTabCount(notifications) {
    const total = Array.isArray(notifications) ? notifications.length : 0;
    document.querySelectorAll('[data-notifications-tab-count="all"]').forEach((el) => {
        el.textContent = String(total);
    });
}

function updateBadge(badgeEl, unreadCount) {
    if (!badgeEl) {
        return;
    }
    const count = Number(unreadCount) || 0;
    if (count <= 0) {
        badgeEl.remove();
        return;
    }
    badgeEl.textContent = count > 9 ? '9+' : String(count);
}

function ensureBadge(rootButtonEl, unreadCount) {
    const count = Number(unreadCount) || 0;
    const existing = rootButtonEl?.querySelector('[data-notifications-badge]') || null;
    if (count <= 0) {
        if (existing) {
            existing.remove();
        }
        return;
    }
    if (existing) {
        return;
    }

    const span = document.createElement('span');
    span.setAttribute('data-notifications-badge', '');
    span.className = 'absolute top-1 right-1 w-2 h-2 rounded-full bg-red-500';
    rootButtonEl.appendChild(span);
}

function updateUnreadLabel(labelEl, unreadCount) {
    if (!labelEl) {
        return;
    }
    const count = Number(unreadCount) || 0;
    labelEl.textContent = count > 0 ? `${count} unread` : 'Up to date';
}

async function refreshNotifications(rootButtonEl, containerEl) {
    const feedUrl = rootButtonEl?.getAttribute('data-notifications-feed-url');
    if (!feedUrl) {
        return;
    }

    // Drawer is teleported to <body>, so always look up list/label from document.
    const listEl = document.querySelector('[data-notifications-list]');
    const labelEl = document.querySelector('[data-notifications-unread-label]');

    try {
        const res = await window.axios.get(feedUrl);
        const unreadCount = res?.data?.unread_count ?? 0;
        const notifications = res?.data?.notifications ?? [];

        ensureBadge(rootButtonEl, unreadCount);
        updateUnreadLabel(labelEl, unreadCount);
        updateNotificationsTabCount(notifications);

        const variant = getNotificationsUiVariant(feedUrl);
        listEl.__mailpurseNotifications = notifications;
        listEl.__mailpurseNotificationsVariant = variant;
        renderNotificationsList(listEl, notifications, variant, currentNotificationsTab);
    } catch (e) {
        // Silent failure: avoid spamming console in production
    }
}

window.addEventListener('mailpurse-notifications-tab-change', (event) => {
    currentNotificationsTab = event?.detail?.tab === 'unread' ? 'unread' : 'all';

    const listEl = document.querySelector('[data-notifications-list]');
    if (!listEl) {
        return;
    }

    const notifications = Array.isArray(listEl.__mailpurseNotifications)
        ? listEl.__mailpurseNotifications
        : [];
    const variant = listEl.__mailpurseNotificationsVariant || 'admin';

    renderNotificationsList(listEl, notifications, variant, currentNotificationsTab);
});

function parseInlineConfirmMessage(code) {
    if (!code || typeof code !== 'string') {
        return '';
    }

    const idx = code.indexOf('confirm(');
    if (idx === -1) {
        return '';
    }

    const slice = code.slice(idx);
    const m = slice.match(/confirm\(\s*(['"])([\s\S]*?)\1\s*\)/);
    if (!m) {
        return '';
    }

    return (m[2] || '').toString();
}

function setupCustomConfirmModal() {
    if (window.__mailpurseCustomConfirmModalInit) {
        return;
    }
    window.__mailpurseCustomConfirmModalInit = true;

    document.addEventListener(
        'submit',
        (e) => {
            const form = e.target;
            if (!(form instanceof HTMLFormElement)) {
                return;
            }

            if (form.dataset && form.dataset.mpSkipConfirm === '1') {
                return;
            }

            const onsubmit = form.getAttribute('onsubmit') || '';
            if (!onsubmit.includes('confirm(')) {
                return;
            }

            const hasConfirmModal = !!document.querySelector('[data-confirm-modal-root]');
            if (!hasConfirmModal) {
                return;
            }

            const message = parseInlineConfirmMessage(onsubmit) || 'This action cannot be undone.';

            const methodInput = form.querySelector('input[name="_method"]');
            const methodOverride = methodInput ? String(methodInput.value || '').toUpperCase() : '';
            const isDelete = methodOverride === 'DELETE';

            window.dispatchEvent(
                new CustomEvent('open-confirm-modal', {
                    detail: {
                        form,
                        title: 'Are you sure?',
                        message,
                        confirmText: isDelete ? 'Delete' : 'Confirm',
                        cancelText: 'Cancel',
                        variant: isDelete ? 'danger' : 'default',
                    },
                })
            );

            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
        },
        true
    );
}

function setupNotificationWidget(rootButtonEl, allWidgets) {
    if (!rootButtonEl || !window.axios) {
        return;
    }

    const containerEl = rootButtonEl.parentElement;
    // Mark-all-read button lives in the teleported drawer (body); query the document.
    const markAllReadButton = document.querySelector('[data-notifications-mark-all-read]');
    const markAllReadUrl = rootButtonEl.getAttribute('data-notifications-mark-all-read-url');

    if (markAllReadButton && markAllReadUrl) {
        markAllReadButton.addEventListener('click', async (e) => {
            e.preventDefault();
            try {
                await window.axios.post(markAllReadUrl);
            } finally {
                (allWidgets || []).forEach((btn) => {
                    refreshNotifications(btn, btn.parentElement);
                });
            }
        });
    }

    if (!window.__mailpurseNotificationMarkReadInit) {
        window.__mailpurseNotificationMarkReadInit = true;
        document.addEventListener('click', async (e) => {
            const button = e.target instanceof Element ? e.target.closest('[data-notification-mark-read]') : null;
            if (!button) {
                return;
            }

            const url = button.getAttribute('data-notification-mark-read-url');
            if (!url) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            try {
                button.setAttribute('disabled', 'disabled');
                await window.axios.post(url);
            } finally {
                button.removeAttribute('disabled');
                (allWidgets || []).forEach((btn) => {
                    refreshNotifications(btn, btn.parentElement);
                });
            }
        });
    }

    // Refresh when the dropdown is opened
    rootButtonEl.addEventListener('click', () => {
        refreshNotifications(rootButtonEl, containerEl);
    });
}

function setupNotificationPolling() {
    const rootButtons = document.querySelectorAll('[data-notifications-root]');
    if (!rootButtons.length) {
        return;
    }

    const widgets = Array.from(rootButtons);
    widgets.forEach((btn) => setupNotificationWidget(btn, widgets));

    // Initial fetch + periodic refresh (single interval for all widgets)
    const refreshAll = () => {
        widgets.forEach((btn) => {
            refreshNotifications(btn, btn.parentElement);
        });
    };

    refreshAll();

    if (window.__mailpurseNotificationsIntervalId) {
        clearInterval(window.__mailpurseNotificationsIntervalId);
    }
    window.__mailpurseNotificationsIntervalId = setInterval(refreshAll, 15000);
}

function ensureUnlayerScriptLoaded() {
    if (window.unlayer && typeof window.unlayer.init === 'function') {
        return Promise.resolve();
    }

    if (window.__mailpurseUnlayerEmbedPromise) {
        return window.__mailpurseUnlayerEmbedPromise;
    }

    window.__mailpurseUnlayerEmbedPromise = new Promise((resolve, reject) => {
        try {
            const existing = document.querySelector('script[src="https://editor.unlayer.com/embed.js"]');
            if (existing) {
                const tick = (timeoutMs = 12000) => {
                    const started = Date.now();
                    const wait = () => {
                        if (window.unlayer && typeof window.unlayer.init === 'function') {
                            resolve();
                            return;
                        }
                        if (Date.now() - started >= timeoutMs) {
                            reject(new Error('Unlayer embed.js did not load in time.'));
                            return;
                        }
                        setTimeout(wait, 75);
                    };
                    wait();
                };
                tick();
                return;
            }

            const script = document.createElement('script');
            script.src = 'https://editor.unlayer.com/embed.js';
            script.async = true;
            script.onload = () => resolve();
            script.onerror = () => reject(new Error('Failed to load Unlayer embed.js.'));
            document.head.appendChild(script);
        } catch (e) {
            reject(e);
        }
    });

    return window.__mailpurseUnlayerEmbedPromise;
}

// Load custom tools from external file
async function loadUnlayerCustomTools() {
    // Simple test code to verify customJS is working
    return `
        console.error('[MailPurse Unlayer customJS] SIMPLE TEST - Script loaded at', new Date().toISOString());
        document.body.style.background = 'rgba(255,0,0,0.3)';
        
        // Test if we can register anything
        setTimeout(function() {
            console.log('[MailPurse Unlayer customJS] Checking for unlayer...', typeof window.unlayer);
            if (window.unlayer) {
                console.log('[MailPurse Unlayer customJS] Unlayer found!');
                console.log('[MailPurse Unlayer customJS] Methods available:', Object.getOwnPropertyNames(window.unlayer).filter(n => typeof window.unlayer[n] === 'function'));
            }
        }, 1000);
    `;
}

const MAILPURSE_UNLAYER_CUSTOM_TOOLS_JS = `(function(){
  try {
    var attempts = 0;

    // Force visible proof that customJS is running inside the iframe
    document.body.style.background = 'rgba(255,0,0,0.3)';
    console.error('[MailPurse Unlayer customJS] Injected and executing at', new Date().toISOString());

    function escAttr(v){
      return String(v == null ? '' : v).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    }
    function escHtml(v){
      return String(v == null ? '' : v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    }

    function post(type, payload){
      try {
        if (window.top && window.top !== window) {
          window.top.postMessage({ type: type, payload: payload || null }, '*');
        }
        if (window.parent && window.parent !== window && (!window.top || window.top === window || window.parent !== window.top)) {
          window.parent.postMessage({ type: type, payload: payload || null }, '*');
        }
      } catch (e) {}
    }

    function getUnlayer(){
      try {
        if (typeof unlayer !== 'undefined') return unlayer;
      } catch (e) {}
      try {
        if (window && window.unlayer) return window.unlayer;
      } catch (e) {}
      return null;
    }

    function boot(){
      try {
        post('mailpurse:unlayer:customjs:tick', { attempts: attempts });
        var u = getUnlayer();
        if (!u || typeof u.registerTool !== 'function' || typeof u.createViewer !== 'function') {
          attempts++;
          if (attempts < 160) {
            setTimeout(boot, 50);
          } else {
            post('mailpurse:unlayer:customjs:timeout', { attempts: attempts });
          }
          return;
        }

        if (window.__mailpurseUnlayerToolsRegistered) {
          post('mailpurse:unlayer:customjs:already-registered');
          return;
        }

        post('mailpurse:unlayer:customjs:unlayer-ready');

        function registerFormTool(){
          function exportForm(){
            return '<div style="padding:16px;border:1px solid #e5e7eb;border-radius:12px;">{{fields}}{{gdpr}}{{submit}}</div>';
          }

          try {
            u.registerTool({
              name: 'mp_form',
              label: 'Form',
              icon: 'fa-wpforms',
              supportedDisplayModes: ['email','web','popup'],
              options: {},
              values: {},
              renderer: {
                Viewer: u.createViewer({
                  render: function(){
                    return '<div style="padding:16px;border:1px dashed #cbd5e1;border-radius:12px;font-size:13px;">Form Container</div>';
                  }
                }),
                exporters: {
                  web: exportForm,
                  popup: exportForm,
                  email: exportForm
                },
                head: { css: function(){}, js: function(){} }
              }
            });
          } catch (e) {
            post('mailpurse:unlayer:customjs:error', { tool: 'mp_form', message: String(e && e.message ? e.message : e), stack: e && e.stack ? String(e.stack) : null });
          }
        }

        function registerInputTool(){
          function exportInput(values){
            var fk = values && values.fieldKey ? String(values.fieldKey) : 'email';
            var ph = values && values.placeholder ? String(values.placeholder) : '';
            var req = values && (values.required === true || values.required === 'true' || values.required === 1 || values.required === '1');
            return '<div data-mailpurse-field="' + escAttr(fk) + '" data-mailpurse-placeholder="' + escAttr(ph) + '"' + (req ? ' data-mailpurse-required="1"' : '') + '></div>';
          }

          u.registerTool({
            name: 'mp_input',
            label: 'Input',
            icon: 'fa-i-cursor',
            supportedDisplayModes: ['email','web','popup'],
            options: {
              field: {
                title: 'Field',
                position: 1,
                options: {
                  fieldKey: { label: 'Field', defaultValue: 'email', widget: 'dropdown' },
                  placeholder: { label: 'Placeholder', defaultValue: '', widget: 'text' },
                  required: { label: 'Required', defaultValue: false, widget: 'toggle' }
                }
              }
            },
            values: {},
            renderer: {
              Viewer: u.createViewer({
                render: function(values){
                  var fk = values && values.fieldKey ? String(values.fieldKey) : 'email';
                  return '<div style="padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;">Input: ' + escHtml(fk) + '</div>';
                }
              }),
              exporters: {
                web: exportInput,
                email: exportInput,
                popup: exportInput
              },
              head: { css: function(){}, js: function(){} }
            }
          });
        }

        function registerTextareaTool(){
          function exportTextarea(values){
            var fk = values && values.fieldKey ? String(values.fieldKey) : '';
            var ph = values && values.placeholder ? String(values.placeholder) : '';
            var req = values && (values.required === true || values.required === 'true' || values.required === 1 || values.required === '1');
            var rows = values && values.rows ? parseInt(values.rows, 10) : 3;
            if (isNaN(rows) || rows < 1) rows = 3;
            return '<div data-mailpurse-textarea="' + escAttr(fk) + '" data-mailpurse-placeholder="' + escAttr(ph) + '" data-mailpurse-rows="' + escAttr(rows) + '"' + (req ? ' data-mailpurse-required="1"' : '') + '></div>';
          }

          u.registerTool({
            name: 'mp_textarea',
            label: 'Textarea',
            icon: 'fa-align-left',
            supportedDisplayModes: ['email','web','popup'],
            options: {
              field: {
                title: 'Field',
                position: 1,
                options: {
                  fieldKey: { label: 'Field', defaultValue: '', widget: 'dropdown' },
                  placeholder: { label: 'Placeholder', defaultValue: '', widget: 'text' },
                  required: { label: 'Required', defaultValue: false, widget: 'toggle' },
                  rows: { label: 'Rows', defaultValue: 3, widget: 'number' }
                }
              }
            },
            values: {},
            renderer: {
              Viewer: u.createViewer({
                render: function(values){
                  var fk = values && values.fieldKey ? String(values.fieldKey) : '';
                  return '<div style="padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;">Textarea: ' + escHtml(fk) + '</div>';
                }
              }),
              exporters: {
                web: exportTextarea,
                email: exportTextarea,
                popup: exportTextarea
              },
              head: { css: function(){}, js: function(){} }
            }
          });
        }

        function registerSubmitTool(){
          function exportSubmit(values){
            var text = values && values.text ? String(values.text) : 'Subscribe';
            var fullWidth = values && (values.fullWidth === true || values.fullWidth === 'true' || values.fullWidth === 1 || values.fullWidth === '1');
            var bg = values && values.backgroundColor ? String(values.backgroundColor) : '#6366f1';
            var tc = values && values.textColor ? String(values.textColor) : '#ffffff';
            var br = values && values.borderRadius != null ? parseInt(values.borderRadius, 10) : 12;
            if (isNaN(br) || br < 0) br = 12;
            var py = values && values.paddingY != null ? parseInt(values.paddingY, 10) : 12;
            if (isNaN(py) || py < 0) py = 12;
            var px = values && values.paddingX != null ? parseInt(values.paddingX, 10) : 16;
            if (isNaN(px) || px < 0) px = 16;
            var style = 'border:0;cursor:pointer;font-weight:700;';
            style += 'background:' + escAttr(bg) + ';color:' + escAttr(tc) + ';';
            style += 'border-radius:' + escAttr(br) + 'px;';
            style += 'padding:' + escAttr(py) + 'px ' + escAttr(px) + 'px;';
            if (fullWidth) style += 'width:100%;display:block;';
            return '<button type="submit" data-mailpurse-submit="1" style="' + style + '">' + escHtml(text) + '</button>';
          }

          u.registerTool({
            name: 'mp_submit',
            label: 'Submit Button',
            icon: 'fa-paper-plane',
            supportedDisplayModes: ['email','web','popup'],
            options: {
              content: {
                title: 'Content',
                position: 1,
                options: {
                  text: { label: 'Text', defaultValue: 'Subscribe', widget: 'text' }
                }
              },
              style: {
                title: 'Style',
                position: 2,
                options: {
                  fullWidth: { label: 'Full Width', defaultValue: true, widget: 'toggle' },
                  backgroundColor: { label: 'Background', defaultValue: '#6366f1', widget: 'color_picker' },
                  textColor: { label: 'Text Color', defaultValue: '#ffffff', widget: 'color_picker' },
                  borderRadius: { label: 'Border Radius', defaultValue: 12, widget: 'number' },
                  paddingY: { label: 'Padding Y', defaultValue: 12, widget: 'number' },
                  paddingX: { label: 'Padding X', defaultValue: 16, widget: 'number' }
                }
              }
            },
            values: {},
            renderer: {
              Viewer: u.createViewer({
                render: function(values){
                  var t = values && values.text ? String(values.text) : 'Subscribe';
                  return '<div style="padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;">Submit Button: ' + escHtml(t) + '</div>';
                }
              }),
              exporters: {
                web: exportSubmit,
                email: exportSubmit,
                popup: exportSubmit
              },
              head: { css: function(){}, js: function(){} }
            }
          });
        }

        registerInputTool();
        registerTextareaTool();
        registerSubmitTool();
        registerFormTool();

        window.__mailpurseUnlayerToolsRegistered = true;
        post('mailpurse:unlayer:tools-registered');
      } catch (e) {}
    }

    post('mailpurse:unlayer:customjs:booted');
    boot();
  } catch (e) {}
})();`;

function registerMailpurseUnlayerTools() {
    if (window.__mailpurseUnlayerToolsRegistered) {
        return;
    }

    if (!window.unlayer || typeof window.unlayer.registerTool !== 'function' || typeof window.unlayer.createViewer !== 'function') {
        return;
    }

    const escAttr = (v) => String(v == null ? '' : v)
        .replace(/&/g, '&amp;')
        .replace(/"/g, '&quot;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');

    const escHtml = (v) => String(v == null ? '' : v)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');

    const exportInput = (values) => {
        const fk = values && values.fieldKey ? String(values.fieldKey) : 'email';
        const ph = values && values.placeholder ? String(values.placeholder) : '';
        const req = !!(values && (values.required === true || values.required === 'true' || values.required === 1 || values.required === '1'));
        return `<div data-mailpurse-field="${escAttr(fk)}" data-mailpurse-placeholder="${escAttr(ph)}"${req ? ' data-mailpurse-required="1"' : ''}></div>`;
    };

    const exportTextarea = (values) => {
        const fk = values && values.fieldKey ? String(values.fieldKey) : '';
        const ph = values && values.placeholder ? String(values.placeholder) : '';
        const req = !!(values && (values.required === true || values.required === 'true' || values.required === 1 || values.required === '1'));
        let rows = values && values.rows != null ? parseInt(values.rows, 10) : 3;
        if (Number.isNaN(rows) || rows < 1) {
            rows = 3;
        }
        return `<div data-mailpurse-textarea="${escAttr(fk)}" data-mailpurse-placeholder="${escAttr(ph)}" data-mailpurse-rows="${escAttr(rows)}"${req ? ' data-mailpurse-required="1"' : ''}></div>`;
    };

    const exportSubmit = (values) => {
        const text = values && values.text ? String(values.text) : 'Subscribe';
        const fullWidth = !!(values && (values.fullWidth === true || values.fullWidth === 'true' || values.fullWidth === 1 || values.fullWidth === '1'));
        const bg = values && values.backgroundColor ? String(values.backgroundColor) : '#6366f1';
        const tc = values && values.textColor ? String(values.textColor) : '#ffffff';

        let br = values && values.borderRadius != null ? parseInt(values.borderRadius, 10) : 12;
        if (Number.isNaN(br) || br < 0) br = 12;
        let py = values && values.paddingY != null ? parseInt(values.paddingY, 10) : 12;
        if (Number.isNaN(py) || py < 0) py = 12;
        let px = values && values.paddingX != null ? parseInt(values.paddingX, 10) : 16;
        if (Number.isNaN(px) || px < 0) px = 16;

        let style = 'border:0;cursor:pointer;font-weight:700;';
        style += `background:${escAttr(bg)};color:${escAttr(tc)};`;
        style += `border-radius:${escAttr(br)}px;`;
        style += `padding:${escAttr(py)}px ${escAttr(px)}px;`;
        if (fullWidth) style += 'width:100%;display:block;';
        return `<button type="submit" data-mailpurse-submit="1" style="${style}">${escHtml(text)}</button>`;
    };

    try {
        window.unlayer.registerTool({
            name: 'mp_input',
            label: 'Input',
            icon: 'fa-i-cursor',
            supportedDisplayModes: ['email', 'web', 'popup'],
            options: {
                field: {
                    title: 'Field',
                    position: 1,
                    options: {
                        fieldKey: { label: 'Field', defaultValue: 'email', widget: 'dropdown' },
                        placeholder: { label: 'Placeholder', defaultValue: '', widget: 'text' },
                        required: { label: 'Required', defaultValue: false, widget: 'toggle' },
                    },
                },
            },
            values: {},
            renderer: {
                Viewer: window.unlayer.createViewer({
                    render(values) {
                        const fk = values && values.fieldKey ? String(values.fieldKey) : 'email';
                        return `<div style='padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;'>Input: ${escHtml(fk)}</div>`;
                    },
                }),
                exporters: {
                    web: exportInput,
                    email: exportInput,
                    popup: exportInput,
                },
                head: { css() {}, js() {} },
            },
        });

        window.unlayer.registerTool({
            name: 'mp_textarea',
            label: 'Textarea',
            icon: 'fa-align-left',
            supportedDisplayModes: ['email', 'web', 'popup'],
            options: {
                field: {
                    title: 'Field',
                    position: 1,
                    options: {
                        fieldKey: { label: 'Field', defaultValue: '', widget: 'dropdown' },
                        placeholder: { label: 'Placeholder', defaultValue: '', widget: 'text' },
                        required: { label: 'Required', defaultValue: false, widget: 'toggle' },
                        rows: { label: 'Rows', defaultValue: 3, widget: 'number' },
                    },
                },
            },
            values: {},
            renderer: {
                Viewer: window.unlayer.createViewer({
                    render(values) {
                        const fk = values && values.fieldKey ? String(values.fieldKey) : '';
                        return `<div style='padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;'>Textarea: ${escHtml(fk)}</div>`;
                    },
                }),
                exporters: {
                    web: exportTextarea,
                    email: exportTextarea,
                    popup: exportTextarea,
                },
                head: { css() {}, js() {} },
            },
        });

        window.unlayer.registerTool({
            name: 'mp_submit',
            label: 'Submit Button',
            icon: 'fa-paper-plane',
            supportedDisplayModes: ['email', 'web', 'popup'],
            options: {
                content: {
                    title: 'Content',
                    position: 1,
                    options: {
                        text: { label: 'Text', defaultValue: 'Subscribe', widget: 'text' },
                    },
                },
                style: {
                    title: 'Style',
                    position: 2,
                    options: {
                        fullWidth: { label: 'Full Width', defaultValue: true, widget: 'toggle' },
                        backgroundColor: { label: 'Background', defaultValue: '#6366f1', widget: 'color_picker' },
                        textColor: { label: 'Text Color', defaultValue: '#ffffff', widget: 'color_picker' },
                        borderRadius: { label: 'Border Radius', defaultValue: 12, widget: 'number' },
                        paddingY: { label: 'Padding Y', defaultValue: 12, widget: 'number' },
                        paddingX: { label: 'Padding X', defaultValue: 16, widget: 'number' },
                    },
                },
            },
            values: {},
            renderer: {
                Viewer: window.unlayer.createViewer({
                    render(values) {
                        const t = values && values.text ? String(values.text) : 'Subscribe';
                        return `<div style='padding:10px;border:1px dashed #cbd5e1;border-radius:10px;font-size:13px;'>Submit Button: ${escHtml(t)}</div>`;
                    },
                }),
                exporters: {
                    web: exportSubmit,
                    email: exportSubmit,
                    popup: exportSubmit,
                },
                head: { css() {}, js() {} },
            },
        });

        window.__mailpurseUnlayerToolsRegistered = true;
    } catch (e) {
    }
}

function ensureMailpurseUnlayerToolsRegistered(retriesLeft = 80) {
    try {
        if (window.__mailpurseUnlayerToolsRegistered) {
            return;
        }
        if (!window.unlayer || typeof window.unlayer.registerTool !== 'function' || typeof window.unlayer.createViewer !== 'function') {
            if (retriesLeft <= 0) {
                return;
            }
            setTimeout(() => ensureMailpurseUnlayerToolsRegistered(retriesLeft - 1), 75);
            return;
        }
        registerMailpurseUnlayerTools();
    } catch (e) {
        // ignore
    }
}

function waitForMailpurseUnlayerToolsRegistered(timeoutMs = 6000) {
    const started = Date.now();

    return new Promise((resolve) => {
        const tick = () => {
            try {
                ensureMailpurseUnlayerToolsRegistered();
            } catch (e) {
                // ignore
            }

            if (window.__mailpurseUnlayerToolsRegistered) {
                resolve(true);
                return;
            }

            if (Date.now() - started >= timeoutMs) {
                resolve(false);
                return;
            }

            setTimeout(tick, 50);
        };

        tick();
    });
}

function readJsonScriptTag(scriptId) {
    if (!scriptId) {
        return null;
    }
    const el = document.getElementById(scriptId);
    if (!el) {
        return null;
    }
    const raw = (el.textContent || '').trim();
    if (!raw) {
        return null;
    }
    try {
        return JSON.parse(raw);
    } catch (e) {
        return null;
    }
}

function getMailpurseSupportedGoogleFontFamilies() {
    try {
        const raw = window.__mailpurseSupportedGoogleFontFamilies;
        if (!Array.isArray(raw)) {
            return [];
        }
        return raw
            .map((v) => (typeof v === 'string' ? v.trim() : ''))
            .filter((v) => v);
    } catch (e) {
        return [];
    }
}

function buildMailpurseUnlayerFontsConfig() {
    const families = getMailpurseSupportedGoogleFontFamilies();
    if (!families.length) {
        return null;
    }

    const buildGoogleFontUrl = (family) => {
        const encoded = encodeURIComponent(String(family || '').replace(/'/g, "\\'"));
        return `https://fonts.googleapis.com/css2?family=${encoded}:wght@400;500;600;700&display=swap`;
    };

    const makeWeights = () => ([
        { label: 'Regular', value: 400 },
        { label: 'Medium', value: 500 },
        { label: 'Semi Bold', value: 600 },
        { label: 'Bold', value: 700 },
    ]);

    const customFonts = families.map((family) => {
        const safeFamily = String(family).replace(/'/g, "\\'");
        return {
            label: String(family),
            value: `'${safeFamily}', sans-serif`,
            url: buildGoogleFontUrl(family),
            weights: makeWeights(),
        };
    });

    return {
        showDefaultFonts: true,
        customFonts,
    };
}

function setupUnlayerEditors() {
    const editorEls = document.querySelectorAll('[data-unlayer-editor]');
    if (!editorEls.length) {
        return;
    }

    const showUnlayerMountError = (el, message) => {
        if (!el) {
            return;
        }
        const safeMessage = message && String(message).trim()
            ? String(message).trim()
            : 'Failed to load the email builder.';
        el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;min-height:320px;padding:16px;"><div data-mailpurse-unlayer-mount-error style="max-width:560px;border:1px solid #fecaca;background:#fef2f2;color:#991b1b;border-radius:10px;padding:14px 16px;font:500 14px/1.4 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;">${safeMessage}</div></div>`;
    };

    ensureUnlayerScriptLoaded()
        .then(() => {
            editorEls.forEach((el) => {
                const id = el.getAttribute('id') || 'editor-container';
                const initKey = '__mailpurseUnlayerInitDone:' + id;

                if (window[initKey]) {
                    return;
                }

                const waitForVisibleAndInit = async () => {
                    const started = Date.now();
                    const tick = async () => {
                        try {
                            if (window[initKey]) {
                                return;
                            }

                            if (!el.isConnected) {
                                return;
                            }

                            // Turbo can fire turbo:load before layout settles; avoid init on 0-height containers.
                            // Note: position:fixed elements always have offsetParent===null — use offsetHeight only.
                            const isFixed = el.style.position === 'fixed' ||
                                (typeof getComputedStyle !== 'undefined' && getComputedStyle(el).position === 'fixed');
                            const isVisible = isFixed ? el.offsetHeight > 0 : !(el.offsetParent === null || el.offsetHeight === 0);
                            if (!isVisible) {
                                setTimeout(tick, 120);
                                return;
                            }

                            try {
                                const staleError = el.querySelector('[data-mailpurse-unlayer-mount-error]');
                                if (staleError) {
                                    el.innerHTML = '';
                                }
                            } catch (_) {
                            }

                            const projectIdRaw = el.getAttribute('data-unlayer-project-id');
                            const projectId = projectIdRaw ? Number(projectIdRaw) : null;
                            const displayMode = el.getAttribute('data-unlayer-display-mode') || 'email';

                            const options = {
                                id,
                                displayMode,
                                fonts: undefined,
                                editor: {
                                    autoSelectOnDrop: true,
                                },
                            };

                            const fontsCfg = buildMailpurseUnlayerFontsConfig();
                            if (fontsCfg) {
                                options.fonts = fontsCfg;
                            } else {
                                delete options.fonts;
                            }

                            if (projectId) {
                                options.projectId = projectId;
                            }

                            window.__mailpurseUnlayerReady = false;
                            let readyHandled = false;

                            const onReady = () => {
                                if (readyHandled) {
                                    return;
                                }
                                readyHandled = true;
                                window.__mailpurseUnlayerReady = true;

                                try {
                                    const loadingEl = el.querySelector('[data-unlayer-loading]');
                                    if (loadingEl) loadingEl.remove();
                                } catch (e) {}

                                const designScriptId = el.getAttribute('data-unlayer-design-script-id');
                                if (designScriptId) {
                                    const designEl = document.getElementById(designScriptId);
                                    const raw = designEl ? (designEl.textContent || '') : '';
                                    if (raw && raw.trim()) {
                                        try {
                                            const design = JSON.parse(raw);
                                            if (design && typeof design === 'object') {
                                                window.unlayer.loadDesign(design);
                                            }
                                        } catch (e) {
                                        }
                                    }
                                }

                                if (window.__mailpurseUnlayerPendingDesign && typeof window.__mailpurseUnlayerPendingDesign === 'object') {
                                    try {
                                        window.unlayer.loadDesign(window.__mailpurseUnlayerPendingDesign);
                                    } catch (e) {
                                    }
                                    window.__mailpurseUnlayerPendingDesign = null;
                                }
                            };

                            ensureMailpurseUnlayerToolsRegistered();

                            try {
                                window.unlayer.addEventListener('editor:ready', onReady);
                            } catch (e) {
                            }

                            window.unlayer.init(options);

                            try {
                                window.unlayer.addEventListener('editor:ready', onReady);
                            } catch (e) {
                            }

                            window[initKey] = true;
                        } catch (e) {
                            const elapsed = Date.now() - started;
                            if (elapsed < 20000) {
                                setTimeout(tick, 200);
                                return;
                            }
                            const message = (e && e.message) ? e.message : 'Unknown initialization error.';
                            showUnlayerMountError(el, `Failed to load builder: ${message}`);
                            try {
                                console.error('[MailPurse Unlayer] init failed', e);
                            } catch (_) {
                            }
                        }
                    };
                    tick();
                };

                waitForVisibleAndInit();
            });
        })
        .catch((e) => {
            const message = (e && e.message) ? e.message : 'Failed to load Unlayer embed.js.';
            editorEls.forEach((el) => {
                showUnlayerMountError(el, message);
            });
            try {
                console.error('[MailPurse Unlayer] embed script load failed', e);
            } catch (_) {
            }
        });
}

try {
    window.__mailpurseSetupUnlayerEditors = setupUnlayerEditors;
} catch (e) {
}

function setupUnlayerMessageBridge() {
    if (window.__mailpurseUnlayerMessageBridgeBound) {
        return;
    }
    window.__mailpurseUnlayerMessageBridgeBound = true;

    if (!Array.isArray(window.__mailpurseUnlayerToolsDebug)) {
        window.__mailpurseUnlayerToolsDebug = [];
    }

    window.addEventListener('message', (event) => {
        try {
            const data = event && event.data ? event.data : null;
            if (!data || typeof data.type !== 'string') {
                return;
            }

            if (data.type.startsWith('mailpurse:unlayer:')) {
                try {
                    window.__mailpurseUnlayerToolsDebug.push({
                        type: data.type,
                        payload: data.payload ?? null,
                        at: Date.now(),
                    });
                } catch (e) {
                    // ignore
                }
            }

            if (data.type === 'mailpurse:unlayer:tools-registered') {
                window.__mailpurseUnlayerToolsRegistered = true;
            }
        } catch (e) {
            // ignore
        }
    });
}

function setupUnlayerSaveBindings() {
    const form = document.getElementById('unlayer-form');
    const saveButton = document.getElementById('btn-save');

    if (!form || !saveButton) {
        return;
    }

    if (saveButton.dataset.unlayerEmbeddedHandled === '1' || saveButton.hasAttribute('data-unlayer-embedded-handled')) {
        return;
    }

    if (saveButton.dataset.unlayerBound === '1') {
        return;
    }
    saveButton.dataset.unlayerBound = '1';

    saveButton.addEventListener('click', () => {
        if (!window.unlayer || typeof window.unlayer.exportHtml !== 'function') {
            encodeHtmlContentForSubmit(form);
            form.submit();
            return;
        }

        if (window.__mailpurseUnlayerReady !== true) {
            encodeHtmlContentForSubmit(form);
            form.submit();
            return;
        }

        let submitted = false;
        const submitOnce = () => {
            if (submitted) {
                return;
            }
            submitted = true;
            encodeHtmlContentForSubmit(form);
            form.submit();
        };

        const exportFallbackTimer = setTimeout(() => {
            submitOnce();
        }, 8000);

        window.unlayer.exportHtml((data) => {
            const htmlInput = document.getElementById('html_content');
            const plainInput = document.getElementById('plain_text_content');
            const dataInput = document.getElementById('grapesjs_data');

            if (htmlInput) {
                htmlInput.value = (data && data.html) ? data.html : '';
            }

            if (dataInput) {
                dataInput.value = JSON.stringify((data && data.design) ? data.design : null);
            }

            const plainText = String((data && data.html) ? data.html : '')
                .replace(/<style[\s\S]*?<\/style>/gi, ' ')
                .replace(/<script[\s\S]*?<\/script>/gi, ' ')
                .replace(/<[^>]+>/g, ' ')
                .replace(/\s+/g, ' ')
                .trim();

            if (plainInput) {
                plainInput.value = plainText;
            }

            clearTimeout(exportFallbackTimer);
            submitOnce();
        });
    });
}

function teardownUnlayerEditorsBeforeCache() {
    const editorEls = document.querySelectorAll('[data-unlayer-editor]');
    editorEls.forEach((el) => {
        const id = el.getAttribute('id') || 'editor-container';
        const initKey = '__mailpurseUnlayerInitDone:' + id;
        try {
            delete window[initKey];
        } catch (e) {
        }
        el.innerHTML = '';
    });

    const saveButton = document.getElementById('btn-save');
    if (saveButton && saveButton.dataset) {
        delete saveButton.dataset.unlayerBound;
    }

    window.__mailpurseUnlayerReady = false;
}

function getToastTheme(type) {
    const t = String(type || '').toLowerCase();
    if (t === 'success') {
        return {
            iconBg: 'bg-white text-emerald-500',
            message: 'text-white',
            close: 'text-white/80 hover:text-white hover:bg-white/10',
            panel: 'bg-emerald-500 text-white',
            icon: '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.704 5.29a1 1 0 010 1.414l-7.13 7.13a1 1 0 01-1.414 0L4.29 9.963a1 1 0 111.414-1.414l3.163 3.163 6.423-6.422a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
        };
    }
    if (t === 'warning') {
        return {
            iconBg: 'bg-white text-amber-500',
            message: 'text-white',
            close: 'text-white/80 hover:text-white hover:bg-white/10',
            panel: 'bg-amber-500 text-white',
            icon: '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.721-1.36 3.486 0l6.518 11.59c.75 1.334-.214 3.011-1.742 3.011H3.48c-1.528 0-2.492-1.677-1.742-3.011l6.519-11.59zM11 14a1 1 0 10-2 0 1 1 0 002 0zm-1-2a.75.75 0 01-.75-.75V7a.75.75 0 011.5 0v4.25A.75.75 0 0110 12z" clip-rule="evenodd"/></svg>',
        };
    }
    if (t === 'error' || t === 'danger') {
        return {
            iconBg: 'bg-white text-red-500',
            message: 'text-white',
            close: 'text-white/80 hover:text-white hover:bg-white/10',
            panel: 'bg-red-500 text-white',
            icon: '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm2.53-10.53a.75.75 0 00-1.06-1.06L10 8.94 8.53 7.47a.75.75 0 10-1.06 1.06L8.94 10l-1.47 1.47a.75.75 0 101.06 1.06L10 11.06l1.47 1.47a.75.75 0 001.06-1.06L11.06 10l1.47-1.47z" clip-rule="evenodd"/></svg>',
        };
    }
    return {
        iconBg: 'bg-white text-sky-500',
        message: 'text-white',
        close: 'text-white/80 hover:text-white hover:bg-white/10',
        panel: 'bg-sky-500 text-white',
        icon: '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10A8 8 0 11 2 10a8 8 0 0116 0zM10 8a1 1 0 100-2 1 1 0 000 2zm-1 2a1 1 0 012 0v4a1 1 0 11-2 0v-4z" clip-rule="evenodd"/></svg>',
    };
}

function createToastElement(toast, onClose) {
    const t = toast && typeof toast === 'object' ? toast : {};
    const theme = getToastTheme(t.type);
    const messageText = typeof t.message === 'string' ? t.message : '';

    const wrapper = document.createElement('div');
    wrapper.className = 'relative w-full rounded-2xl shadow-lg';

    const panel = document.createElement('div');
    panel.className = `backdrop-blur ${theme.panel} rounded-lg px-3 py-2 transition duration-300 ease-out opacity-0 translate-y-4`;

    const row = document.createElement('div');
    row.className = 'flex items-center gap-3';

    const iconWrap = document.createElement('div');
    iconWrap.className = `shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${theme.iconBg}`;
    iconWrap.innerHTML = theme.icon;

    const content = document.createElement('div');
    content.className = 'flex-1 min-w-0';

    if (messageText) {
        const msg = document.createElement('div');
        msg.className = `text-sm sm:text-base leading-6 font-medium whitespace-pre-line ${theme.message}`;
        msg.textContent = messageText;
        content.appendChild(msg);
    }

    const closeBtn = document.createElement('button');
    closeBtn.type = 'button';
    closeBtn.className = `shrink-0 -mr-1 p-2 rounded-lg transition-colors ${theme.close}`;
    closeBtn.setAttribute('aria-label', 'Close');
    closeBtn.innerHTML = '<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>';

    row.appendChild(iconWrap);
    row.appendChild(content);
    row.appendChild(closeBtn);
    panel.appendChild(row);
    wrapper.appendChild(panel);

    const animateIn = () => {
        requestAnimationFrame(() => {
            panel.classList.remove('opacity-0', 'translate-y-4');
            panel.classList.add('opacity-100', 'translate-y-0');
        });
    };

    const close = () => {
        panel.classList.remove('opacity-100', 'translate-y-0');
        panel.classList.add('opacity-0', 'translate-y-4');
        window.setTimeout(() => {
            wrapper.remove();
            if (typeof onClose === 'function') {
                onClose();
            }
        }, 220);
    };

    closeBtn.addEventListener('click', close);
    animateIn();

    return { el: wrapper, close };
}

function showToast(toast, opts = {}) {
    const root = document.querySelector('[data-toast-root]');
    if (!root) {
        return;
    }

    const durationMs = Number(opts.durationMs ?? toast?.durationMs) || 4500;

    const created = createToastElement(toast, null);
    root.appendChild(created.el);

    if (durationMs > 0) {
        window.setTimeout(() => created.close(), durationMs);
    }
}

function setupToasts() {
    const root = document.querySelector('[data-toast-root]');
    if (!root) {
        return;
    }

    if (typeof window.mailpurseToast !== 'function') {
        window.mailpurseToast = (toast, opts = {}) => showToast(toast, opts);
    }

    const toasts = window.__mailpursePageToasts;
    if (Array.isArray(toasts) && toasts.length) {
        toasts.forEach((t) => showToast(t));
    }

    try {
        window.__mailpursePageToasts = [];
    } catch (e) {
    }
}

const runAppBoot = () => {
    setupNotificationPolling();
    setupToasts();
    setupCustomConfirmModal();
    setupUnlayerMessageBridge();
    setupUnlayerEditors();
    setupUnlayerSaveBindings();
};

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

document.addEventListener('turbo:before-cache', () => {
    saveSidebarScrollPosition();

    if (window.Alpine && typeof window.Alpine.destroyTree === 'function') {
        window.Alpine.destroyTree(document.body);
    }

    const toastRoot = document.querySelector('[data-toast-root]');
    if (toastRoot) {
        toastRoot.innerHTML = '';
    }

    teardownUnlayerEditorsBeforeCache();
});

document.addEventListener('turbo:load', () => {
    if (window.Alpine && typeof window.Alpine.initTree === 'function') {
        if (!document.documentElement.__x) {
            window.Alpine.initTree(document.documentElement);
        }
        window.Alpine.initTree(document.body);
    }
    runAppBoot();
});