/**
 * Consolety Campaigns JavaScript
 *
 * Handles the Campaigns page functionality:
 * - Tab switching
 * - Loading campaigns, my campaigns, my claims
 * - Creating campaigns (Free: max 2 active, Pro: unlimited)
 * - Claiming campaigns
 * - Accepting/Rejecting claims
 * - Withdrawing campaigns
 */
(function($) {
    'use strict';

    // State
    let currentCampaigns = [];
    let myCampaigns = [];
    let myClaims = [];
    let currentCampaignId = null;

    // Config
    const config = window.consoletyCampaigns || {};
    const ajaxUrl = config.ajaxUrl || '/wp-admin/admin-ajax.php';
    const nonce = config.nonce || '';
    const isPro = config.isPro || false;
    const i18n = config.i18n || {};

    // ==========================================
    // Error Helpers
    // ==========================================

    /**
     * Show error state in container (XSS-safe)
     */
    function showError($container, message) {
        const $error = $('<div class="vp-empty-state"><span class="dashicons dashicons-warning"></span><p></p></div>');
        $error.find('p').text(message);
        $container.html($error);
    }

    /**
     * Map API error codes to user-friendly messages
     */
    function mapErrorCode(code, fallback) {
        const map = {
            'TOKEN_REQUIRED': 'Please connect your site first.',
            'UNAUTHORIZED': 'Connection expired. Please reconnect.',
            'INVALID_TOKEN': 'Token invalid. Please reconnect.',
            'INSUFFICIENT_CREDITS': 'Not enough points for this action.',
            'SITE_NOT_FOUND': 'Site not registered. Please reconnect.',
            'REQUEST_FAILED': 'Network error. Check your connection.',
            'API_ERROR': 'Server error. Please try again later.'
        };
        return map[code] || fallback || 'An error occurred';
    }

    // ==========================================
    // Category Row Management (dropdown-based)
    // ==========================================

    const campaignCategoriesCache = config.categories || [];
    const MAX_CATEGORY_ROWS = 2;

    /**
     * Populate subcategory dropdown based on main category selection
     */
    function populateCampaignSubcategories($row, mainCatId) {
        const $subSelect = $row.find('.campaign-sub-cat');
        $subSelect.empty().append(
            '<option value="">' + escapeHtml(i18n.subcategoryOptional || 'Subcategory (optional)') + '</option>'
        );

        if (!mainCatId) {
            $subSelect.prop('disabled', true);
            return;
        }

        const parent = campaignCategoriesCache.find(function(c) { return c.id == mainCatId; });
        const children = (parent && parent.children) ? parent.children : [];

        if (children.length > 0) {
            children.forEach(function(sub) {
                $subSelect.append(
                    '<option value="' + sub.id + '">' + escapeHtml(sub.name) + '</option>'
                );
            });
            $subSelect.prop('disabled', false);
        } else {
            $subSelect.prop('disabled', true);
        }
    }

    /**
     * Add a new category row (max 2)
     */
    function addCategoryRow() {
        const existingRows = $('.vp-category-row').length;
        if (existingRows >= MAX_CATEGORY_ROWS) return;

        const rowNum = existingRows + 1;
        const mainOptions = $('.vp-category-row:first .campaign-main-cat').html();

        const rowHtml = '<div class="vp-category-row" data-row="' + rowNum + '">' +
            '<select class="vp-select campaign-main-cat" data-row="' + rowNum + '">' + mainOptions + '</select>' +
            '<select class="vp-select campaign-sub-cat" data-row="' + rowNum + '" disabled>' +
            '<option value="">' + escapeHtml(i18n.subcategoryOptional || 'Subcategory (optional)') + '</option></select>' +
            '<button type="button" class="vp-btn vp-btn-ghost vp-category-remove" data-row="' + rowNum + '" title="' + escapeHtml(i18n.remove || 'Remove') + '">' +
            '<span class="dashicons dashicons-no-alt"></span></button>' +
            '</div>';

        $('#campaign-category-rows').append(rowHtml);

        // Reset the new row's main category
        var $newRow = $('.vp-category-row[data-row="' + rowNum + '"]');
        $newRow.find('.campaign-main-cat').val('');

        updateCategoryRowState();
    }

    /**
     * Update visibility of add/remove buttons
     */
    function updateCategoryRowState() {
        var rows = $('.vp-category-row');
        var count = rows.length;

        // Show/hide add button
        if (count >= MAX_CATEGORY_ROWS) {
            $('#add-category-row').hide();
        } else {
            $('#add-category-row').show();
        }

        // Show remove button only if more than 1 row
        rows.each(function() {
            $(this).find('.vp-category-remove').toggle(count > 1);
        });
    }

    /**
     * Collect structured category data for API (flat array for backward compat)
     */
    function collectCategoryData() {
        var filterCategories = [];
        $('.vp-category-row').each(function() {
            var mainId = $(this).find('.campaign-main-cat').val();
            var subId = $(this).find('.campaign-sub-cat').val();
            if (mainId) {
                filterCategories.push(mainId);
                if (subId) {
                    filterCategories.push(subId);
                }
            }
        });
        return filterCategories;
    }

    /**
     * Get user-friendly message from XHR error
     */
    function getXhrErrorMessage(xhr) {
        // WP nonce failure returns -1 or 0
        if (xhr.responseText === '-1' || xhr.responseText === '0') {
            return 'Session expired. Please refresh the page.';
        }

        if (xhr.status === 401) return 'Connection expired. Please reconnect your site.';
        if (xhr.status === 403) return 'Access denied. Please refresh the page.';
        if (xhr.status >= 500) return 'Server error. Please try again later.';
        if (xhr.status === 0) return 'Unable to reach server. Check your internet connection.';

        return 'Network error. Check your connection.';
    }

    // ==========================================
    // Word Count & Link Detection (TinyMCE + textarea fallback)
    // ==========================================

    function extractCampaignLinks() {
        // Get content from TinyMCE or fallback to textarea
        let content = '';
        if (typeof tinymce !== 'undefined' && tinymce.get('campaign-content')) {
            content = tinymce.get('campaign-content').getContent();
        } else {
            content = $('#campaign-content').val() || '';
        }

        // Word count with visual feedback
        const text = content.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
        const words = text ? text.split(' ').length : 0;
        const minWords = 100; // Campaigns minimum

        const $wordCount = $('#campaign-word-count');
        $wordCount.text(words + ' / ' + minWords + ' words');

        if (words < minWords) {
            $wordCount.removeClass('vp-word-count-ok').addClass('vp-word-count-low');
        } else {
            $wordCount.removeClass('vp-word-count-low').addClass('vp-word-count-ok');
        }

        // Link detection using DOM parser (handles bold/italic anchor text)
        const $list = $('#campaign-links-list');
        const links = extractLinksFromHtml(content);

        if (links.length > 0) {
            let html = '';
            links.forEach(function(link) {
                const displayUrl = link.url.length > 50 ? link.url.substring(0, 50) + '...' : link.url;
                html += '<div class="vp-link-item">';
                html += '<span class="vp-link-anchor">"' + escapeHtml(link.anchor) + '"</span>';
                html += '<span class="vp-link-url">&rarr; ' + escapeHtml(displayUrl) + '</span>';
                html += '</div>';
            });
            $list.html(html);
        } else {
            $list.html('<p class="vp-no-links">' + (i18n.noLinks || 'No links detected yet.') + '</p>');
        }
    }

    // ==========================================
    // TinyMCE Integration
    // ==========================================

    function initCampaignTinyMCE() {
        if (typeof tinymce !== 'undefined' && tinymce.get('campaign-content')) {
            const editor = tinymce.get('campaign-content');
            if (!editor.initialized) return false;
            editor.on('keyup change NodeChange input paste', extractCampaignLinks);
            extractCampaignLinks(); // Initial call
            return true;
        }
        return false;
    }

    // Poll for TinyMCE initialization (editor may load after script)
    let campaignMcePollCount = 0;
    const campaignMcePoll = setInterval(function() {
        campaignMcePollCount++;
        if (initCampaignTinyMCE() || campaignMcePollCount > 20) {
            clearInterval(campaignMcePoll);
        }
    }, 500);

    // ==========================================
    // Initialization
    // ==========================================

    $(document).ready(function() {
        initTabs();
        initModals();
        loadDiscoverCampaigns();

        // Bind events
        $('#create-campaign-btn').on('click', openCreateCampaignModal);
        $('#cancel-create-campaign').on('click', closeCreateCampaignModal);
        $('#submit-create-campaign').on('click', submitCreateCampaign);
        $('#cancel-claim-campaign').on('click', closeClaimModal);
        $('#submit-claim-campaign').on('click', submitClaimCampaign);
        $('#submit-publish-campaign').on('click', submitPublishCampaign);

        // Textarea word count and link detection
        $('#campaign-content').on('input keyup', extractCampaignLinks);

        // Category row management (dropdown-based, max 2 rows)
        $(document).on('change', '.campaign-main-cat', function() {
            const $row = $(this).closest('.vp-category-row');
            populateCampaignSubcategories($row, $(this).val());
        });

        $('#add-category-row').on('click', function() {
            addCategoryRow();
        });

        $(document).on('click', '.vp-category-remove', function() {
            $(this).closest('.vp-category-row').remove();
            updateCategoryRowState();
        });

        updateCategoryRowState();
    });

    // ==========================================
    // Tab Switching
    // ==========================================

    function initTabs() {
        // Use same tab selectors as network.php for consistency
        $('.vp-tabs-row .vp-tab').on('click', function(e) {
            e.preventDefault();
            const tab = $(this).data('tab');
            switchTab(tab);
        });
    }

    function switchTab(tab) {
        // Update tab buttons (consistent with network.php)
        $('.vp-tabs-row .vp-tab').removeClass('active').attr('aria-selected', 'false');
        $(`.vp-tabs-row .vp-tab[data-tab="${tab}"]`).addClass('active').attr('aria-selected', 'true');

        // Update tab content (consistent with network.php: #tab-{name})
        $('.vp-tab-content').hide();
        $(`#tab-${tab}`).show();

        // Load data for tab
        switch (tab) {
            case 'discover':
                loadDiscoverCampaigns();
                break;
            case 'my-campaigns':
                loadMyCampaigns();
                break;
            case 'my-claims':
                loadMyClaims();
                break;
        }
    }

    // ==========================================
    // Load Data
    // ==========================================

    function loadDiscoverCampaigns() {
        const $container = $('#campaigns-list');
        $container.html(`<div class="vp-loading-state"><span class="spinner is-active"></span> ${i18n.loading || 'Loading...'}</div>`);

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_get_campaigns',
                nonce: nonce
            },
            success: function(response) {
                if (response.success) {
                    currentCampaigns = response.data || [];
                    renderDiscoverCampaigns(currentCampaigns);
                } else {
                    // Map error code to user-friendly message
                    const code = response.data?.code || 'UNKNOWN';
                    const msg = mapErrorCode(code, response.data?.message);
                    showError($container, msg);
                }
            },
            error: function(xhr) {
                showError($container, getXhrErrorMessage(xhr));
            }
        });
    }

    function loadMyCampaigns() {
        const $container = $('#my-campaigns-list');
        $container.html(`<div class="vp-loading-state"><span class="spinner is-active"></span> ${i18n.loading || 'Loading...'}</div>`);

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_get_my_campaigns',
                nonce: nonce
            },
            success: function(response) {
                if (response.success) {
                    myCampaigns = response.data || [];
                    renderMyCampaigns(myCampaigns);
                } else {
                    const code = response.data?.code || 'UNKNOWN';
                    const msg = mapErrorCode(code, response.data?.message);
                    showError($container, msg);
                }
            },
            error: function(xhr) {
                showError($container, getXhrErrorMessage(xhr));
            }
        });
    }

    function loadMyClaims() {
        const $container = $('#my-claims-list');
        $container.html(`<div class="vp-loading-state"><span class="spinner is-active"></span> ${i18n.loading || 'Loading...'}</div>`);

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_get_my_claims',
                nonce: nonce
            },
            success: function(response) {
                if (response.success) {
                    myClaims = response.data || [];
                    renderMyClaims(myClaims);
                    // Sync points after loading claims; claim status may have changed remotely.
                    updatePointsBalance();
                } else {
                    const code = response.data?.code || 'UNKNOWN';
                    const msg = mapErrorCode(code, response.data?.message);
                    showError($container, msg);
                }
            },
            error: function(xhr) {
                showError($container, getXhrErrorMessage(xhr));
            }
        });
    }

    // ==========================================
    // Render Functions
    // ==========================================

    function renderDiscoverCampaigns(campaigns) {
        const $container = $('#campaigns-list');

        if (!campaigns || campaigns.length === 0) {
            $container.html(`
                <div class="vp-empty-state">
                    <span class="dashicons dashicons-megaphone"></span>
                    <p>${i18n.noCampaigns || 'No campaigns available yet.'}</p>
                </div>
            `);
            return;
        }

        let html = '';
        campaigns.forEach(function(campaign) {
            const matchPct = campaign.match?.percentage || 0;
            const matchClass = matchPct >= 50 ? 'vp-match-good' : 'vp-match-warning';
            const matchIcon = matchPct >= 50 ? 'yes' : 'warning';
            const matchText = matchPct + '% match';
            const badgeCount = campaign.match?.badges?.length || 0;
            const localeText = campaign.locale ? formatLocale(campaign.locale) : '';

            html += `
                <div class="vp-campaign-card" data-campaign-id="${campaign.id}">
                    <h3>${escapeHtml(campaign.title)}</h3>
                    <div class="vp-campaign-meta">
                        <span><span class="dashicons dashicons-admin-site"></span> ${escapeHtml(campaign.creator?.domain || 'Unknown')}</span>
                        <span><span class="dashicons dashicons-chart-bar"></span> ${formatNumber(campaign.creator?.monthly_clicks || 0)} ${i18n.clicks || 'clicks/month'}</span>
                        ${localeText ? '<span><span class="dashicons dashicons-translation"></span> ' + escapeHtml(localeText) + '</span>' : ''}
                    </div>
                    <div class="vp-campaign-excerpt">${escapeHtml(campaign.content_preview || '')}</div>
                    <div class="vp-campaign-footer">
                        <div class="vp-match-indicator ${matchClass}">
                            <span class="dashicons dashicons-${matchIcon}"></span>
                            <span>${matchText}</span>
                            ${badgeCount > 0 ? '<span style="margin-left:4px;opacity:0.7;">(' + badgeCount + ' signals)</span>' : ''}
                        </div>
                        <button type="button" class="vp-btn vp-btn-primary view-campaign-btn" data-campaign-id="${campaign.id}">
                            ${i18n.viewAndClaim || 'View & Claim'}
                        </button>
                    </div>
                </div>
            `;
        });

        $container.html(html);

        // Bind view buttons
        $container.find('.view-campaign-btn').on('click', function() {
            const campaignId = $(this).data('campaign-id');
            openViewCampaignModal(campaignId, false);
        });
    }

    function renderMyCampaigns(campaigns) {
        const $container = $('#my-campaigns-list');

        if (!campaigns || campaigns.length === 0) {
            let emptyHtml = `
                <div class="vp-empty-state">
                    <span class="dashicons dashicons-edit-page"></span>
                    <p>${i18n.noMyCampaigns || "You haven't created any campaigns yet."}</p>
            `;
            emptyHtml += `<p>${i18n.createFirst || 'Create your first campaign to start receiving publishers.'}</p>`;
            emptyHtml += '</div>';
            $container.html(emptyHtml);
            return;
        }

        let html = '';
        campaigns.forEach(function(campaign) {
            const statusClass = `vp-status-${campaign.status}`;
            const statusHtml = getStatusHtml(campaign.status);
            const expiresText = campaign.expires_at ? getExpiresText(campaign.expires_at) : '';

            html += `
                <div class="vp-campaign-card" data-campaign-id="${campaign.id}">
                    <h3>${escapeHtml(campaign.title)}</h3>
                    <div class="vp-campaign-meta">
                        <span><span class="dashicons dashicons-calendar-alt"></span> ${formatDate(campaign.created_at)}</span>
                        <span><span class="dashicons dashicons-groups"></span> ${campaign.claims_count || 0} ${i18n.claims || 'claims'}</span>
                        ${expiresText ? `<span><span class="dashicons dashicons-clock"></span> ${expiresText}</span>` : ''}
                    </div>
                    <div class="vp-campaign-footer">
                        <span class="vp-campaign-status ${statusClass}">${statusHtml}</span>
                        <div class="vp-campaign-actions">
                            ${campaign.published_url && isValidHttpUrl(campaign.published_url) ? `<a href="${escapeHtml(campaign.published_url)}" target="_blank" rel="noopener" class="vp-btn vp-btn-outline" style="font-size: 12px;"><span class="dashicons dashicons-external" style="font-size:14px;width:14px;height:14px;"></span> View Article</a>` : ''}
                            ${campaign.status === 'open' && campaign.claims_count > 0 ? `<button type="button" class="vp-btn vp-btn-outline review-claims-btn" data-campaign-id="${campaign.id}">Review Claims</button>` : ''}
                            ${campaign.status === 'open' ? `<button type="button" class="vp-btn vp-btn-outline withdraw-campaign-btn" data-campaign-id="${campaign.id}">Withdraw</button>` : ''}
                            <button type="button" class="vp-btn vp-btn-outline view-my-campaign-btn" data-campaign-id="${campaign.id}">View</button>
                            ${campaign.status === 'expired' || campaign.status === 'withdrawn' ? `<button type="button" class="vp-btn vp-btn-outline dismiss-campaign-btn" data-campaign-id="${campaign.id}" title="Dismiss"><span class="dashicons dashicons-no-alt"></span></button>` : ''}
                        </div>
                    </div>
                </div>
            `;
        });

        $container.html(html);

        // Bind buttons
        $container.find('.view-my-campaign-btn').on('click', function() {
            const campaignId = $(this).data('campaign-id');
            openViewCampaignModal(campaignId, true);
        });

        $container.find('.review-claims-btn').on('click', function() {
            const campaignId = $(this).data('campaign-id');
            openReviewClaimsModal(campaignId);
        });

        $container.find('.withdraw-campaign-btn').on('click', function() {
            const campaignId = $(this).data('campaign-id');
            confirmWithdrawCampaign(campaignId);
        });

        $container.find('.dismiss-campaign-btn').on('click', function() {
            dismissCampaign($(this).data('campaign-id'), $(this).closest('.vp-campaign-card'));
        });
    }

    function renderMyClaims(claims) {
        const $container = $('#my-claims-list');

        if (!claims || claims.length === 0) {
            $container.html(`
                <div class="vp-empty-state">
                    <span class="dashicons dashicons-clipboard"></span>
                    <p>${i18n.noMyClaims || "You haven't claimed any campaigns yet."}</p>
                    <p>${i18n.browseDiscover || 'Browse the Discover tab to find articles to publish.'}</p>
                </div>
            `);
            return;
        }

        let html = '';
        claims.forEach(function(claim) {
            const statusClass = `vp-status-${claim.status}`;
            const statusHtml = getStatusHtml(claim.status);

            html += `
                <div class="vp-claim-card" data-claim-id="${claim.id}">
                    <div class="vp-claim-header">
                        <div>
                            <div class="vp-claim-title">${escapeHtml(claim.campaign?.title || 'Campaign')}</div>
                            <div class="vp-claim-stats">
                                <span class="dashicons dashicons-admin-site"></span> ${escapeHtml(claim.campaign?.creator_domain || 'Unknown')}
                                ${claim.claimed_at ? `<span class="vp-claim-date"><span class="dashicons dashicons-calendar-alt"></span> Claimed ${formatDate(claim.claimed_at)}</span>` : ''}
                                ${claim.published_at ? `<span class="vp-claim-date"><span class="dashicons dashicons-yes-alt"></span> Published ${formatDate(claim.published_at)}</span>` : ''}
                            </div>
                        </div>
                        <span class="vp-campaign-status ${statusClass}">${statusHtml}</span>
                    </div>
                    ${claim.pitch_text ? `<div class="vp-claim-pitch">"${escapeHtml(claim.pitch_text)}"</div>` : ''}
                    ${claim.status === 'accepted' ? `
                        <div class="vp-claim-actions">
                            <button type="button" class="vp-btn vp-btn-primary publish-campaign-btn"
                                data-campaign-id="${claim.campaign?.id || ''}"
                                data-claim-id="${claim.id}"
                                data-campaign-title="${escapeHtml(claim.campaign?.title || '')}">
                                <span class="dashicons dashicons-upload"></span> ${i18n.publishArticle || 'Publish Article'}
                            </button>
                        </div>
                    ` : ''}
                    ${claim.published_url && isValidHttpUrl(claim.published_url) ? `<div class="vp-claim-published"><a href="${escapeHtml(claim.published_url)}" target="_blank" rel="noopener">${i18n.viewPublished || 'View Published Article'}</a></div>` : ''}
                    ${claim.status === 'rejected' ? `<div class="vp-claim-actions" style="margin-top: 8px;"><button type="button" class="vp-btn vp-btn-outline dismiss-claim-btn" data-claim-id="${claim.id}" title="Dismiss"><span class="dashicons dashicons-no-alt"></span> Dismiss</button></div>` : ''}
                </div>
            `;
        });

        $container.html(html);

        // Bind publish button click events
        $container.find('.publish-campaign-btn').on('click', function() {
            openPublishCampaignModal(
                $(this).data('campaign-id'),
                $(this).data('claim-id'),
                $(this).data('campaign-title')
            );
        });

        // Bind dismiss claim button
        $container.find('.dismiss-claim-btn').on('click', function() {
            dismissClaim($(this).data('claim-id'), $(this).closest('.vp-claim-card'));
        });
    }

    // ==========================================
    // Modals
    // ==========================================

    function initModals() {
        // Close modal on backdrop click - ONLY for view/confirm modals, NOT form modals
        // Form modals (create-campaign, claim-campaign) should only close via X/Cancel buttons
        $(document).on('click', '.vp-modal-overlay', function(e) {
            // Only close if clicking the overlay itself (not modal content)
            if ($(e.target).hasClass('vp-modal-overlay')) {
                const modalId = $(this).attr('id');
                // Whitelist: only these modals close on backdrop click
                const backdropCloseModals = ['view-campaign-modal', 'vp-confirm-modal', 'review-claims-modal', 'publish-campaign-modal'];
                if (backdropCloseModals.includes(modalId)) {
                    $(this).hide();
                }
            }
        });

        // Close on X button click (always works)
        $(document).on('click', '.vp-modal-backdrop-close', function() {
            $(this).closest('.vp-modal-overlay').hide();
            $('body').removeClass('vp-modal-open');
        });

        // Close on ESC key - same whitelist as backdrop click
        $(document).on('keydown', function(e) {
            if (e.key === 'Escape') {
                const backdropCloseModals = ['view-campaign-modal', 'vp-confirm-modal', 'review-claims-modal', 'publish-campaign-modal'];
                backdropCloseModals.forEach(function(id) {
                    $('#' + id).hide();
                });
            }
        });
    }

    function openCreateCampaignModal() {
        $('#create-campaign-form')[0].reset();

        // Reset TinyMCE content
        if (typeof tinymce !== 'undefined' && tinymce.get('campaign-content')) {
            tinymce.get('campaign-content').setContent('');
        }
        $('#campaign-content').val('');

        // Reset word count with visual state
        $('#campaign-word-count')
            .text('0 / 100 words')
            .removeClass('vp-word-count-ok vp-word-count-low');

        $('#campaign-links-list').html('<p class="vp-no-links">No links detected yet.</p>');

        // Reset category rows: remove extra rows, reset first row
        $('.vp-category-row:not(:first)').remove();
        $('.vp-category-row:first .campaign-main-cat').val('');
        $('.vp-category-row:first .campaign-sub-cat')
            .empty()
            .append('<option value="">' + escapeHtml(i18n.subcategoryOptional || 'Subcategory (optional)') + '</option>')
            .prop('disabled', true);
        updateCategoryRowState();

        // Pre-select locale from WordPress locale if it matches a Hub-supported option
        var wpLocale = consoletyCampaigns.wpLocale || '';
        var $localeSelect = $('#campaign-locale');
        if (wpLocale && $localeSelect.find('option[value="' + wpLocale + '"]').length) {
            $localeSelect.val(wpLocale);
        } else {
            $localeSelect.val('');
        }

        $('#create-campaign-modal').show();
        $('body').addClass('vp-modal-open');

        // Re-init TinyMCE event binding
        setTimeout(function() {
            initCampaignTinyMCE();
        }, 100);
    }

    function closeCreateCampaignModal() {
        $('#create-campaign-modal').hide();
        $('body').removeClass('vp-modal-open');
    }

    function openViewCampaignModal(campaignId, isOwner) {
        currentCampaignId = campaignId;
        const $modal = $('#view-campaign-modal');
        const $body = $('#view-campaign-body');
        const $footer = $('#view-campaign-footer');

        $body.html(`<div class="vp-loading-state"><span class="spinner is-active"></span> ${i18n.loading || 'Loading...'}</div>`);
        $footer.html('');
        $modal.show();

        // Fetch campaign details
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_get_campaign',
                nonce: nonce,
                campaign_id: campaignId
            },
            success: function(response) {
                if (response.success) {
                    renderCampaignDetail(response.data, isOwner);
                } else {
                    // XSS-safe: Use .text() for server messages
                    const $error = $(`<div class="vp-empty-state"><p></p></div>`);
                    $error.find('p').text(response.data?.message || 'Failed to load campaign');
                    $body.html($error);
                }
            },
            error: function(xhr) {
                showError($body, getXhrErrorMessage(xhr));
            }
        });
    }

    function renderCampaignDetail(campaign, isOwner) {
        const $body = $('#view-campaign-body');
        const $footer = $('#view-campaign-footer');
        const $title = $('#view-campaign-title');

        $title.text(campaign.title);

        // Sanitize content with strict DOMPurify config (no style attrs, no data-attrs)
        const sanitizedContent = typeof DOMPurify !== 'undefined'
            ? DOMPurify.sanitize(campaign.content || '', window.VP_DOMPURIFY_CONFIG || {})
            : escapeHtml(campaign.content || '');

        let html = `
            <div class="vp-campaign-detail-creator">
                <strong>Created by:</strong> ${escapeHtml(campaign.creator?.domain || 'Unknown')}
                <span style="margin-left: 16px;"><span class="dashicons dashicons-chart-bar"></span> ${formatNumber(campaign.creator?.monthly_clicks || 0)} clicks/month</span>
            </div>
        `;

        if (campaign.target_url && isValidHttpUrl(campaign.target_url)) {
            html += `<p><strong>Target URL:</strong> <a href="${escapeHtml(campaign.target_url)}" target="_blank" rel="noopener">${escapeHtml(campaign.target_url)}</a></p>`;
        } else if (campaign.target_url) {
            // Show URL as text only if protocol is invalid (security measure)
            html += `<p><strong>Target URL:</strong> ${escapeHtml(campaign.target_url)}</p>`;
        }

        html += `
            <div class="vp-campaign-detail-content">
                ${sanitizedContent}
            </div>
        `;

        // Show locale and category filters
        const hasLocale = campaign.locale;
        const hasCategories = campaign.filters?.categories?.length;

        if (hasLocale || hasCategories) {
            html += '<div class="vp-campaign-detail-filters"><strong>Publisher Preferences:</strong><div class="vp-filter-tags">';
            if (hasLocale) {
                html += `<span class="vp-filter-tag"><span class="dashicons dashicons-translation" style="font-size:14px;width:14px;height:14px;margin-right:4px;"></span>${escapeHtml(formatLocale(campaign.locale))}</span>`;
            }
            if (hasCategories) {
                campaign.filters.categories.forEach(cat => {
                    const name = typeof cat === 'object' ? cat.name : ('Category #' + cat);
                    html += `<span class="vp-filter-tag"><span class="dashicons dashicons-category" style="font-size:14px;width:14px;height:14px;margin-right:4px;"></span>${escapeHtml(name)}</span>`;
                });
            }
            html += '</div></div>';
        }

        // Show match info with badges for non-owners
        if (!isOwner && campaign.match) {
            const pct = campaign.match.percentage || 0;
            const badges = campaign.match.badges || [];
            const warnings = campaign.match.warnings || [];
            const matchClass = pct >= 50 ? 'vp-match-good' : 'vp-match-warning';

            html += '<div class="vp-match-detail">';
            html += `<div class="vp-match-score-label"><span class="dashicons dashicons-${pct >= 50 ? 'yes' : 'warning'}"></span> Match Score: ${pct}/100</div>`;

            if (badges.length) {
                html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;">';
                badges.forEach(badge => {
                    const colorClass = badge.color === 'green' ? 'vp-badge-green'
                                     : badge.color === 'blue' ? 'vp-badge-blue'
                                     : badge.color === 'orange' ? 'vp-badge-orange'
                                     : 'vp-badge-red';
                    html += `<span class="vp-badge-match ${colorClass}">${escapeHtml(badge.label)}</span>`;
                });
                html += '</div>';
            }

            if (warnings.length) {
                html += '<ul style="margin: 10px 0 0 16px; color: var(--vp-text-muted, #64748b); font-size: 13px;">';
                warnings.forEach(w => {
                    html += `<li>${escapeHtml(w)}</li>`;
                });
                html += '</ul>';
            }

            html += '</div>';
        }

        $body.html(html);

        // Footer buttons
        let footerHtml = '<button type="button" class="vp-btn vp-btn-secondary vp-modal-backdrop-close">Close</button>';

        if (!isOwner && campaign.status === 'open') {
            // Check if already claimed
            if (campaign.my_claim) {
                footerHtml += `<span class="vp-campaign-status vp-status-${campaign.my_claim.status}" style="margin-left: 8px;">${getStatusHtml(campaign.my_claim.status)} - Already claimed</span>`;
            } else {
                footerHtml += `<button type="button" class="vp-btn vp-btn-primary" id="claim-this-campaign">Claim Campaign (5 pts)</button>`;
            }
        }

        $footer.html(footerHtml);

        // Bind claim button
        $('#claim-this-campaign').on('click', function() {
            $('#view-campaign-modal').hide();
            openClaimModal(currentCampaignId);
        });
    }

    function openClaimModal(campaignId) {
        currentCampaignId = campaignId;
        $('#claim-campaign-id').val(campaignId);
        $('#claim-pitch').val('');
        $('#claim-campaign-modal').show();
    }

    function closeClaimModal() {
        $('#claim-campaign-modal').hide();
    }

    // ==========================================
    // Publish Campaign Modal (publisher-side)
    // ==========================================

    function openPublishCampaignModal(campaignId, claimId, title) {
        $('#publish-campaign-id').val(campaignId);
        $('#publish-claim-id').val(claimId);
        $('#publish-campaign-title-display').text(title || 'Campaign Article');
        $('#publish-campaign-modal').show();
    }

    function submitPublishCampaign() {
        var $btn = $('#submit-publish-campaign');
        var campaignId = $('#publish-campaign-id').val();
        var claimId = $('#publish-claim-id').val();
        var categoryId = $('#publish-campaign-category').val();

        if (!campaignId || !claimId) {
            showNotice('error', 'Missing campaign or claim ID');
            return;
        }

        $btn.prop('disabled', true).html('<span class="spinner is-active"></span> ' + (i18n.publishing || 'Publishing...'));

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_publish_campaign',
                nonce: nonce,
                campaign_id: campaignId,
                claim_id: claimId,
                category_id: categoryId
            },
            success: function(response) {
                if (response.success) {
                    $('#publish-campaign-modal').hide();
                    var msg = response.data?.message || 'Article published!';
                    if (response.data?.credits_earned) {
                        msg += ' (+' + response.data.credits_earned + ' points earned)';
                    }
                    showNotice('success', msg);

                    // Add "View article" link if URL is valid
                    if (response.data?.published_url && isValidHttpUrl(response.data.published_url)) {
                        var $link = $('<a target="_blank" rel="noopener" style="margin-left: 8px;">View article</a>');
                        $link.attr('href', response.data.published_url);
                        $('.vp-admin-notice p').append(' ').append($link);
                    }

                    loadMyClaims();
                    updatePointsBalance();
                } else {
                    var code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            },
            complete: function() {
                $btn.prop('disabled', false).html('<span class="dashicons dashicons-yes"></span> ' + (i18n.publishArticle || 'Publish Article'));
            }
        });
    }

    function openReviewClaimsModal(campaignId) {
        currentCampaignId = campaignId;
        const $modal = $('#review-claims-modal');
        const $body = $('#review-claims-body');

        $body.html(`<div class="vp-loading-state"><span class="spinner is-active"></span> ${i18n.loading || 'Loading...'}</div>`);
        $modal.show();

        // Fetch campaign with claims
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_get_campaign',
                nonce: nonce,
                campaign_id: campaignId
            },
            success: function(response) {
                if (response.success && response.data.claims) {
                    renderClaimsForReview(response.data.claims);
                } else {
                    $body.html(`<div class="vp-empty-state"><p>No claims to review</p></div>`);
                }
            },
            error: function(xhr) {
                showError($body, getXhrErrorMessage(xhr));
            }
        });
    }

    function renderClaimsForReview(claims) {
        const $body = $('#review-claims-body');

        if (!claims || claims.length === 0) {
            $body.html(`<div class="vp-empty-state"><p>No claims to review</p></div>`);
            return;
        }

        let html = '';
        claims.forEach(function(claim) {
            const statusClass = `vp-status-${claim.status}`;
            const statusHtml = getStatusHtml(claim.status);
            const isPending = claim.status === 'claimed';

            const isPublished = claim.status === 'published';

            html += `
                <div class="vp-claim-card" data-claim-id="${claim.id}">
                    <div class="vp-claim-header">
                        <div>
                            <div class="vp-claim-publisher">${escapeHtml(claim.publisher?.domain || 'Unknown Publisher')}</div>
                            <div class="vp-claim-stats">
                                <span class="dashicons dashicons-chart-bar"></span> ${formatNumber(claim.publisher?.monthly_clicks || 0)} clicks/month
                                | ${escapeHtml(claim.publisher?.language?.toUpperCase() || '??')}
                            </div>
                            <div class="vp-claim-dates" style="font-size: 12px; color: #666; margin-top: 4px;">
                                ${claim.claimed_at ? `<span><span class="dashicons dashicons-calendar-alt" style="font-size:14px;width:14px;height:14px;"></span> Claimed ${formatDate(claim.claimed_at)}</span>` : ''}
                                ${claim.decided_at ? ` &middot; Decided ${formatDate(claim.decided_at)}` : ''}
                                ${claim.published_at ? ` &middot; Published ${formatDate(claim.published_at)}` : ''}
                            </div>
                        </div>
                        <span class="vp-campaign-status ${statusClass}">${statusHtml}</span>
                    </div>
                    ${claim.pitch_text ? `<div class="vp-claim-pitch">"${escapeHtml(claim.pitch_text)}"</div>` : '<div class="vp-claim-pitch" style="color: #999;">No pitch provided</div>'}
                    ${isPublished && claim.published_url ? `<div class="vp-claim-published" style="margin-top: 8px;"><a href="${escapeHtml(claim.published_url)}" target="_blank" rel="noopener" class="vp-btn vp-btn-outline" style="font-size: 12px;"><span class="dashicons dashicons-external" style="font-size:14px;width:14px;height:14px;"></span> View Published Article</a></div>` : ''}
                    ${isPending ? `
                        <div class="vp-claim-actions">
                            <button type="button" class="vp-btn vp-btn-primary accept-claim-btn" data-claim-id="${claim.id}">
                                <span class="dashicons dashicons-yes"></span> Accept
                            </button>
                            <button type="button" class="vp-btn vp-btn-outline reject-claim-btn" data-claim-id="${claim.id}">
                                <span class="dashicons dashicons-no"></span> Reject
                            </button>
                        </div>
                    ` : ''}
                </div>
            `;
        });

        $body.html(html);

        // Bind buttons
        $body.find('.accept-claim-btn').on('click', function() {
            const claimId = $(this).data('claim-id');
            confirmAcceptClaim(claimId);
        });

        $body.find('.reject-claim-btn').on('click', function() {
            const claimId = $(this).data('claim-id');
            confirmRejectClaim(claimId);
        });
    }

    // ==========================================
    // API Actions
    // ==========================================

    function submitCreateCampaign() {
        const $btn = $('#submit-create-campaign');
        const $form = $('#create-campaign-form');

        const title = $('#campaign-title').val().trim();

        // Sync TinyMCE content to textarea before reading
        if (typeof tinymce !== 'undefined' && tinymce.get('campaign-content')) {
            tinymce.get('campaign-content').save();
        }

        // Get content from textarea (now synced from TinyMCE)
        const content = $('#campaign-content').val().trim();

        // Get categories from dropdown rows
        const filterCategories = collectCategoryData();

        // Get locale (single dropdown combining language + country)
        const locale = $('#campaign-locale').val();

        // Validate
        if (!title) {
            alert(i18n.titleRequired || 'Title is required');
            return;
        }
        const plainText = content.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
        const wordCount = plainText ? plainText.split(' ').length : 0;
        if (wordCount < 100) {
            alert(i18n.contentRequired || 'Content must be at least 100 words');
            return;
        }
        if (!locale) {
            alert(i18n.localeRequired || 'Please select a language and target market');
            return;
        }

        $btn.prop('disabled', true).text(i18n.creating || 'Creating...');

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_create_campaign',
                nonce: nonce,
                title: title,
                content: content,
                filter_categories: filterCategories,
                locale: locale
            },
            success: function(response) {
                if (response.success) {
                    closeCreateCampaignModal();
                    showNotice('success', response.data?.message || 'Campaign created successfully!');
                    loadMyCampaigns();
                    switchTab('my-campaigns');
                    updatePointsBalance();
                } else {
                    const code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            },
            complete: function() {
                $btn.prop('disabled', false).html('<span class="dashicons dashicons-yes"></span> Create Campaign (30 pts)');
            }
        });
    }

    function submitClaimCampaign() {
        const $btn = $('#submit-claim-campaign');
        const campaignId = $('#claim-campaign-id').val();
        const pitch = $('#claim-pitch').val().trim();

        $btn.prop('disabled', true).text(i18n.claiming || 'Claiming...');

        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_claim_campaign',
                nonce: nonce,
                campaign_id: campaignId,
                pitch: pitch
            },
            success: function(response) {
                if (response.success) {
                    closeClaimModal();
                    showNotice('success', response.data?.message || 'Campaign claimed successfully!');
                    loadDiscoverCampaigns();
                    loadMyClaims();
                    updatePointsBalance();
                } else {
                    const code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            },
            complete: function() {
                $btn.prop('disabled', false).html('<span class="dashicons dashicons-yes"></span> Claim Campaign (5 pts)');
            }
        });
    }

    function confirmAcceptClaim(claimId) {
        showConfirm(
            'Accept Claim',
            i18n.confirmAccept || 'Accept this claim? The publisher will be able to publish the article on their site within 7 days.',
            function() {
                acceptClaim(claimId);
            }
        );
    }

    function acceptClaim(claimId) {
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_accept_claim',
                nonce: nonce,
                campaign_id: currentCampaignId,
                claim_id: claimId
            },
            success: function(response) {
                if (response.success) {
                    $('#review-claims-modal').hide();
                    showNotice('success', response.data?.message || 'Claim accepted!');
                    loadMyCampaigns();
                    updatePointsBalance();
                } else {
                    const code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            }
        });
    }

    function confirmRejectClaim(claimId) {
        showConfirm(
            'Reject Claim',
            i18n.confirmReject || "Are you sure you want to reject this claim? The publisher will be refunded their 5 points.",
            function() {
                rejectClaim(claimId);
            }
        );
    }

    function rejectClaim(claimId) {
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_reject_claim',
                nonce: nonce,
                campaign_id: currentCampaignId,
                claim_id: claimId
            },
            success: function(response) {
                if (response.success) {
                    showNotice('success', response.data?.message || 'Claim rejected');
                    openReviewClaimsModal(currentCampaignId);
                } else {
                    const code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            }
        });
    }

    function confirmWithdrawCampaign(campaignId) {
        showConfirm(
            'Withdraw Campaign',
            i18n.confirmWithdraw || 'Are you sure you want to withdraw this campaign? Your 30 points will be refunded.',
            function() {
                withdrawCampaign(campaignId);
            }
        );
    }

    function withdrawCampaign(campaignId) {
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_withdraw_campaign',
                nonce: nonce,
                campaign_id: campaignId
            },
            success: function(response) {
                if (response.success) {
                    showNotice('success', response.data?.message || 'Campaign withdrawn');
                    loadMyCampaigns();
                    updatePointsBalance();
                } else {
                    const code = response.data?.code || 'API_ERROR';
                    showNotice('error', mapErrorCode(code, response.data?.message));
                }
            },
            error: function(xhr) {
                showNotice('error', getXhrErrorMessage(xhr));
            }
        });
    }

    function dismissCampaign(campaignId, $card) {
        $card.css('opacity', '0.5');
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_dismiss_campaign',
                nonce: nonce,
                campaign_id: campaignId
            },
            success: function(response) {
                if (response.success) {
                    $card.slideUp(200, function() { loadMyCampaigns(); });
                } else {
                    $card.css('opacity', '1');
                    showNotice('error', response.data?.message || 'Failed to dismiss');
                }
            },
            error: function() {
                $card.css('opacity', '1');
                showNotice('error', 'Failed to dismiss campaign');
            }
        });
    }

    function dismissClaim(claimId, $card) {
        $card.css('opacity', '0.5');
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_dismiss_claim',
                nonce: nonce,
                claim_id: claimId
            },
            success: function(response) {
                if (response.success) {
                    $card.slideUp(200, function() { loadMyClaims(); });
                } else {
                    $card.css('opacity', '1');
                    showNotice('error', response.data?.message || 'Failed to dismiss');
                }
            },
            error: function() {
                $card.css('opacity', '1');
                showNotice('error', 'Failed to dismiss claim');
            }
        });
    }

    // ==========================================
    // Helpers
    // ==========================================

    function updatePointsBalance() {
        $.ajax({
            url: ajaxUrl,
            method: 'POST',
            data: {
                action: 'consolety_fetch_points_balance',
                nonce: nonce
            },
            success: function(response) {
                if (response.success) {
                    const balance = response.data?.balance || response.data?.credits || 0;
                    $('#campaigns-points-balance').text(balance);
                }
            }
        });
    }

    /**
     * Validate URL is http/https only (prevent javascript: XSS)
     */
    function isValidHttpUrl(url) {
        if (!url) return false;
        try {
            const parsed = new URL(url);
            return ['http:', 'https:'].includes(parsed.protocol);
        } catch {
            return false;
        }
    }

    /**
     * Show admin notice
     * @param {string} type - 'success' or 'error'
     * @param {string} message - The message to display (always escaped, never HTML)
     */
    function showNotice(type, message) {
        // Remove existing notices
        $('.vp-admin-notice').remove();

        const noticeClass = type === 'success' ? 'notice-success' : 'notice-error';
        const $notice = $(`<div class="notice ${noticeClass} vp-admin-notice is-dismissible"><p></p></div>`);

        // SECURITY: Always use .text() - never render HTML from messages
        // If you need to add links, append them as separate DOM nodes after calling showNotice()
        $notice.find('p').text(message);

        // If a modal is open, show inside the modal so it's not hidden behind the overlay
        var $modalNotice = $('.vp-modal-overlay:visible .vp-modal-notice-area');
        if ($modalNotice.length) {
            $modalNotice.empty().append($notice).show();
            $notice[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
        } else {
            $('.vp-dashboard .vp-header').after($notice);
        }

        // Auto-dismiss after 5 seconds
        setTimeout(function() {
            $notice.fadeOut(300, function() { $(this).remove(); });
        }, 5000);
    }

    function showConfirm(title, message, onConfirm) {
        const $modal = $('#vp-confirm-modal');
        $('#vp-confirm-title').text(title);
        $('#vp-confirm-message').text(message);
        $modal.show();

        // Unbind previous handlers
        $('#vp-confirm-ok').off('click').on('click', function() {
            $modal.hide();
            onConfirm();
        });

        $('#vp-confirm-cancel').off('click').on('click', function() {
            $modal.hide();
        });
    }

    function formatLocale(locale) {
        const localeMap = {
            'en_US': 'English (US)', 'en_GB': 'English (UK)', 'en_CA': 'English (Canada)',
            'en_AU': 'English (Australia)', 'en_NZ': 'English (New Zealand)',
            'en_IE': 'English (Ireland)', 'en_IN': 'English (India)', 'en_ZA': 'English (South Africa)',
            'nl_NL': 'Nederlands (NL)', 'nl_BE': 'Nederlands (BE)',
            'de_DE': 'Deutsch (DE)', 'de_AT': 'Deutsch (AT)', 'de_CH': 'Deutsch (CH)',
            'fr_FR': 'Francais (FR)', 'fr_BE': 'Francais (BE)', 'fr_CH': 'Francais (CH)', 'fr_CA': 'Francais (CA)',
            'es_ES': 'Espanol (ES)', 'es_MX': 'Espanol (MX)', 'es_AR': 'Espanol (AR)', 'es_CO': 'Espanol (CO)',
            'pt_PT': 'Portugues (PT)', 'pt_BR': 'Portugues (BR)',
            'it_IT': 'Italiano (IT)', 'it_CH': 'Italiano (CH)',
            'sv_SE': 'Svenska (SE)', 'da_DK': 'Dansk (DK)', 'no_NO': 'Norsk (NO)', 'fi_FI': 'Suomi (FI)',
            'pl_PL': 'Polski (PL)', 'cs_CZ': 'Cestina (CZ)', 'ru_RU': 'Russkij (RU)', 'uk_UA': 'Ukrainska (UA)',
            'ja_JP': 'Nihongo (JP)', 'ko_KR': 'Hangugeo (KR)', 'zh_CN': 'Zhongwen (CN)', 'zh_TW': 'Zhongwen (TW)'
        };
        return localeMap[locale] || locale || '';
    }

    function escapeHtml(text) {
        if (!text) return '';
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    /**
     * Extract links from HTML using DOM parsing (handles bold/italic/span anchor text).
     * Container is never attached to DOM - used only for parsing.
     */
    function extractLinksFromHtml(html) {
        if (!html) return [];
        var container = document.createElement('div');
        container.innerHTML = html;
        var anchors = container.querySelectorAll('a[href]');
        var links = [];
        for (var i = 0; i < anchors.length; i++) {
            var href = anchors[i].getAttribute('href');
            if (href && /^https?:\/\//i.test(href)) {
                links.push({
                    url: href,
                    anchor: anchors[i].textContent.trim() || href
                });
            }
        }
        return links;
    }

    function formatNumber(num) {
        if (!num) return '0';
        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    function formatDate(iso) {
        if (!iso) return '';
        return new Date(iso).toLocaleDateString();
    }

    // Unified status config: text + Dashicon per status (matches Guest Posts module)
    const statusConfig = {
        'open':      { text: i18n.open || 'Open',           icon: 'dashicons-yes' },
        'published': { text: i18n.published || 'Published', icon: 'dashicons-yes-alt' },
        'expired':   { text: i18n.expired || 'Expired',     icon: 'dashicons-calendar' },
        'withdrawn': { text: i18n.withdrawn || 'Withdrawn', icon: 'dashicons-dismiss' },
        'claimed':   { text: i18n.pending || 'Pending',     icon: 'dashicons-clock' },
        'accepted':  { text: i18n.accepted || 'Accepted',   icon: 'dashicons-yes-alt' },
        'rejected':  { text: i18n.rejected || 'Rejected',   icon: 'dashicons-no' }
    };

    function getStatusText(status) {
        return (statusConfig[status] || {}).text || status;
    }

    function getStatusHtml(status) {
        const cfg = statusConfig[status] || { text: status, icon: 'dashicons-marker' };
        return `<span class="dashicons ${cfg.icon}"></span> ${cfg.text}`;
    }

    function getExpiresText(expiresAt) {
        const now = new Date();
        const expires = new Date(expiresAt);
        const diffMs = expires - now;
        const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));

        if (diffDays < 0) {
            return i18n.expired || 'Expired';
        } else if (diffDays === 0) {
            return 'Expires today';
        } else if (diffDays === 1) {
            return 'Expires tomorrow';
        } else {
            return `${i18n.expiresIn || 'Expires in'} ${diffDays} ${i18n.days || 'days'}`;
        }
    }

})(jQuery);
