/**
 * Main page functionality for the Postomatic plugin.
 *
 * This file contains the main Vue.js application and supporting functions
 * for the Postomatic admin interface. It handles API interactions, content
 * generation, user interface components, and various plugin features.
 *
 * @package Postomatic
 * @since 1.0.0
 */

function redirectToKeyRefresh(nonce) {
    const params = new URLSearchParams(window.location.search);
    const p = params.get("page") || "postomatic";
    window.location.href = `?page=${p}&key=refresh&_wpnonce=${nonce}`;
}

function handle401Error(nonce) {
    // Make an AJAX request to handle 401 error.
    const data = {
        action: 'postomatic_handle_401',
        nonce: nonce
    };

    // Use the WordPress admin-ajax.php endpoint.
    fetch(ajaxurl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: new URLSearchParams(data)
    })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // The key has been renewed at this point, so this is a notice
                // rather than an error: the user only needs to repeat what they
                // were doing. It used to be shown with an 'error' icon.
                Swal.fire({
                    icon: 'info',
                    title: 'Connection Renewed',
                    text: (data.data && data.data.message) || 'Please try again.',
                    confirmButtonColor: '#3085d6'
                });
            } else {
                const message = (data.data && data.data.message) || 'Unknown error';
                console.error('Error handling 401:', message);
                Swal.fire({
                    icon: 'error',
                    title: 'Connection Error',
                    text: message,
                    confirmButtonColor: '#3085d6'
                });
            }
        })
        .catch(error => {
            console.error('Error handling 401:', error);
        });
}

new Vue({
    el: '#app',
    data: {
        loading: true,
        generating: false,
        // True while a regenerate request is in flight, so the buttons cannot be
        // double-submitted into a double charge.
        regenerating: false,
        errors: {
            speaker: ''
        },
        deleteId: null,
        // credits & plan.
        availableCredits: "*",
        remainingDays: "*",
        creditsLoaded: false,
        planLabel: 'Starter',
        planSlug: 'starter',
        // nonces from PHP.
        nonce: {
            postomatic: PostomaticData.nonce,
            search: PostomaticData.nonce_search,
            attachment: PostomaticData.attachmentNonce,
            support: PostomaticData.supportNonce,
            create: PostomaticData.createNonce
        },
        // Plan slugs ordered lowest tier first, supplied by PHP.
        planTiers: Array.isArray(PostomaticData.planTiers) && PostomaticData.planTiers.length
            ? PostomaticData.planTiers
            : ['starter', 'basic', 'pro-ultimate'],
        //Rest Urls.
        url: {
            reactive: PostomaticData.reactiveUrl,
            creditScore: PostomaticData.creditScore,
            countryList: PostomaticData.countryList,
            recentList: PostomaticData.recentList,
            postMaker: PostomaticData.postMaker,
            deletePost: PostomaticData.deletePost,
            speakersList: PostomaticData.speakerList,
        },
        page: {
            addCredit: PostomaticData.addCreditPage,
            membership: PostomaticData.membershipPage,
        },
        // alerts.
        successMessage: 'Your request has been submitted.',
        showSuccessAlert: false,
        showMainAlert: false,
        // fetched data.
        contentData: {languages: [], countries: {}},
        categories: PostomaticData.categories,
        speakers: [],
        writers: PostomaticData.writers,
        woocommerceActive: PostomaticData.woocommerceActive,
        hasProducts: PostomaticData.hasProducts,
        contentGoalTypes: [],
        recentPosts: [],
        creditValues: [],
        pagination: {},
        // image handling.
        selectedImage: {},
        // modals / views.
        currentPost: null,
        showPostModal: false,
        showDeleteModal: false,
        // advanced.
        advancedVisible: false,
        activeTab: 'content-planning',
        //searchble.
        countrySearch: '',
        languageSearch: '',
        selectedLangName: '',
        selectedCategoryName: '',
        selectedSpeakerName: '',
        requiredCredit: 0,
        // the form.
        form: {
            titleType: 'single',
            postTitle: '',
            bulkTitles: '',
            featureImageType: '',
            bodyImages: [],
            language: '',
            category: '',
            speaker: '',
            autoCategory: false,
            postDesc: '',
            writer: PostomaticData.writer,
            contentTone: 'conversational',
            targetAudience: 'general',
            contentLength: 'brief',
            addEmoji: false,
            realExamples: false,
            includeToc: false,
            executiveSummary: false,
            includeKeyPoints: false,
            faqData: false,
            includeConclusion: true,
            includeReferences: false,
            suggestVideos: false,
            focusKeyword: '',
            secondaryKeywords: '',
            autoMetaDesc: true,
            autoAltTags: false,
            internalLinking: false,
            postSuggestionSection: false,
            readabilityCheck: false,
            grammarCheck: false,
            keywordDensityCheck: false,
            aiCreativity: 'balanced',
            researchDepth: 'basic-free',
            targetCountry: '',
            postStatus: 'draft-free',
            publishDate: '',
            bulkCount: 1,
            exactTitle: false,
            commentStatus: 'closed',
            selectedProduct: null,
            selectedProducts: [],
            contentGoalType: '',
            // Required when titleType === 'product_sales': what the article
            // should accomplish for the product, and the keywords it should
            // target. Mirrors the equivalent required fields already used by
            // the AutoPilot product wizard.
            contentGoalDetails: '',
            productKeywords: ''
        },
        productSearchQuery: '',
        products: [],
        isLoadingProducts: false,
        productPagination: {
            current_page: 1,
            total_pages: 1,
            total: 0,
            per_page: 20
        },
        fieldErrors: {},
        selectedProductDetails: null,
    },
    computed: {
        isPro() {
            return this.planSlug === 'pro-ultimate';
        },
        isBasic() {
            return this.planSlug === 'basic';
        },
        // Rank of the current plan within planTiers; -1 for 'free'/unknown so
        // those always rank below every real plan.
        planRank() {
            return this.planTiers.indexOf(String(this.planSlug || '').toLowerCase());
        },
        // True once the user owns the top tier, meaning there is nothing left
        // to sell them and every "Upgrade Plan" call to action should be hidden.
        isHighestPlan() {
            return this.planTiers.length > 0 && this.planRank === this.planTiers.length - 1;
        },
        creditInfoUrl() {
            return `?page=${this.page.addCredit}#item_credits`;
        },
        // Empty on the top plan so the credit trackers drop their Upgrade
        // buttons instead of linking users to a page with nothing to buy.
        upgradeUrl() {
            return this.isHighestPlan ? '' : `?page=${this.page.membership}`;
        },
        purchaseCreditsUrl() {
            return `?page=${this.page.addCredit}`;
        },
        contentTones() {
            return [
                {value: 'conversational', label: 'Conversational', available: true},
                {
                    value: 'professional',
                    label: 'Professional',
                    available: this.isBasic || this.isPro
                },
                {value: 'academic', label: 'Academic', available: this.isBasic || this.isPro},
                {value: 'technical', label: 'Technical', available: this.isPro},
                {value: 'storytelling', label: 'Storytelling', available: this.isPro},
                {value: 'persuasive', label: 'Persuasive', available: this.isBasic || this.isPro},
                {value: 'humorous', label: 'Humorous', available: this.isPro},
            ];
        },
        audiences() {
            return [
                {value: 'general', label: 'General Public – [Starter]', available: true},
                {
                    value: 'beginners',
                    label: 'Beginners & Novices – [BASIC][PRO]',
                    available: this.isBasic || this.isPro
                },
                {
                    value: 'intermediate',
                    label: 'Intermediate Learners – [BASIC][PRO]',
                    available: this.isBasic || this.isPro
                },
                {
                    value: 'students',
                    label: 'Students & Academics – [BASIC][PRO]',
                    available: this.isBasic || this.isPro
                },
                {
                    value: 'expert',
                    label: 'Experts & Advanced Practitioners – [PRO]',
                    available: this.isPro
                },
                {
                    value: 'professionals',
                    label: 'Corporate & Industry Professionals – [PRO]',
                    available: this.isPro
                },
                {
                    value: 'entrepreneurs',
                    label: 'Entrepreneurs & Innovators – [PRO]',
                    available: this.isPro
                },
            ];
        },
        contentLengths() {
            return [
                {value: 'brief', label: 'Brief (600+ words) – [Starter]', available: true},
                {
                    value: 'standard',
                    label: 'Standard (1000+ words) – [BASIC][PRO]',
                    available: this.isBasic || this.isPro
                },
                {
                    value: 'detailed',
                    label: 'Detailed (1500+ words) – [PRO]',
                    available: this.isPro
                },
                {
                    value: 'comprehensive',
                    label: 'Comprehensive (2500+ words) – [PRO]',
                    available: this.isPro
                },
            ];
        },
        aiCreativityLevels() {
            return [
                {value: 'balanced', label: 'Balanced – [Starter]', available: true},
                {value: 'conservative', label: 'Conservative [PRO]', available: this.isPro},
                {value: 'creative', label: 'Creative [PRO]', available: this.isPro},
                {value: 'experimental', label: 'Experimental [PRO]', available: this.isPro},
            ]
        },
        researchDepthOptions() {
            return [
                {value: 'basic-free', label: 'Basic – [Starter]', available: true},
                {value: 'moderate', label: 'Moderate – [BASIC][PRO]', available: this.isBasic || this.isPro},
                {value: 'thorough', label: 'Thorough – [BASIC][PRO]', available: this.isBasic || this.isPro},
                {value: 'extensive', label: 'Extensive – [PRO]', available: this.isPro},
                {
                    value: 'deep-research',
                    label: 'Comprehensive – [PRO]',
                    available: this.isPro
                },
            ];
        },
        postStatusOptions() {
            return [
                {value: 'draft-free', label: 'Draft – [Starter]', available: true},
                {value: 'pending', label: 'Pending Review – [BASIC][PRO]', available: this.isBasic || this.isPro},
                {
                    value: 'publish',
                    label: 'Publish Immediately – [PRO]',
                    available: this.isPro
                },
                {value: 'schedule', label: 'Schedule – [PRO]', available: this.isPro}
            ];
        },
        filteredCountries() {
            const term = this.countrySearch.trim().toLowerCase();
            if (!term) return this.contentData.countries;

            const out = {};
            for (const [continent, list] of Object.entries(this.contentData.countries)) {
                const matches = list.filter(c => c.toLowerCase().includes(term));
                if (matches.length) out[continent] = matches;
            }
            return out;
        },
        // step 1: turn the flat array into a map of group → [lang…].
        groupedLanguages() {
            const out = {};
            // contentData.languages is an ARRAY of { code, name, group }.
            this.contentData.languages.forEach(lang => {
                if (!out[lang.group]) out[lang.group] = [];
                out[lang.group].push(lang);
            });
            return out;
        },
        // step 2: filter that map by search term.
        filteredLanguagesByGroup() {
            const term = this.languageSearch.trim().toLowerCase();
            if (!term) {
                return this.groupedLanguages;
            }

            const out = {};
            for (const [grp, list] of Object.entries(this.groupedLanguages)) {
                const matches = list.filter(l => l.name.toLowerCase().includes(term));
                if (matches.length) out[grp] = matches;
            }
            return out;
        },
        manualBulkTitlesCount() {
            return this.form.bulkTitles
                .split('\n')
                .filter(line => line.trim())
                .length;
        },
        showBuyCredit() {
            // don't even consider showing until we've loaded the real number.
            if (!this.creditsLoaded) return false;
            return this.availableCredits < 6;
        },

        isProductComparisonType() {
            // Check if the selected content goal type is "product-comparison".
            if (!this.form.contentGoalType || this.contentGoalTypes.length === 0) {
                return false;
            }
            const selectedType = this.contentGoalTypes.find(type => type.id === this.form.contentGoalType);
            return selectedType && selectedType.id === 'product-comparison';
        },

        isProductType() {
            // Check if the selected content goal type is product-related.
            if (!this.form.contentGoalType || this.contentGoalTypes.length === 0) {
                return false;
            }
            const selectedType = this.contentGoalTypes.find(type => type.id === this.form.contentGoalType);
            return selectedType && selectedType.type === 'product';
        },

    },
    watch: {
        // whenever the filtered list changes….
        filteredCountries(newVal) {
            // gather all remaining country names.
            const all = Object.values(newVal).flat();
            if (all.length === 1) {
                // if there’s exactly one, pre‑select it.
                this.form.targetCountry = all[0];
            }
        },
        filteredLanguagesByGroup(groups) {
            if (!groups) return; // Skip if groups is null or undefined.
            const all = Object.values(groups).flat();
            if (all.length === 1) {
                this.form.language = all[0].code;
            }
        },
        'form.autoCategory'(isAuto) {
            if (isAuto) {
                this.selectedCategoryName = '';
                this.form.category = '';
            }
        },
        'form.contentGoalType'() {
            // Reset selected products when content goal type changes.
            this.form.selectedProduct = null;
            this.form.selectedProducts = [];
            this.selectedProductDetails = null;
        },
        'form.titleType'(newVal) {
            if (newVal === 'bulk') {
                // User switched to Bulk Titles.
                this.form.bulkCount = 1;
                this.form.postTitle = '';
                // No need to set titleType again, it's already 'bulk'.
            } else if (newVal === 'product_sales') {
                // User switched to Product Sales.
                // Fetch content goal types for products.
                this.fetchContentGoalTypes();
                this.form.bulkCount = 1;
                this.form.postTitle = '';
                this.form.bulkTitles = '';
                // No need to set titleType again, it's already 'product_sales'.
            } else {
                // Back to Single Title.
                this.form.bulkCount = 1;
                this.form.bulkTitles = '';
                // No need to set titleType again, it's already 'single'.
            }

         },
        'form.contentTone'() {
            this.recalculateRequiredCredit();
        },
        'form.targetAudience'() {
            this.recalculateRequiredCredit();
        },
        'form.contentLength'() {
            this.recalculateRequiredCredit();
        },
        'form.bulkCount'(newVal) {
            this.validateExactTitle()
            this.recalculateRequiredCredit();
        },
        'form.featureImageType'() {
            this.recalculateRequiredCredit();
        },
        'form.bodyImages'() {
            this.recalculateRequiredCredit();
        },
        'form.language'() {
            this.recalculateRequiredCredit();
        },
        'form.category'() {
            this.recalculateRequiredCredit();
        },
        'form.speaker'() {
            this.recalculateRequiredCredit();
        },
        'form.writer'() {
            this.recalculateRequiredCredit();
        },
        'form.addEmoji'() {
            this.recalculateRequiredCredit();
        },
        'form.realExamples'() {
            this.recalculateRequiredCredit();
        },
        'form.includeToc'() {
            this.recalculateRequiredCredit();
        },
        'form.executiveSummary'() {
            this.recalculateRequiredCredit();
        },
        'form.includeKeyPoints'() {
            this.recalculateRequiredCredit();
        },
        'form.faqData'() {
            this.recalculateRequiredCredit();
        },
        'form.includeConclusion'() {
            this.recalculateRequiredCredit();
        },
        'form.includeReferences'() {
            this.recalculateRequiredCredit();
        },
        'form.suggestVideos'() {
            this.recalculateRequiredCredit();
        },
        'form.autoMetaDesc'() {
            this.recalculateRequiredCredit();
        },
        'form.autoAltTags'() {
            this.recalculateRequiredCredit();
        },
        'form.internalLinking'() {
            this.recalculateRequiredCredit();
        },
        'form.postSuggestionSection'() {
            this.recalculateRequiredCredit();
        },
        'form.readabilityCheck'() {
            this.recalculateRequiredCredit();
        },
        'form.grammarCheck'() {
            this.recalculateRequiredCredit();
        },
        'form.keywordDensityCheck'() {
            this.recalculateRequiredCredit();
        },
        'form.aiCreativity'() {
            this.recalculateRequiredCredit();
        },
        'form.researchDepth'() {
            this.recalculateRequiredCredit();
        },
        'form.targetCountry'() {
            this.recalculateRequiredCredit();
        },
        'form.postStatus'() {
            this.recalculateRequiredCredit();
        },
        'form.exactTitle'() {
            this.validateExactTitle()
            this.recalculateRequiredCredit();
        },
        'form.commentStatus'() {
            this.recalculateRequiredCredit();
        },
        'form.selectedProduct'() {
            this.recalculateRequiredCredit();
        },
        'form.selectedProducts'() {
            this.recalculateRequiredCredit();
        },
        planLabel() {
            this.applyFeatureRestrictions();
        },
        planSlug() {
            // wait for DOM update then re‑apply.
            this.$nextTick(this.applyFeatureRestrictions);
        },
        form: {
            handler(newVal, oldVal) {
                // Identify which property changed.
                const changedProps = this.findChangedProperties(newVal, oldVal);

                // Skip recalculation if only text fields or textareas changed.
                const textFields = ['postTitle', 'bulkTitles', 'postDesc', 'focusKeyword', 'secondaryKeywords', 'publishDate'];
                const shouldRecalculate = changedProps.some(prop => !textFields.includes(prop));

                if (shouldRecalculate || !oldVal) {
                    this.recalculateRequiredCredit();
                }
            },
            deep: true,
            immediate: true
        },

    },
    methods: {
        clearErrors() {
            // Clear all errors when any button is clicked.
            this.errors = {};
            this.fieldErrors = {};
        },
        onLanguageChange(selectedValue) {
            this.form.language = selectedValue;

            // Find the language name for the selected code.
            if (selectedValue) {
                const selectedLang = this.contentData.languages.find(l => l.code === selectedValue);
                if (selectedLang) {
                    this.selectedLangName = selectedLang.name;
                }
            } else {
                this.selectedLangName = '';
            }
        },
        onCategoryChange(selectedValue) {
            this.form.category = selectedValue;

            // Find the category name for the selected slug.
            if (selectedValue) {
                const selectedCat = this.categories.find(c => c.slug === selectedValue);
                if (selectedCat) {
                    this.selectedCategoryName = selectedCat.name;
                }
            } else {
                this.selectedCategoryName = '';
            }
        },
        onSpeakerChange(selectedValue) {
            this.form.speaker_select = selectedValue;

            // Find the speaker name for the selected id.
            if (selectedValue) {
                const selectedSpeaker = this.speakers.find(s => s.id === selectedValue);
                if (selectedSpeaker) {
                    this.selectedSpeakerName = selectedSpeaker.name;
                }
            } else {
                this.selectedSpeakerName = '';
            }
        },
        recalculateRequiredCredit() {
            this.requiredCredit = Number(this.calculateTotalCredits()) || 0;
        },
        init() {
            this.fetchCreditData();
            this.fetchCreditScoresData();
            this.fetchLanguagesData();
            this.fetchCountriesData();
            this.fetchRecentPostsData();
            this.fetchSpeakersData();
        },
        fetchCreditData() {
            that = this
            // Make an AJAX request to the WordPress admin-ajax.php.
            const data = {
                action: 'postomatic_get_credits',
                nonce: this.nonce.postomatic
            };

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams(data)
            })
                .then(r => {
                    if (!r.ok) {
                        if (r.status === 401) {
                            // Handle 401 error.
                            const nonce = this.nonce.postomatic;
                            handle401Error(nonce);
                            return;
                        }
                        if (r.status === 402) {
                            // Payment Required - Get the specific error message from the response.
                            return r.json().then(errorData => {
                                // Show the specific error message from the API.
                                Swal.fire({
                                    icon: 'error',
                                    title: 'Payment Required',
                                    html: `<div style="font-size: 16px; margin-bottom: 15px;">${errorData.message || "Your account requires payment to continue."}</div>
                                           <div style="font-weight: bold; color: #d33;">Please purchase credits to continue using the service.</div>`,
                                    showCancelButton: true,
                                    confirmButtonColor: '#3085d6',
                                    cancelButtonColor: '#d33',
                                    confirmButtonText: 'Buy Credits',
                                    cancelButtonText: 'Cancel'
                                }).then((result) => {
                                    if (result.isConfirmed) {
                                        // Redirect to the purchase credits page.
                                        window.location.href = this.purchaseCreditsUrl;
                                    }
                                });
                                throw new Error(errorData.message || "Payment required");
                            }).catch(jsonError => {
                                // If we can't parse the JSON, show a generic message.
                                Swal.fire({
                                    icon: 'error',
                                    title: 'Payment Required',
                                    html: `<div style="font-size: 16px; margin-bottom: 15px;">Your account requires payment to continue.</div>
                                           <div style="font-weight: bold; color: #d33;">Please purchase credits to continue using the service.</div>`,
                                    showCancelButton: true,
                                    confirmButtonColor: '#3085d6',
                                    cancelButtonColor: '#d33',
                                    confirmButtonText: 'Buy Credits',
                                    cancelButtonText: 'Cancel'
                                }).then((result) => {
                                    if (result.isConfirmed) {
                                        // Redirect to the purchase credits page.
                                        window.location.href = this.purchaseCreditsUrl;
                                    }
                                });
                                throw new Error("Payment required");
                            });
                        }
                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${r.status}`);
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${r.status}`);
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (!data || !data.data) {
                        console.error('API Error: Empty response from postomatic_get_credits');
                        throw new Error('Empty response from API');
                    }

                    if (!data.data.plan_slug) {
                        console.error('API Error: plan_slug is null or empty');
                        throw new Error('Invalid plan information received');
                    }

                    this.availableCredits = data.data.credit;
                    this.remainingDays = data.data.remaining_days;
                    this.planLabel = data.data.plan;
                    this.planSlug = data.data.plan_slug;
                })
                .catch(err => {
                    // Initialize with default values on error.
                    this.availableCredits = 0;
                    this.remainingDays = 0;
                    this.planLabel = '';
                    this.planSlug = '';

                    // Show error message to user
                    console.error('Error fetching credit data:', err.message);

                    // Only show alert for validation errors, not for network errors that might happen during background refresh
                    if (err.message === 'Empty response from API' || err.message === 'Invalid plan information received') {
                        Swal.fire({
                            icon: 'info',
                            title: 'Refreshing Account Information',
                            text: 'Communicating with the server to fetch your account information. The page will refresh in 5 seconds.',
                            confirmButtonColor: '#3085d6',
                            // 5 seconds, matching the message. This was 500000
                            // (over eight minutes), so the dialog sat there with
                            // a spinner and the promised refresh never came.
                            timer: 5000,
                            timerProgressBar: true,
                            didOpen: () => {
                                Swal.showLoading();
                            }
                        }).then(() => {
                            // Refresh the page after 5 seconds
                            window.location.reload();
                        });
                    }
                })
                .finally(() => {
                    this.creditsLoaded = true;
                });
        },
        fetchCreditScoresData() {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const data = {
                action: 'postomatic_get_credit_scores',
                nonce: this.nonce.support
            };

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams(data)
            })
                .then(response => {
                    // check HTTP status.
                    if (!response.ok) {
                        if (response.status === 401) {
                            // Handle 401 error.
                            const nonce = this.nonce.postomatic;
                            handle401Error(nonce);
                            return;
                        }
                        if (response.status === 402) {
                            // Payment Required - Get the specific error message from the response.
                            return response.json().then(errorData => {
                                // Show the specific error message from the API.
                                Swal.fire({
                                    icon: 'error',
                                    title: 'Payment Required',
                                    html: `<div style="font-size: 16px; margin-bottom: 15px;">${errorData.message || "Your account requires payment to continue."}</div>
                                           <div style="font-weight: bold; color: #d33;">Please purchase credits to continue using the service.</div>`,
                                    showCancelButton: true,
                                    confirmButtonColor: '#3085d6',
                                    cancelButtonColor: '#d33',
                                    confirmButtonText: 'Buy Credits',
                                    cancelButtonText: 'Cancel'
                                }).then((result) => {
                                    if (result.isConfirmed) {
                                        // Redirect to the purchase credits page.
                                        window.location.href = this.purchaseCreditsUrl;
                                    }
                                });
                                throw new Error(errorData.message || "Payment required");
                            }).catch(jsonError => {
                                // If we can't parse the JSON, show a generic message.
                                Swal.fire({
                                    icon: 'error',
                                    title: 'Payment Required',
                                    html: `<div style="font-size: 16px; margin-bottom: 15px;">Your account requires payment to continue.</div>
                                           <div style="font-weight: bold; color: #d33;">Please purchase credits to continue using the service.</div>`,
                                    showCancelButton: true,
                                    confirmButtonColor: '#3085d6',
                                    cancelButtonColor: '#d33',
                                    confirmButtonText: 'Buy Credits',
                                    cancelButtonText: 'Cancel'
                                }).then((result) => {
                                    if (result.isConfirmed) {
                                        // Redirect to the purchase credits page.
                                        window.location.href = this.purchaseCreditsUrl;
                                    }
                                });
                                throw new Error("Payment required 1");
                            });
                        }
                        // Try to get the error message from the response.
                        return response.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${response.status}`);
                            throw new Error(errorData.message || `API request failed with status ${response.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${response.status}`);
                            throw new Error(`API request failed with status ${response.status}`);
                        });
                    }
                    return response.json()
                })
                .then(response => {
                    response = response.data.data
                    if (response) {
                        // Ensure we have an array of objects with key and score properties.
                        if (Array.isArray(response)) {
                            this.creditValues = response.map(item => {
                                // If the item already has key and score properties, use them.
                                if (item.key && (item.score !== undefined)) {
                                    return item;
                                }

                                // Always normalize score to a number to avoid string
                                // concatenation issues in credit calculations.
                                const key = item.key;
                                const score = Number(item.score) || 0;
                                const type = item.type;

                                return { key, score, type };
                            });
                        } else {
                            this.creditValues = [];
                        }
                        this.recalculateRequiredCredit();
                    }
                })
                .catch(err => {
                    console.error('Error fetching credit scores data:', err);
                    this.creditValues = []; // Initialize with empty array on error.
                    this.requiredCredit = 0; // Set required credit to 0 on error.
                })
        },
        calculateProgress() {
            // Calculate progress based on remaining days.
            // Assuming a 30-day subscription period.
            const totalDays = 30;
            const daysUsed = totalDays - this.remainingDays;
            const progressPercentage = Math.min(100, Math.max(0, (daysUsed / totalDays) * 100));
            return progressPercentage + '%';
        },
        initializeFeatureBadges() {
            // (paste your original addBadgesTo / querySelectorAll logic here).
            document.querySelectorAll('select, .checkbox-item').forEach(el => {
                let hasBasic = el.querySelector('[data-basic]') != null;
                let hasPro = el.querySelector('[data-premium]') != null;
                if (hasBasic || hasPro) {
                    const container = document.createElement('div');
                    container.className = 'badge-container';
                    if (hasBasic) {
                        const sb = document.createElement('span');
                        sb.className = 'feature-badge basic-badge';
                        sb.textContent = 'BASIC';
                        container.appendChild(sb);
                    }
                    if (hasPro) {
                        const sp = document.createElement('span');
                        sp.className = 'feature-badge pro-badge';
                        sp.textContent = 'PRO';
                        container.appendChild(sp);
                    }
                    el.appendChild(container);
                }
            });
        },
        fetchLanguagesData() {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_languages');
            formData.append('nonce', this.nonce.postomatic);

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                body: formData
            })
                .then(r => {
                    if (!r.ok) {
                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${r.status}`);
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${r.status}`);
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (data && data.success && data.data) {
                        this.contentData.languages = data.data || [];
                    }
                })
                .catch(error => {
                    console.error('Error fetching languages data:', error);
                    this.contentData.languages = []; // Initialize with empty array on error.
                });
        },
        fetchCountriesData() {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_countries');
            formData.append('nonce', this.nonce.support);

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                body: formData
            })
                .then(r => {
                    if (!r.ok) {
                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${r.status}`);
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${r.status}`);
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (data && data.success && data.data) {
                        this.contentData.countries = data.data || [];
                    }
                })
                .catch(error => {
                    console.error('Error fetching countries data:', error);
                    this.contentData.countries = []; // Initialize with empty array on error.
                });
        },
        fetchSpeakersData() {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_speakers');
            formData.append('nonce', this.nonce.support);

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                body: formData
            })
                .then(r => {
                    if (!r.ok) {
                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${r.status}`);
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${r.status}`);
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (data && data.success && data.data) {
                        this.speakers = data.data || [];
                    }
                })
                .catch(error => {
                    console.error('Error fetching speakers data:', error);
                    this.speakers = []; // Initialize with empty array on error.
                });
        },
        fetchRecentPostsData(url) {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_recent_posts');
            formData.append('nonce', this.nonce.support);

            // Add URL parameter if provided (for pagination).
            if (url) {
                formData.append('url', url);
            }

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                body: formData
            })
                .then(r => {
                    if (!r.ok) {
                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            console.error('API Error:', errorData.message || `API request failed with status ${r.status}`);
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            console.error('API Error:', `API request failed with status ${r.status}`);
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (data && data.success && data.data) {
                        this.recentPosts = data.data.data || [];
                        this.pagination = data.data.meta || {};

                        // Set next and prev properties based on current_page and last_page.
                        if (this.pagination.current_page && this.pagination.last_page) {
                            this.pagination.prev = this.pagination.current_page > 1;
                            this.pagination.next = this.pagination.current_page < this.pagination.last_page;
                        }
                    }
                })
                .catch(error => {
                    console.error('Error fetching recent posts data:', error);
                    this.recentPosts = []; // Initialize with empty array on error.
                    this.pagination = {}; // Initialize with empty object on error.
                });
        },

        fetchPrevPage() {
            if (this.pagination && this.pagination.current_page > 1) {
                const prevPage = this.pagination.current_page - 1;
                const url = `${this.url.recentList}?page=${prevPage}`;
                this.fetchRecentPostsData(url);
            }
        },

        fetchNextPage() {
            if (this.pagination && this.pagination.current_page < this.pagination.last_page) {
                const nextPage = this.pagination.current_page + 1;
                const url = `${this.url.recentList}?page=${nextPage}`;
                this.fetchRecentPostsData(url);
            }
        },
        updateBulkCount() {
            const lines = this.form.bulkTitles
                .split('\n')
                .filter(l => l.trim());
            this.recalculateRequiredCredit(); // Update required credits.
        },
        openMediaSelector() {
            // create the WordPress media frame.
            const frame = wp.media({
                title: 'Select Image',
                button: {text: 'Use this image'},
                multiple: false  // only allow one image.
            });

            // when an image is selected, grab its data and store in Vue state.
            frame.on('select', () => {
                const attachment = frame.state().get('selection').first().toJSON();
                this.selectedImage = {
                    id: attachment.id,
                    url: attachment.url
                };
                this.recalculateRequiredCredit(); // Update required credits.
            });

            // open the modal.
            frame.open();
        },
        selectBodyImage() {
            // determine the max allowed images.
            const maxImages = this.isPro ? 3 : 1;

            // if the user has already selected the max, show an error.
            if (this.form.bodyImages.length >= maxImages) {
                Swal.fire({
                    icon: 'error',
                    title: 'Too many images',
                    text: `You can only add up to ${maxImages} image${maxImages > 1 ? 's' : ''}.`,
                });
                return;  // don’t open the media frame.
            }

            // otherwise, open the WP media selector as before.
            const frame = wp.media({
                title: 'Select Images',
                button: {text: 'Use these images'},
                multiple: true
            });

            frame.on('select', () => {
                const attachments = frame.state().get('selection').toArray();
                attachments.forEach(att => {
                    // if we hit the limit mid‑way, show the same error.
                    if (this.form.bodyImages.length < maxImages) {
                        const image = att.toJSON();
                        this.form.bodyImages.push({id: image.id, url: image.url});
                    } else {
                        Swal.fire({
                            icon: 'error',
                            title: 'Too many images',
                            text: `You can only add up to ${maxImages} image${maxImages > 1 ? 's' : ''}.`,
                        });
                    }
                });
                this.recalculateRequiredCredit(); // Update required credits.
            });

            frame.open();
        },
        removeImage() {
            this.selectedImage = {};
            this.recalculateRequiredCredit(); // Update required credits.
        },
        removeBodyImage(id) {
            this.form.bodyImages = this.form.bodyImages.filter(i => i.id !== id);
            this.recalculateRequiredCredit(); // Update required credits.
        },
        toggleFeatureImageType(type) {
            // If the current type is already selected, uncheck it by setting to empty string.
            if (this.form.featureImageType === type) {
                this.form.featureImageType = '';
                this.featureImageType = '';
                // If we're unchecking 'custom', also clear the selected image.
                if (type === 'custom') {
                    this.selectedImage = {};
                }
            } else {
                this.form.featureImageType = type;

            }
            this.recalculateRequiredCredit(); // Update required credits.
        },


        toggleAdvanced() {
            this.advancedVisible = !this.advancedVisible;
        },
        validate() {
            this.errors = {};
            if (this.form.postTitle.trim().length < 4 && this.form.titleType === 'single') {
                this.errors.postTitle = 'Please enter a post title with at least 5 characters.';
            }

            if (this.form.titleType === 'bulk') {
                // break the textarea into non‑empty lines.
                const titles = this.form.bulkTitles
                    .split('\n')
                    .map(l => l.trim())
                    .filter(l => l);

                if (!titles.length) {
                    this.errors.bulkTitles = 'Please enter at least one bulk title.';
                } else {
                    // ensure each line is long enough.
                    const tooShort = titles.find(t => t.length < 4);
                    if (tooShort) {
                        this.errors.bulkTitles = 'Each bulk title must be at least 4 characters.';
                    }
                }
            }

            if (this.form.titleType === 'product_sales') {
                // Validate that a product is selected.
                if (this.isProductComparisonType) {
                    // For product comparison, check selectedProducts array.
                    if (!this.form.selectedProducts || this.form.selectedProducts.length === 0) {
                        this.errors.selectedProduct = 'Please select a product.';
                    } else if (this.form.selectedProducts.length < 2) {
                        this.errors.selectedProduct = 'Please select at least 2 products to compare.';
                    }
                } else {
                    // For other product types, check selectedProduct.
                    if (this.form.selectedProduct === null) {
                        this.errors.selectedProduct = 'Please select a product.';
                    }
                }

                // Goal Description and Keywords are required so the AI has enough
                // context to write content that actually supports the sales goal.
                if (!this.form.contentGoalDetails || !this.form.contentGoalDetails.trim()) {
                    this.errors.contentGoalDetails = 'Please describe what this article should accomplish for the product.';
                }
                if (!this.form.productKeywords || !this.form.productKeywords.trim()) {
                    this.errors.productKeywords = 'Please enter at least one keyword for this product.';
                }
            }

            if (!this.form.language) {
                this.errors.language = 'Language required';
            }

            if (!this.form.category && !this.form.autoCategory) {
                this.errors.category = 'Category or auto-select required';
            }

            if (!this.form.speaker) {
                this.errors.speaker = 'Speaker required';
            }

            // If attach_image_manual is empty and featured_image_type is custom, show error.
            if (this.form.featureImageType === 'custom' && !this.selectedImage.url) {
                this.errors.featureImage = 'Select at least one image as the featured post image.';
            }

            if (Object.keys(this.errors).length) {
                // build a numbered list of error messages.
                const errorItems = Object.values(this.errors)
                    .map(msg => `<li style="margin-bottom:4px;">${msg}</li>`)
                    .join('');

                Swal.fire({
                    icon: 'error',
                    title: 'Please fix the following:',
                    html: `
        <ol style="
          font-size: 0.85em;
          text-align: left;
          margin: 0;
          padding-left: 1.5em;
          line-height: 1.4;
        ">
          ${errorItems}
        </ol>
      `,
                    confirmButtonText: 'OK'
                });

                return false;
            }

            return true;
        },
        async handleSubmit(event) {
            event.preventDefault();

            // run your validation; if it fails, don’t start loading.
            if (!this.validate()) return;

            // start loading state.
            this.generating = true;

            // default is minimum 1 single title.
            let titleCount = 1;
            const bulkPostNumber = this.form.bulkCount
            if (parseInt(bulkPostNumber) && parseInt(bulkPostNumber) > 1) { // bulk count is the bulk with only one.
                // single title.
                titleCount = parseInt(bulkPostNumber);
            } else if (this.form.titleType === 'bulk') {
                titleCount = this.getBulkTitleCount() // it can be single post.
            }

            // Generate and save one-time code on the server.
            let code = await this.getOneTimeCode(); // Generate unique code per post.


            // 1) form element from the event.
            const formEl = event.target;
            // 2) build payload by looping every named control.
            const payload = {};

            Array.from(formEl.elements).forEach(el => {
                // skip anything without a name or disabled.
                if (!el.name) return;
                // checkboxes and radios need special handling.
                if (el.type === 'checkbox') {
                    payload[el.name] = el.checked;
                } else if (el.type === 'radio') {
                    if (el.checked) {
                        payload[el.name] = el.value;
                    }
                } else {
                    payload[el.name] = el.value;
                }
            });

            // Ensure category_select has a value when auto-select is checked.
            if (payload.category_by_ai === true && (!payload.category_select || payload.category_select.trim() === '')) {
                // Set a default value or use the first available category.
                payload.category_select = this.categories.length > 0 ? this.categories[0].name : 'Uncategorized';
            } else if (payload.category_select && this.form.category) {
                // Find the category by slug and use its name instead.
                const selectedCategory = this.categories.find(cat => cat.slug === this.form.category);
                if (selectedCategory) {
                    payload.category_select = selectedCategory.name;
                }
            }

            payload.categories = JSON.stringify(this.categories.map(c => c.name))
            payload.body_images = JSON.stringify(this.form.bodyImages)
            payload.one_time_code = code
            payload._wp_http_referer = 'MAIN_PAGE_SUBMIT_FORM'
            payload._wp_sender_user = this.form.writer
            // Always include the feature image type in the payload.
            payload.feature_image_type = this.form.featureImageType;

            // Include the selected image URL if available.
            payload.attach_image_manual = this.selectedImage?.url ?? '';

            // Add selected products to payload if product_sales title type is selected.
            if (this.form.titleType === 'product_sales') {
                // For product comparison, send the selectedProducts array.
                payload.selected_products = this.form.selectedProducts;

                // Also send the full product details for each selected product.
                const productDetails = [];
                this.form.selectedProducts.forEach(productId => {
                    const product = this.getProductById(productId);
                    if (product) {
                        productDetails.push(product);
                    }
                });

                payload.selected_products_details = btoa(JSON.stringify(productDetails));


                // Always send the content goal type.
                payload.content_goal_type = this.form.contentGoalType;

                // Send the required goal description and keywords so the backend
                // has enough context to write content that supports the sales goal.
                payload.content_goal_details = this.form.contentGoalDetails;
                payload.product_keywords = this.form.productKeywords;
            }

            if (this.form.internalLinking || this.form.postSuggestionSection) {
                let searchTerm = this.form.postTitle;

                // If title type is bulk, combine all titles and remove new lines.
                if (this.form.titleType === 'bulk' && this.form.bulkTitles) {
                    searchTerm = this.form.bulkTitles.split('\n')
                        .filter(line => line.trim())
                        .join(' ');
                }

                const searchResult = await this.search(searchTerm);
                // Ensure internal_links_list is an array, not an object.
                const linksArray = Array.isArray(searchResult) ? searchResult : Object.values(searchResult);
                // Convert the array of objects to a JSON string with relevant properties.
                payload.internal_links_list = JSON.stringify(linksArray.map(link => {
                    // Extract the most likely useful properties (title, url, id).
                    // If the structure is different, this may need adjustment.
                    return {
                        title: link.title || '',
                        url: link.url || link.link || '',
                        id: link.id || ''
                    };
                }));
            }

            // Search for similar titles.
            let title = '';
            if (this.form.titleType === 'single') {
                title = this.form.postTitle;
            } else if (this.form.titleType === 'bulk' && this.form.bulkTitles) {
                // Combine all bulk titles and remove new lines.
                const titles = this.form.bulkTitles.split('\n').filter(line => line.trim());
                if (titles.length > 0) {
                    title = titles.join(' ');
                }
            }


            if (title) {
                // Get category, keywords, and goal description from the form.
                const category = this.form.category ? this.form.category : '';
                const keywords = this.form.focusKeyword ? this.form.focusKeyword + (this.form.secondaryKeywords ? ', ' + this.form.secondaryKeywords : '') : '';
                const goalDescription = this.form.postDesc ? this.form.postDesc : '';

                const similarTitles = await this.searchSimilarTitles(title, category, keywords, goalDescription);
                if (similarTitles && similarTitles.length > 0) {
                    // Send similar titles as a JSON string to ensure they're processed as a nested array.
                    payload.similar_titles = JSON.stringify(similarTitles.map(t => t.title));
                    // Also include similar titles in SEO metadata.
                    if (!payload.seo_data) {
                        payload.seo_data = [];
                    }
                    payload.seo_data.similar_titles = JSON.stringify(similarTitles.map(t => t.title));
                }
            }

            // Add action and nonce to the payload for admin-ajax.php.
            payload.action = 'postomatic_generate_post';
            payload.nonce = this.nonce.postomatic;

            fetch(ajaxurl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams(payload)
            })
                .then(r => {
                    if (!r.ok) {

                        if (r.status === 400) {
                            // Payment Required - Get the specific error message from the response.
                            return r.json().then(errorData => {
                                throw new Error(errorData.message || "Error");
                            });
                        }

                        if (r.status === 401) {
                            // Handle 401 error.
                            const nonce = this.nonce.postomatic;
                            handle401Error(nonce);
                            return;
                        }
                        if (r.status === 402) {
                            // Payment Required. The server is authoritative on the
                            // cost, and its 402 message now carries the real
                            // required amount ("You only have 5 credits, but need
                            // 12."), so show that instead of the generic line this
                            // used to substitute.
                            return r.json().then(errorData => {
                                const detail = errorData?.data?.message || errorData?.message;
                                throw new Error(detail || "You don't have enough credits to complete this action.");
                            });
                        }
                        if (r.status === 429) {
                            // Throttled. Not a credential or credit problem: tell
                            // the user to wait and do not retry automatically.
                            return r.json().then(errorData => {
                                const detail = errorData?.data?.message || errorData?.message;
                                throw new Error(detail || 'Too many requests. Please try again in a moment.');
                            }).catch(() => {
                                throw new Error('Too many requests. Please try again in a moment.');
                            });
                        }
                        if (r.status === 403) {
                            // Forbidden - Refresh nonce and retry.
                            return this.getOneTimeCode().then(newCode => {
                                // Update the nonce with the new one from the response.
                                this.nonce.postomatic = newCode;
                                payload.nonce = newCode;
                                payload.one_time_code = newCode;

                                // Retry the request with the new nonce.
                                return fetch(ajaxurl, {
                                    method: 'POST',
                                    headers: {
                                        'Content-Type': 'application/x-www-form-urlencoded',
                                    },
                                    body: new URLSearchParams(payload)
                                });
                            }).then(r => {
                                if (!r.ok) {
                                    throw new Error(`Retry failed with status ${r.status}`);
                                }
                                return r.json();
                            });
                        }

                        // Try to get the error message from the response.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            Swal.fire({
                                icon: 'error',
                                title: 'API Error',
                                html: `<div style="font-size: 16px; margin-bottom: 15px;">Error: ${r.status}</div>
                                           <div style="font-weight: bold; color: #d33;">${errorData.message || 'An error occurred while processing your request.'}</div>`,
                            });
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            Swal.fire({
                                icon: 'error',
                                title: 'API Error',
                                text: `There is an issue connecting to server. Please try again.`,
                            });
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(result => {
                    if (!result || !result.success) {
                        // stop here and show the backend’s message.
                        // WordPress wp_send_json_error() nests the payload under `data`,
                        // so the message lives at result.data.message (with legacy fallbacks).
                        const errorPayload = result?.data ?? result ?? {};
                        const message = errorPayload.message || result?.message || 'Something went wrong.';
                        const isCreditError = message.includes('credits') || message.includes('credit');

                        if (isCreditError) {
                            // For credit-related errors, show a more detailed message with a button to buy credits.
                            Swal.fire({
                                icon: 'error',
                                title: 'Not enough credits',
                                html: `<div style="font-size: 16px; margin-bottom: 15px;">${message}</div>
                                           <div style="font-weight: bold; color: #d33;">You need more credits to complete this action.</div>`,
                                showCancelButton: true,
                                confirmButtonColor: '#3085d6',
                                cancelButtonColor: '#d33',
                                confirmButtonText: 'Buy Credits',
                                cancelButtonText: 'Cancel'
                            }).then((result) => {
                                if (result.isConfirmed) {
                                    // Redirect to the purchase credits page.
                                    window.location.href = this.purchaseCreditsUrl;
                                }
                            });
                        } else {
                            // For other errors, show the standard error message.
                            Swal.fire({
                                icon: 'error',
                                title: result?.title ?? 'Error',
                                text: message,
                            });
                        }
                        return;
                    }

                    Swal.fire({
                        icon: 'info',
                        title: 'Post submitted',
                        text: 'Your post is pending approval. You can check its status at the bottom of this page.',
                        confirmButtonText: 'Got it'
                    });

                    // otherwise continue as before.
                    this.showSuccessAlert = true;
                    setTimeout(() => (this.showSuccessAlert = false), 20000);
                    this.fetchRecentPostsData();
                    this.fetchCreditData();

                    // Reset title and category fields after successful submission.
                    if (this.form.titleType === 'single') {
                        this.form.postTitle = '';
                    } else if (this.form.titleType === 'bulk') {
                        this.form.bulkTitles = '';
                    }
                    this.selectedCategoryName = '';
                    this.form.autoCategory = false;

                    // Reset Content Goal, Content Goal Type, and Select Products.
                    this.form.contentGoal = '';
                    this.form.contentGoalType = '';
                    this.form.selectedProduct = '';
                    this.form.selectedProducts = [];
                    this.selectedProductDetails = null;
                })
                .catch(err => {
                    // optional: network or unexpected errors.
                    Swal.fire({
                        icon: 'error',
                        title: 'Request failed',
                        text: err.message || 'Please try again.',
                    });
                })
                .finally(() => {
                    this.generating = false;
                });
        },
        // This method is replaced by getOneTimeCode which both generates and saves the code.
        search(term) {
            return fetch('/wp-json/custom-search/v1/posts?search=' + encodeURIComponent(term),
                {headers: {'X-WP-Nonce': this.nonce.search}}
            ).then(r => {
                if (!r.ok) {
                    // Try to get the error message from the response.
                    return r.json().then(errorData => {
                        // Show the error message from the API.
                        console.error('Search API Error:', errorData.message || `API request failed with status ${r.status}`);
                        throw new Error(errorData.message || `API request failed with status ${r.status}`);
                    }).catch(jsonError => {
                        // If we can't parse the JSON, just show the status.
                        console.error('Search API Error:', `API request failed with status ${r.status}`);
                        throw new Error(`API request failed with status ${r.status}`);
                    });
                }
                return r.json();
            });
        },

        searchSimilarTitles(title, category = '', keywords = '', goalDescription = '') {
            return new Promise((resolve, reject) => {
                const formData = new FormData();
                formData.append('action', 'postomatic_similar_titles_search');
                formData.append('title', title);
                formData.append('nonce', this.nonce.postomatic);

                // Add new parameters.
                if (category) {
                    formData.append('category', category);
                }
                if (keywords) {
                    formData.append('keywords', keywords);
                }
                if (goalDescription) {
                    formData.append('goal_description', goalDescription);
                }

                fetch(ajaxurl, {
                    method: 'POST',
                    body: formData
                })
                    .then(response => response.json())
                    .then(data => {
                        if (data.success) {
                            resolve(data.data.similar_titles);
                        } else {
                            console.error('Similar Titles Search Error:', data.data.message);
                            resolve([]); // Return empty array on error to continue the process.
                        }
                    })
                    .catch(error => {
                        console.error('Similar Titles Search Error:', error);
                        resolve([]); // Return empty array on error to continue the process.
                    });
            });
        },
        openModal(post) {
            this.currentPost = post;
            this.showPostModal = true;
        },
        confirmDelete(id) {
            this.deleteId = id;
            this.showDeleteModal = true;
        },
        deletePost(postId) {
            // Use WordPress admin-ajax.php endpoint.
            const url = ajaxurl; // WordPress global variable for admin-ajax.php.

            // Create form data.
            const formData = new FormData();
            formData.append('action', 'postomatic_delete_post');
            formData.append('post_id', postId);
            formData.append('nonce', this.nonce.postomatic);

            fetch(url, {
                method: 'POST',
                body: formData
            })
                .then(r => {
                    if (!r.ok) {
                        if (r.status === 401) {
                            // Handle 401 error.
                            const nonce = this.nonce.postomatic;
                            handle401Error(nonce);
                            return;
                        }
                        if (r.status === 402) {
                            // Payment Required - Show credit purchase dialog.
                            Swal.fire({
                                icon: 'error',
                                title: 'Payment Required',
                                html: `<div style="font-size: 16px; margin-bottom: 15px;">Your account requires payment to continue.</div>
                                           <div style="font-weight: bold; color: #d33;">Please purchase credits to continue using the service.</div>`,
                                showCancelButton: true,
                                confirmButtonColor: '#3085d6',
                                cancelButtonColor: '#d33',
                                confirmButtonText: 'Buy Credits',
                                cancelButtonText: 'Cancel'
                            }).then((result) => {
                                if (result.isConfirmed) {
                                    // Redirect to the purchase credits page.
                                    window.location.href = this.purchaseCreditsUrl;
                                }
                            });
                            return;
                        }
                        // Try to get the error message from the response/.
                        return r.json().then(errorData => {
                            // Show the error message from the API.
                            Swal.fire({
                                icon: 'error',
                                title: 'Error Deleting Post',
                                html: `<div style="font-size: 16px; margin-bottom: 15px;">Error: ${r.status}</div>
                                           <div style="font-weight: bold; color: #d33;">${errorData.message || 'An error occurred while deleting the post.'}</div>`,
                            });
                            throw new Error(errorData.message || `API request failed with status ${r.status}`);
                        }).catch(jsonError => {
                            // If we can't parse the JSON, just show the status.
                            Swal.fire({
                                icon: 'error',
                                title: 'Error Deleting Post',
                                text: `There is an issue connecting to server. Please try again.`,
                            });
                            throw new Error(`API request failed with status ${r.status}`);
                        });
                    }
                    return r.json();
                })
                .then(data => {
                    if (!data) {
                        return;
                    }
                    // WordPress returns HTTP 200 even for wp_send_json_error,
                    // so a successful HTTP status does not mean the delete worked.
                    if (data.success === false) {
                        const message = data.data?.message || data.message || 'Failed to delete the post. Please try again.';
                        Swal.fire({
                            icon: 'error',
                            title: 'Error Deleting Post',
                            text: message,
                        });
                        return;
                    }
                    this.fetchRecentPostsData();
                    this.fetchCreditData();
                })
                .catch(error => {
                    console.error('Error deleting post:', error);
                });
        },
        regeneratePost(postId) {
            this.runRegenerate(
                {action: 'postomatic_regenerate_post', post_id: postId},
                'This post is back in the queue with a pending status.'
            );
        },
        regenerateAllCancelled() {
            this.runRegenerate(
                {action: 'postomatic_regenerate_cancelled_posts'},
                'Your cancelled posts are back in the queue with a pending status.'
            );
        },
        // Shared path for both regenerate actions. Credits were refunded when the
        // post was cancelled, so the service charges them again here; the amount
        // comes from the plan and is never sent by the client.
        runRegenerate(fields, successMessage) {
            if (this.regenerating) return;

            this.regenerating = true;

            const formData = new FormData();
            Object.keys(fields).forEach(key => formData.append(key, fields[key]));
            formData.append('nonce', this.nonce.postomatic);

            fetch(ajaxurl, {
                method: 'POST',
                credentials: 'same-origin',
                body: formData
            })
                .then(r => r.json().catch(() => null).then(payload => ({status: r.status, ok: r.ok, payload})))
                .then(({status, payload}) => {
                    const message = payload?.data?.message || payload?.message || '';

                    if (payload && payload.success) {
                        Swal.fire({
                            icon: 'success',
                            title: 'Queued for regeneration',
                            text: message || successMessage,
                            confirmButtonColor: '#3085d6'
                        });

                        // Statuses and balance both changed.
                        this.fetchRecentPostsData();
                        this.fetchCreditData();
                        return;
                    }

                    if (status === 401) {
                        handle401Error(this.nonce.postomatic);
                        return;
                    }

                    if (status === 402) {
                        // The service's message carries the real amount required.
                        Swal.fire({
                            icon: 'error',
                            title: 'Not enough credits',
                            text: message || 'You do not have enough credits to regenerate this content.',
                            showCancelButton: true,
                            confirmButtonText: 'Buy Credits',
                            cancelButtonText: 'Cancel',
                            confirmButtonColor: '#3085d6',
                            cancelButtonColor: '#d33'
                        }).then(result => {
                            if (result.isConfirmed) {
                                window.location.href = this.purchaseCreditsUrl;
                            }
                        });
                        return;
                    }

                    // 429 included: a wait, not a failure to fix by retrying.
                    Swal.fire({
                        icon: status === 429 ? 'info' : 'error',
                        title: status === 429 ? 'Please wait' : 'Could not regenerate',
                        text: message || 'The content could not be re-queued. Please try again.',
                        confirmButtonColor: '#3085d6'
                    });
                })
                .catch(error => {
                    console.error('Error regenerating content:', error);
                    Swal.fire({
                        icon: 'error',
                        title: 'Could not regenerate',
                        text: 'There is an issue connecting to the server. Please try again.',
                        confirmButtonColor: '#3085d6'
                    });
                })
                .finally(() => {
                    this.regenerating = false;
                });
        },
        goToBuyCredits() {
            // redirect to your purchase‐credits page.
            window.location.href = this.purchaseCreditsUrl;
        },
        validateExactTitle() {
            // if they tick “Exact Title” but have more than one bulk post, warn and uncheck.
            if (this.form.exactTitle && this.form.bulkCount > 1 && this.form.titleType === 'single') {
                Swal.fire({
                    icon: 'warning',
                    text: 'Warning: You can’t use “Exact Title” when you’re generating multiple posts. Please choose' +
                        ' just one title, if you want to use this feature.',
                });
                // undo their click.
                this.form.exactTitle = 0;
            }
        },
        onBulkCountChange() {
            // clamp inside [1,100].
            this.form.bulkCount = Math.max(
                1,
                Math.min(100, this.form.bulkCount)
            );
            // recalc credits, enforce exact‑title rules, etc.
            this.validateExactTitle();
            this.recalculateRequiredCredit(); // Update required credits.
        },
        getBulkTitleCount() {
            // only if we're in “bulk” mode.
            if (this.form.titleType === 'single') {
                this.form.bulkCount = 1;
                return 1;
            }

            // 1. split on newlines.
            const lines = this.form.bulkTitles
                .split(/\r?\n/)
                // 2. trim each.
                .map(line => line.trim())
                // 3. drop empty ones.
                .filter(line => line !== '');

            // 4. set bulkCount (or at least 1).
            const count = lines.length || 1;
            this.form.bulkCount = count;

            // 5. clean up the textarea.
            this.form.bulkTitles = lines.join('\n');

            // 6. recalculate required credits.
            this.recalculateRequiredCredit();

            // 7. return it.
            return count;
        },
        async getOneTimeCode() {
            try {
                const response = await fetch(ajaxurl, {
                    method: "POST",
                    credentials: 'same-origin',
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded",
                    },
                    body: new URLSearchParams({
                        action: "save_one_time_code",
                        nonce: this.nonce.postomatic
                    }).toString()
                });

                const data = await response.json();

                if (data.success && data.data && data.data.code) {
                     return data.data.code;
                } else {
                     return '';
                }
            } catch (error) {
                console.error("Request failed:", error);
                return '';
            }
        },
        findChangedProperties(newObj, oldObj) {
            // If oldObj is undefined (first run), return empty array.
            if (!oldObj) return [];

            // Find all properties that have changed.
            const changedProps = [];

            // Check all properties in newObj.
            for (const prop in newObj) {
                // Skip if property doesn't exist in oldObj.
                if (!(prop in oldObj)) continue;

                // Compare values.
                if (JSON.stringify(newObj[prop]) !== JSON.stringify(oldObj[prop])) {
                    changedProps.push(prop);
                }
            }

            return changedProps;
        },
        calculateTotalCredits() {
            let total = 0;
            let titleCount = 1; // Default to 1 for single titles.

            // Loop over all form fields first.
            if (this.form && this.creditValues && this.creditValues.length > 0) {

                // Iterate through all form fields.
                Object.keys(this.form).forEach(formKey => {
                    // Skip featureImageType as it's handled separately below.
                    if (formKey === 'featureImageType') {
                        return;
                    }

                    const nameAttribute = formKey.replace(/([A-Z])/g, '_$1').toLowerCase();
                    const field = document.querySelector(`[name="${nameAttribute}"]`);

                    let creditItem = 0;
                    //const score = creditItem ? creditItem.score : 0;.

                    if (field) {
                        let fieldValue;
                        switch (field.type) {
                            case 'radio': {
                                // Get the selected radio button value.
                                if (field.checked) {
                                    fieldValue = field.value;
                                    creditItem = this.creditValues.find(item => item.key === nameAttribute)
                                    if (creditItem) {
                                        total += Number(creditItem.score) || 0;
                                    }
                                }
                                break;
                            }
                            case 'select-one': {
                                // Get the selected option value.
                                fieldValue = field.options[field.selectedIndex].value;
                                if (fieldValue) {
                                    creditItem = this.creditValues.find(item => item.key === fieldValue)
                                    if (creditItem) {
                                        total += Number(creditItem.score) || 0;
                                    }
                                }

                                break;
                            }

                            case 'checkbox': {
                                // Get the checkbox value (if checked).
                                fieldValue = field.checked ? field.value : null;
                                if (fieldValue) {
                                    creditItem = this.creditValues.find(item => item.key === nameAttribute)
                                    if (creditItem) {
                                        total += Number(creditItem.score) || 0;
                                    }

                                }

                                break;
                            }

                            default:
                                // Add handling for unknown or additional field types here.
                                break;
                        }
                    }
                });

                // Special handling for specific form elements.
                // Get the featureImageType field.
                const featureImageType = this.form.featureImageType;
                const featureImageName = 'feature_image_type';

                // Determine field type for featureImageType based on JavaScript value type.
                let fieldType = 'unknown';
                if (typeof featureImageType === 'string') {
                    fieldType = 'text';
                }

                // Try to get the actual type attribute from the DOM.
                let typeAttribute = 'unknown';
                try {
                    // Try to find the element by name attribute.
                    const element = document.querySelector(`[name="${featureImageName}"]`);

                    if (element) {
                        // Get the type attribute if it exists.
                        typeAttribute = element.getAttribute('type') || element.tagName.toLowerCase();
                    } else {
                        // If element not found by name, try other selectors.
                        const elements = document.querySelectorAll(`[name="${featureImageName}"]`);
                        if (elements.length > 0) {
                            typeAttribute = elements[0].getAttribute('type') || elements[0].tagName.toLowerCase();
                        }
                    }
                } catch (e) {
                    console.error('Error getting type attribute for featureImageType:', e);
                }


                // Check if it's an AI-generated feature image.
                if (fieldType === 'text' && featureImageType === 'ai') {
                    // Add score for AI-generated feature image.
                    // Convert featureImageType to feature_image_type for name attribute.
                    const featureImageName = 'feature_image_type';

                    const aiImageCredit = this.creditValues.find(item =>
                        item.key === 'ai' || item.key === 'ai_image' || item.key === featureImageName);

                    if (aiImageCredit && aiImageCredit.score) {
                        total += Number(aiImageCredit.score) || 0;
                    } else {
                        // Default value if not found.
                        total += 1;
                    }
                }
            }

            // Using the form values directly here as this is internal logic, not credit calculation.
            if (this.form.titleType === 'bulk') {
                const manual = this.manualBulkTitlesCount;    // lines you typed in the textarea.
                if (manual > 0) {
                    titleCount = manual;
                }
            } else if (this.form.bulkCount > 1) {
                titleCount = this.form.bulkCount;
            }

            // ---- apply that multiplier once ----.
            if (titleCount > 1) {
                total *= titleCount;
            }

            return total;
        },
        applyFeatureRestrictions() {
            const plan = this.planSlug; // 'starter', 'basic', or 'pro-ultimate'.

            // for every element you marked with data-basic or data-premium….
            this.$el.querySelectorAll('[data-basic], [data-premium]').forEach(container => {
                const isBasic = container.getAttribute('data-basic') === 'true';
                const isPremium = container.getAttribute('data-premium') === 'true';

                // decide whether it should be disabled.
                let shouldDisable;
                if (plan === 'pro-ultimate') {
                    shouldDisable = false;                       // pro: everything enabled.
                } else if (plan === 'basic') {
                    shouldDisable = (isPremium && !isBasic);     // basic: premium‑only off.
                } else {
                    shouldDisable = (isBasic || isPremium);      // free: basic+premium off.
                }

                // find all real controls inside (inputs, selects, textareas, buttons).
                const controls = [];
                if (container.matches('input,select,textarea,button')) {
                    controls.push(container);
                }
                controls.push(...container.querySelectorAll('input,select,textarea,button'));

                // apply the disabled state.
                controls.forEach(ctrl => {
                    ctrl.disabled = shouldDisable;
                });
            });
        },

        // Product search and selection methods.
        searchProducts: _.debounce(function () {
            this.isLoadingProducts = true;
            this.fetchProducts(this.productSearchQuery, 1);
        }, 500),

        fetchProducts(search = '', page = 1) {
            // Use the WordPress AJAX API to fetch products.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_products');
            formData.append('nonce', this.nonce.postomatic);
            formData.append('search', search);
            formData.append('page', page);

            // Store the currently selected product IDs before fetching new products.
            const selectedProductIds = this.form.selectedProducts || [];
            const singleSelectedProductId = this.form.selectedProduct;

            // Store the currently selected product details.
            const selectedProductDetails = this.selectedProductDetails;

            fetch(ajaxurl, {
                method: 'POST',
                credentials: 'same-origin',
                body: formData
            })
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        // Get the new products from the API response.
                        let newProducts = data.data.products;

                        // If we have selected products, make sure they're included in the products array.
                        if (selectedProductIds.length > 0 || singleSelectedProductId) {
                            // Create a map of product IDs to avoid duplicates.
                            const productMap = {};

                            // Add all new products to the map.
                            newProducts.forEach(product => {
                                productMap[product.id] = product;
                            });

                            // Add selected products from the current products array if they're not already in the map.
                            this.products.forEach(product => {
                                if ((selectedProductIds.includes(product.id) || product.id === singleSelectedProductId) && !productMap[product.id]) {
                                    productMap[product.id] = product;
                                }
                            });

                            // Convert the map back to an array.
                            newProducts = Object.values(productMap);
                        }

                        // Update the products array with the new products.
                        this.products = newProducts;
                        this.productPagination = data.data.pagination;

                        // Restore the selected product details if it was set.
                        if (selectedProductDetails && singleSelectedProductId) {
                            this.selectedProductDetails = selectedProductDetails;
                        }
                    } else {
                        console.error('Error fetching products:', data.data);
                        this.products = [];
                    }
                })
                .catch(error => {
                    console.error('Error fetching products:', error);
                    this.products = [];
                })
                .finally(() => {
                    this.isLoadingProducts = false;
                });
        },

        // Humanize a camelCase variable name to Title Case with spaces.
        humanizeVariableName(name) {
            // Replace camelCase with spaces and capitalize each word.
            return name
                .replace(/([A-Z])/g, ' $1') // Insert a space before all capital letters.
                .replace(/^./, function (str) {
                    return str.toUpperCase();
                }) // Capitalize the first letter.
                .trim(); // Remove any leading/trailing spaces.
        },

        // Get a product by its ID.
        getProductById(productId) {
            if (!productId || !this.products || this.products.length === 0) {
                return null;
            }
            return this.products.find(product => product.id === productId) || null;
        },

        // Fetch content goal types for products.
        fetchContentGoalTypes() {
            // Make an AJAX request to the WordPress admin-ajax.php.
            const formData = new FormData();
            formData.append('action', 'postomatic_get_content_goal_types');
            formData.append('nonce', this.nonce.postomatic);

            // Use the WordPress admin-ajax.php endpoint.
            fetch(ajaxurl, {
                method: 'POST',
                credentials: 'same-origin',
                body: formData
            })
                .then(response => response.json())
                .then(data => {
                    if (data.success && data.data) {
                        this.contentGoalTypes = data.data;
                    }
                })
                .catch(err => {
                    console.error('Error fetching content goal types:', err);
                    // If there's an error, we'll keep using an empty array.
                    this.contentGoalTypes = [];
                });
        },

        // Handle product selection based on content goal type.
        selectProduct(productId) {
            if (!this.form.contentGoalType) {
                // Show alert if no content goal type is selected.
                alert("Please select a Content Goal Type before selecting products.");
                return;
            }

            // Initialize selectedProducts array if it doesn't exist.
            if (!this.form.selectedProducts) {
                this.form.selectedProducts = [];
            }

            if (this.isProductType) {
                const index = this.form.selectedProducts.indexOf(productId);

                if (index === -1) {
                    // Product is not already selected, try to add it.
                    if (this.isProductComparisonType) {
                        // For product comparison type, allow up to 3 products.
                        if (this.form.selectedProducts.length < 3) {
                            this.form.selectedProducts.push(productId);
                        } else {
                            // Show alert if user tries to select more than 3 products.
                            alert("You cannot select more than 3 products for comparison.");
                            return; // Exit the function to prevent adding a 4th product.
                        }
                    } else {
                        // For other product types, limit to 1 product.
                        this.form.selectedProducts = [productId];
                        this.form.selectedProduct = productId;

                        // Find the selected product to display its details.
                        const selectedProduct = this.products.find(product => product.id === productId);
                        if (selectedProduct) {
                            // Create a copy of the selected product.
                            this.selectedProductDetails = {...selectedProduct};

                            // Add permalink if it doesn't exist.
                            if (!this.selectedProductDetails.permalink) {
                                // Get the site URL from the window.location object.
                                const siteUrl = window.location.origin;

                                // In WordPress, product permalinks typically follow the pattern:.
                                // If the product has a slug property, use it to construct the permalink.
                                if (this.selectedProductDetails.slug) {
                                    this.selectedProductDetails.permalink = `${siteUrl}/product/${this.selectedProductDetails.slug}/`;
                                } else {
                                    // If no slug is available, try to create one from the name.
                                    const slug = this.selectedProductDetails.name
                                        ? this.selectedProductDetails.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '')
                                        : `product-${this.selectedProductDetails.id}`;
                                    this.selectedProductDetails.permalink = `${siteUrl}/product/${slug}/`;
                                }
                            }
                        }
                    }
                } else {
                    // Product is already selected, remove it.
                    this.form.selectedProducts.splice(index, 1);

                    // If it's not a comparison type and this was the selected product, clear it.
                    if (!this.isProductComparisonType && this.form.selectedProduct === productId) {
                        this.form.selectedProduct = null;
                        this.selectedProductDetails = null;
                    }
                }
            } else {
                // For non-product types, only allow single selection.
                if (this.form.selectedProduct === productId) {
                    // If clicking the same product again, deselect it.
                    this.form.selectedProduct = null;
                    this.selectedProductDetails = null;

                    // Also remove from selectedProducts array.
                    const index = this.form.selectedProducts.indexOf(productId);
                    if (index !== -1) {
                        this.form.selectedProducts.splice(index, 1);
                    }
                } else {
                    // Select this product.
                    this.form.selectedProduct = productId;

                    // Also add to selectedProducts array for consistency.
                    if (!this.form.selectedProducts.includes(productId)) {
                        // Clear previous selections for non-product types.
                        this.form.selectedProducts = [productId];
                    }

                    // Find the selected product to display its details.
                    const selectedProduct = this.products.find(product => product.id === productId);
                    if (selectedProduct) {
                        // Create a copy of the selected product.
                        this.selectedProductDetails = {...selectedProduct};

                        // Add permalink if it doesn't exist.
                        if (!this.selectedProductDetails.permalink) {
                            // Get the site URL from the window.location object.
                            const siteUrl = window.location.origin;

                            // In WordPress, product permalinks typically follow the pattern: /product/product-slug/.
                            // If the product has a slug property, use it to construct the permalink.
                            if (this.selectedProductDetails.slug) {
                                this.selectedProductDetails.permalink = `${siteUrl}/product/${this.selectedProductDetails.slug}/`;
                            } else {
                                // If no slug is available, try to create one from the name.
                                const slug = this.selectedProductDetails.name
                                    ? this.selectedProductDetails.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '')
                                    : `product-${this.selectedProductDetails.id}`;
                                this.selectedProductDetails.permalink = `${siteUrl}/product/${slug}/`;
                            }
                        }
                    }
                }
            }
        },

        // Remove a product from the comparison list.
        removeProductFromComparison(productId) {
            const index = this.form.selectedProducts.indexOf(productId);
            if (index !== -1) {
                this.form.selectedProducts.splice(index, 1);
            }
        },

        changePage(page) {
            if (page < 1 || page > this.productPagination.total_pages) {
                return;
            }

            this.isLoadingProducts = true;
            this.fetchProducts(this.productSearchQuery, page);
        },

    },
    mounted() {
        this.applyFeatureRestrictions();
        this.init();
        this.calculateTotalCredits();

        // Set up interval to fetch credit data every 12 seconds
        this.creditDataInterval = setInterval(() => {
            this.fetchCreditData();
        }, 12000);

        // Add global click event listener to clear errors when any button is clicked.
        document.addEventListener('click', (event) => {
            if (event.target.tagName === 'BUTTON' ||
                event.target.closest('button') ||
                event.target.tagName === 'A' ||
                event.target.closest('a') ||
                event.target.classList.contains('btn') ||
                event.target.closest('.btn')) {
                this.clearErrors();
            }
        });
        this.$nextTick(() => {
            this.initializeFeatureBadges();

            // Initialize select2 for language dropdown.
            if (jQuery && jQuery.fn.select2 && this.$refs.languageSelect) {
                const languageSelect = jQuery(this.$refs.languageSelect);

                // Initialize select2.
                languageSelect.select2({
                    placeholder: 'Select a language',
                    allowClear: true,
                    width: '100%',
                    dropdownCssClass: 'language-select-dropdown',
                    minimumResultsForSearch: 0, // Always show search box (0 forces it to show).
                    minimumInputLength: 0, // Allow empty searches.
                    searchInputPlaceholder: 'Search languages...', // Add placeholder to search input.
                    templateResult: function (data) {
                        // This ensures the dropdown items are properly displayed.
                        if (data.loading) return data.text;
                        return jQuery('<span>' + data.text + '</span>');
                    }
                });

                // Set initial value if available.
                if (this.form.language) {
                    languageSelect.val(this.form.language).trigger('change');
                }

                // Handle select2 change event using Vue method.
                languageSelect.on('change', (e) => {
                    const selectedValue = e.target.value;
                    this.onLanguageChange(selectedValue);
                });
            }

            // Initialize select2 for category dropdown.
            if (jQuery && jQuery.fn.select2 && this.$refs.categorySelect) {
                const categorySelect = jQuery(this.$refs.categorySelect);

                // Initialize select2.
                categorySelect.select2({
                    placeholder: 'Select a category',
                    allowClear: true,
                    width: '100%',
                    dropdownCssClass: 'category-select-dropdown',
                    minimumResultsForSearch: 0, // Always show search box (0 forces it to show).
                    minimumInputLength: 0, // Allow empty searches.
                    searchInputPlaceholder: 'Search categories...', // Add placeholder to search input.
                    templateResult: function (data) {
                        // This ensures the dropdown items are properly displayed.
                        if (data.loading) return data.text;
                        return jQuery('<span>' + data.text + '</span>');
                    }
                });

                // Set initial value if available.
                if (this.form.category) {
                    categorySelect.val(this.form.category).trigger('change');
                }

                // Handle select2 change event using Vue method.
                categorySelect.on('change', (e) => {
                    const selectedValue = e.target.value;
                    this.onCategoryChange(selectedValue);
                });
            }
        });

        // hide the overlay after load.
        const o = document.getElementById('loading-overlay');
        if (o) {
            o.classList.add('hide');
            setTimeout(() => (o.style.display = 'none'), 300);
        }
        // Initialize products list if WooCommerce is active and products exist.
        if (this.woocommerceActive && this.hasProducts) {
            this.fetchProducts();
        }
        // poll recent posts.
        setInterval(() => {
            if (this.pagination && this.pagination.current_page === 1) {
                this.fetchRecentPostsData();
            }
        }, 8000);


    },
    beforeDestroy() {
        // Clear the credit data interval to prevent memory leaks
        if (this.creditDataInterval) {
            clearInterval(this.creditDataInterval);
        }
    }
});
