/**
 * VMO AI Consultation System - HYBRID INTELLIGENT APPROACH
 * Version: 9.0.1 - CRITICAL FIX: Prevents AI self-response loop
 * 
 * This system uses:
 * - REAL AI (GPT-4o) for ALL conversation responses (no canned responses!)
 * - State tracker to monitor what information has been collected
 * - Flexible, natural conversation flow
 * - AI adapts questioning based on business type (products vs services)
 * 
 * CRITICAL FIX: Speech recognition STOPS when AI speaks to prevent feedback loop
 */

// Email Categories - Updated from legacy auto-provisioning to user-provided IMAP/SMTP
const VMO_EMAIL_CATEGORIES = ['general', 'support', 'transactions', 'customercare', 'bizdev', 'marketing', 'newsletter'];
const VMO_EMAIL_CATEGORY_LABELS = {
    general: 'General Info',
    support: 'Support',
    transactions: 'Billing & Payments',
    customercare: 'Customer Care',
    bizdev: 'Business Development',
    marketing: 'Marketing',
    newsletter: 'Newsletter & Subscriber Updates'
};

// Legacy alias for backward compatibility
const VMO_PROVISIONING_ROLES = VMO_EMAIL_CATEGORIES;
const VMO_PROVISIONING_ROLE_LABELS = VMO_EMAIL_CATEGORY_LABELS;

// Bulk Email Provider Configuration
const VMO_BULK_EMAIL_PROVIDERS = {
    brevo: {
        name: 'Brevo (formerly Sendinblue)',
        apiKeyLabel: 'API Key',
        apiKeyHelp: 'Get your API key from https://app.brevo.com/settings/keys/api',
        docsUrl: 'https://developers.brevo.com/docs',
        features: ['campaigns', 'transactional', 'automation', 'contacts']
    },
    mailerlite: {
        name: 'MailerLite',
        apiKeyLabel: 'API Key',
        apiKeyHelp: 'Get your API key from https://app.mailerlite.com/integrations/api/',
        docsUrl: 'https://developers.mailerlite.com/docs',
        features: ['campaigns', 'automation', 'subscribers', 'landing_pages']
    }
};

// Common IMAP/SMTP Provider Presets
const VMO_EMAIL_PROVIDER_PRESETS = {
    hosting: {
        name: 'My Web Hosting (cPanel/Plesk)',
        imap: { host: 'mail.', port: 993, encryption: 'ssl' },
        smtp: { host: 'mail.', port: 587, encryption: 'tls' },
        note: 'Uses mail.yourdomain.com. Common for cPanel, Plesk, DirectAdmin hosts.',
        hostingBased: true
    },
    gmail: {
        name: 'Gmail / Google Workspace',
        imap: { host: 'imap.gmail.com', port: 993, encryption: 'ssl' },
        smtp: { host: 'smtp.gmail.com', port: 587, encryption: 'tls' },
        note: 'Requires App Password if 2FA enabled. Enable IMAP in Gmail settings.'
    },
    outlook: {
        name: 'Outlook / Microsoft 365',
        imap: { host: 'outlook.office365.com', port: 993, encryption: 'ssl' },
        smtp: { host: 'smtp.office365.com', port: 587, encryption: 'tls' },
        note: 'Use your full email address as username.'
    },
    yahoo: {
        name: 'Yahoo Mail',
        imap: { host: 'imap.mail.yahoo.com', port: 993, encryption: 'ssl' },
        smtp: { host: 'smtp.mail.yahoo.com', port: 587, encryption: 'tls' },
        note: 'Requires App Password. Generate in Yahoo Account Security settings.'
    },
    zoho: {
        name: 'Zoho Mail',
        imap: { host: 'imap.zoho.com', port: 993, encryption: 'ssl' },
        smtp: { host: 'smtp.zoho.com', port: 587, encryption: 'tls' },
        note: 'Use your Zoho email as username.'
    },
    custom: {
        name: 'Custom / Other Provider',
        imap: { host: '', port: 993, encryption: 'ssl' },
        smtp: { host: '', port: 587, encryption: 'tls' },
        note: 'Enter your email provider\'s IMAP and SMTP server details.'
    }
};

console.log('�� VMO Intelligent Consultation v9.0.1 - FIXED VERSION Loading...');

// Admin-AJAX URL helper (the plugin localizes `ajaxurl`, but some code paths historically used `ajax_url`).
function vmoGetAdminAjaxUrl() {
    try {
        if (typeof vmoAjax !== 'undefined' && vmoAjax && vmoAjax.ajaxurl) return vmoAjax.ajaxurl;
        if (typeof vmo_ajax !== 'undefined' && vmo_ajax) {
            if (vmo_ajax.ajaxurl) return vmo_ajax.ajaxurl;
            if (vmo_ajax.ajax_url) return vmo_ajax.ajax_url;
        }
        if (typeof ajaxurl !== 'undefined' && ajaxurl) return ajaxurl;
    } catch (_) {}
    return '/wp-admin/admin-ajax.php';
}

class VMOIntelligentConsultation {
    constructor() {
        // Core state
        this.sessionId = (() => {
            // Prefer an existing session id so consultations can resume after reload
            try {
                const fromWindow = (typeof window !== 'undefined')
                    ? (window.vmoConsultationSessionId || window.vmoCurrentConsultationSessionId)
                    : '';
                const fromStorage = (typeof localStorage !== 'undefined')
                    ? localStorage.getItem('vmoConsultationSessionId')
                    : '';
                const existing = (fromWindow || fromStorage || '').trim();
                if (existing) return existing;
            } catch (_) {}
            const created = this.generateSessionId();
            try {
                if (typeof localStorage !== 'undefined') {
                    localStorage.setItem('vmoConsultationSessionId', created);
                }
            } catch (_) {}
            return created;
        })();
        // Expose current consultation session globally so other modules (e.g., Jarvis voice panel)
        // can align context and avoid asking for already-provided information
        try { window.vmoCurrentConsultationSessionId = this.sessionId; } catch (_) {}
        try {
            window.vmoBusinessConsultantVoice = 'onyx';
        } catch (_) {}
        this.userDisplayName = this.resolveUserDisplayName();
        this.conversationHistory = [];
        // CANONICAL DATA STRUCTURE - aligned with backend
        this.consultation_data = {
            extracted_information: {
                businessOverview: null,
                businessName: null,
                industry: null,
                offerings: [],
                targetAudience: null,
                location: null,
                pricing: [],
                operations: {
                    payments: null,
                    booking: null,
                    phone: null,
                    email: null,
                    analytics: null
                },
                social: {},
                hours: null,
                // Legacy compat
                businessIdea: null,
                offeringType: null,
                businessGoals: null,
                websitePreferences: null,
                brandingInfo: null,
                needsEcommerce: null,
                needsBooking: false,
                needsPhoneSystem: true,
                needsEmailMarketing: true,
                // ✅ Custom pages and special features requested by user
                customPages: [], // ['affiliate-signup', 'faq', 'referral-calculator', 'partner-dashboard', etc.]
                specialFeatures: [], // ['affiliate-calculator', 'membership-area', 'booking-calendar', etc.]
                // ✅ Asset requirements
                hasLogo: false,
                logoUrl: null,
                hasProductImages: false,
                productImages: [],
                hasVideos: false,
                videoUrls: [],
                hasAudioFiles: false,
                audioFiles: [], // [{url, role:'podcast'|'music'|'voice_intro', title}]
                hasDownloadables: false,
                downloadableDocuments: [], // [{url, title, role:'brochure'|'menu'|'pricelist'|'guide'}]
                brandAssets: [], // [{type:'logo'|'image'|'video', url, description}]
                // ✅ Products/Services Inventory with linked media + 360° views
                products_inventory: [], // [{id:'product_001', name, description, price, currency, category, 
                                         //   main_image:{url}, gallery_images:[], 
                                         //   product_360:{has_360:bool, needs_ai_angles:bool, angle_images:[{url,angle:'front'|'back'|'left'|'right'|'45deg'}], spin_video_url?},
                                         //   videos:[{url,role:'demo'|'promo'}], variants:[], stock_quantity, is_digital, requires_shipping}]
                services_inventory: [], // [{id:'service_001', name, description, price, duration, booking_required, availability, images:[], videos:[]}]

                // ✅ Payment collection (not all payments are product sales)
                // `collects_payments`: whether visitors will pay on the website (products checkout OR donations/contributions)
                // `mode`: 'products' | 'donations' | 'none' | null
                // `confirmed`: true only after the user explicitly confirms
                payment_collection: {
                    collects_payments: null,
                    mode: null,
                    confirmed: false
                },

                // ✅ Ecommerce/Payments generation hints (consumed by backend generation)
                // `collection_mode`: 'products' | 'donations' | null
                ecommerce: {
                    collection_mode: null,
                    donation_funds: []
                }
            },
            documents: [], // [{id,title,url,mime,chunks:[{id,text}],saved_at}]
            // Media files with product_id linking for product images/videos
            media_files: [], // [{id,url,mime,title,role:'hero|gallery|product_image|product_video|product_360|logo|bg',product_id?:'product_001',product_name?:'Product A',angle?:'front',meta:{}}]
            consultation_flow: {
                phases: [
                    "Phase 1: Business Foundations Intake",
                    "Phase 2: Executive Blueprint Confirmation",
                    "Phase 3: Deep Industry & Market Research",
                    "Phase 4: Strategic Recommendations & Decisions",
                    "Phase 5: Website & Experience Architecture",
                    "Phase 6: Systems, Automations & Compliance",
                    "Phase 7: Roadmap & Daily Execution Model",
                    "Phase 8: Launch Readiness & Deliverables"
                ],
                current_index: 0,
                status: "in_progress",
                timestamps: {}
            },
            scenario_type: 'scenario_a_new',
            business_stage: 'new_business',
            phase_flags: {
                phase2_confirmed: false,
                phase3_completed: false,
                phase4_presented: false
            },
            research_results: null,
            recommendation_checklist: null,
            doc_locked: false,
            phone_provisioning_status: null,
            // ✅ Scanned websites for reference/inspiration/competitor analysis
            scanned_websites: {} // {url: {scanned_at, purpose, title, analysis:{}, headings:[], navigation:[]}}
        };

        this.roleMailboxes = Array.isArray(VMO_PROVISIONING_ROLES)
            ? VMO_PROVISIONING_ROLES.slice()
            : ['ceo', 'operations', 'finance', 'hr', 'compliance', 'marketing'];

        this.state = this.buildInitialState();
        this.phaseSidebarEl = null;
        this.hasInjectedPhaseStyles = false;
        this.bootstraptingState = false;
        this._ajaxFallbackWarned = false;
        this._ajaxWarnedMissingNonce = false;
        
        // Legacy reference for backward compat
        this.collectedInformation = this.consultation_data.extracted_information;
        
        // Topics to guide conversation (not rigid questions!)
        this.consultationTopics = [
            'website_status', // New vs Existing
            'business_concept', // What they want to create/sell
            'target_market', // Who they serve
            'offerings', // What they offer (products/services)
            'business_goals', // What they want to achieve
            'website_vision', // How they envision their online presence
            'custom_pages', // ✅ NEW: Specific pages they want (FAQ, affiliate, calculator, etc.)
            'brand_assets' // ✅ NEW: Logo, product images, videos they have or need
        ];
        
        this.topicsCovered = new Set();
        this.pendingDocumentQuestions = [];
        this.totalDocumentFollowups = 0;
        this.awaitingDocumentAnswer = null;
        this.documentFollowupActive = false;
        this.lastDocumentSummaryDocId = null;
        this.lastDocumentSummarySignature = null;
        
        // Voice & UI state
        this.isVoiceEnabled = false;
        this.recognition = null;
        this.synthesis = null;
        this.isSpeaking = false;
        this.isProcessing = false;
        this._pendingVoiceMessage = '';
        this._drainingVoiceQueue = false;
        this._restartMicTimer = null;
        this._ttsWatchdogTimer = null;
        this.currentAudio = null;
        this._lastSpokenText = '';
        this._ttsStartTime = 0;           // Timestamp when TTS playback started
        this._ttsWordsPerSecond = 2.8;     // Average speaking rate (~168 WPM for TTS HD)
        this._bargeInTriggeredAt = 0;
        
        // ✅ FIX: Speech debounce to prevent cutting off users mid-sentence
        this._speechDebounceTimer = null;
        this._accumulatedTranscript = '';
        this._speechDebounceMs = 3500;    // Wait 3.5 seconds of TRUE silence (no speech, no interim) before sending
        this._maxSpeechDurationMs = 180000; // Allow up to 3 minutes of continuous speaking
        this._speechStartTime = null;      // Track when user started speaking
        this._isReturningUser = false;    // Track if user has previous session data
        this._bargeInCandidate = '';
        this._bargeInCandidateCount = 0;
        
        // ✅ LIVE TRANSCRIPT DISPLAY: Show what user/AI is saying in real-time
        this._liveTranscriptEl = null;      // Reference to transcript display element
        this._interimTranscript = '';       // Current interim (not-final) transcript
        this._transcriptBuffer = '';        // Accumulated final transcripts
        
        // ✅ FIX: Synchronized text+audio streaming
        this._activeTextAnimation = null;       // Reference to current text animation interval
        this._activeMessageElement = null;      // Reference to message content element being animated
        this._textRevealIndex = 0;              // Current character index for progressive reveal
        this._fullMessageText = '';             // Full message to reveal

        // ✅ Quote feature: allow users to select and quote text from previous messages
        this._quotedText = null;                // Currently quoted text
        this._quotePopup = null;                // Quote popup element reference

        // SpeechRecognition safety guards (prevents double-start + self-loop)
        this._recognitionActive = false;
        this._recognitionStarting = false;
        
        // ✅ SELF-LOOP PREVENTION: TTS timing guards
        this._ttsStartedAt = 0;             // When TTS started playing
        this._ttsEndedAt = 0;               // When TTS finished playing
        this._ttsEndCooldownMs = 1800;      // Wait 1.8s after TTS ends before accepting speech (prevents self-loop)

        // VAD-based barge-in (mic level detection) so we can keep SpeechRecognition
        // fully OFF while the AI is talking, but still allow natural interruption.
        this._vad = {
            enabled: true,
            stream: null,
            audioContext: null,
            analyser: null,
            rafId: null,
            aboveSince: 0,
            lastTripAt: 0
        };
        // Tuneable: lower = more sensitive
        // ✅ BARGE-IN FIX: Lowered thresholds — echoCancellation removes TTS audio,
        // so the remaining signal is mostly user voice. Old values (0.08/160ms) were
        // too conservative and made barge-in nearly impossible.
        this._vadThreshold = 0.048;  // More sensitive so natural interruption cuts in sooner
        this._vadHoldMs = 180;       // Shorter sustained speech window for faster barge-in
        this._vadCooldownMs = 900;
        this._vadBaselineRms = 0;         // Adaptive baseline: tracks ambient noise floor
        this._vadBaselineReady = false;   // Whether baseline has been calibrated
        this._vadCalibrationFrames = 0;   // Counter for initial calibration frames
        this._vadCalibrationTargetFrames = 18;
        this.stagedAttachments = []; // ChatGPT-style: files staged before sending
        this.blueprintRaw = null;
        this.blueprintSummary = null;
        this.scenarioType = 'scenario_a_new';
        this.businessStage = 'new_business';
        this.priorBusinessInfo = null;
        this.phase3ResearchRequested = false;
        this.phase4ChecklistPresented = false;
        this.allowedActions = null;
        this.lastParsedDocument = null;
        this.lastDocMissingItems = [];
        
        // ✅ FIX: Flags to prevent repeated paste detection prompts
        // Once documents are loaded from the database, we don't need to keep asking about them
        this._documentsLoadedFromDB = false;
        this._skipPasteDetection = false;
        this._pastedContentChoice = null;
        this._awaitingPastedContentChoice = false;
        this._pendingPastedContent = null;
        
        this.provisioningSaveTimer = null;
        this.provisioningSaveInFlight = false;
        this.provisioningSavePending = false;
        this.provisioningRefreshing = false;
        this.defaultGreetingText = `Welcome, {{user_name}}. I'm Jerreme, and I'll lead your VMO Business Wizard consultation.

    Jarvis and the rest of the VMO AI team are already standing by across design, content, SEO, media, documents, automation, and launch operations. My job is to turn your goals into a clear build plan we can execute inside your website builder without overwhelm.

    We'll keep this focused and easy. First, tell me this: are we building a brand new business from scratch, or are we improving an existing business?

    You can type, use speech-to-text, tap the mic, or upload documents if that is faster. I'll keep things simple, efficient, and moving forward.`;
        this.initialGreetingText = this.resolveGreetingText();
        this.greetingHasPlayed = false;
        this.isGreetingPlaying = false;
        this.pendingGreetingAudio = null;
        this.preloadedGreetingAudio = null;
        this.greetingPreloadPromise = null;

        // ✅ Track whether user has manually edited the transcribed text
        this._userManuallyEdited = false;
        
        // ✅ FIX: Guards to prevent double-trigger issues
        this._consultationStarted = false;
        this._setupEventListenersRan = false;
        
        // Feature flag: enable canonical consultation_data saves from frontend
        if (typeof window.vmo_consultant_context_v1 === 'undefined') {
            window.vmo_consultant_context_v1 = true;
        }

        // Global helper for site import
        window.vmoStartSiteImport = (url) => {
            // Show loading state
            const btn = document.querySelector(`button[onclick="window.vmoStartSiteImport('${url}')"]`);
            if (btn) {
                btn.innerHTML = '⏳ Importing Content...';
                btn.disabled = true;
            }
            
            // Real AJAX call to backend to fetch and parse the URL
            const formData = new FormData();
            formData.append('action', 'vmo_import_site');
            formData.append('nonce', typeof vmoAjax !== 'undefined' ? vmoAjax.nonce : '');
            formData.append('url', url);

            fetch(typeof vmoAjax !== 'undefined' ? vmoAjax.ajaxurl : '/wp-admin/admin-ajax.php', {
                method: 'POST',
                body: formData,
                credentials: 'same-origin'
            })
            .then(res => res.json())
            .then(resp => {
                if (resp.success && resp.data) {
                    const d = resp.data;
                    let summary = `✅ Successfully imported content from **${url}**!\n\n`;
                    if (d.title) summary += `**Site Title:** ${d.title}\n`;
                    if (d.meta_description) summary += `**Description:** ${d.meta_description}\n`;
                    if (d.headings_count) summary += `**Headings found:** ${d.headings_count}\n`;
                    if (d.images_count) summary += `**Images found:** ${d.images_count}\n`;
                    if (d.nav_links && d.nav_links.length) {
                        summary += `**Navigation pages:** ${d.nav_links.map(l => l.text).join(', ')}\n`;
                    }
                    if (d.technologies && d.technologies.length) {
                        summary += `**Technologies detected:** ${d.technologies.join(', ')}\n`;
                    }
                    summary += `\nI've analyzed your existing site and saved the content. I'll use this to create a modernized version that preserves your branding while enhancing the design and features.`;

                    this.addMessageToUI('ai', summary);

                    // Update state to reflect existing site mode
                    this.consultation_data.business_stage = 'existing_business';
                    this.consultation_data.extracted_information.websitePreferences = 'modernize_existing';
                    this.consultation_data.extracted_information.importedSite = d;

                    if (window.vmoOpenPreview) {
                        window.vmoOpenPreview('modernized');
                    }
                } else {
                    const errMsg = resp.data?.message || 'Could not import the website. Please check the URL and try again.';
                    this.addMessageToUI('ai', `⚠️ ${errMsg}`);
                }
            })
            .catch(err => {
                console.error('Site import error:', err);
                this.addMessageToUI('ai', `⚠️ There was a network error importing the site. Please check that the URL is correct and try again.`);
            })
            .finally(() => {
                if (btn) {
                    btn.innerHTML = '🔄 Re-import';
                    btn.disabled = false;
                }
            });
        };

        // ✅ NEW: Global helper for Radio Button choices in chat
        window.vmoSubmitChoice = (val) => {
            // Disable all radios in the group to prevent double-click
            const radios = document.querySelectorAll('input[name="preference_choice"]');
            radios.forEach(el => el.disabled = true);

            // Find main instance and queue message
            // We find the chat input and simulate sending
            const input = document.querySelector('#vmo-user-input, .vmo-chat-input textarea, #user-input');
            const btn = document.querySelector('#vmo-send-btn, .vmo-send-btn, #send-btn');
            
            if (input && btn) {
                // native setter for React/Vue compatibility if needed, though this is likely vanilla/jQuery
                const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
                if (nativeInputValueSetter) {
                    nativeInputValueSetter.call(input, "I choose: " + val);
                } else {
                    input.value = "I choose: " + val;
                }
                
                const event = new Event('input', { bubbles: true });
                input.dispatchEvent(event);
                
                setTimeout(() => btn.click(), 100);
            } else {
                console.error('VMO: Could not find chat input to submit choice.');
            }
        };

        this.enforceSystemRecommendations();
        this.init();
    }

    enqueueVoiceMessage(text = '') {
        const msg = (text || '').trim();
        if (!msg) return;
        // If we are mid-turn, store the latest utterance and process it right after.
        if (this.isProcessing || this.isSpeaking) {
            this._pendingVoiceMessage = msg;
            return;
        }
        this.handleUserMessage(msg, true);
    }

    hasPhase2EmailRolesConfirmed() {
        const roles = (this.state && this.state.email_roles && typeof this.state.email_roles === 'object') ? this.state.email_roles : null;
        if (!roles) return false;
        const anySet = Object.values(roles).some(v => typeof v === 'string' && v.trim() && v.includes('@'));
        const metaConfirmed = !!(this.state?.email_roles_meta && this.state.email_roles_meta.roles_confirmed);
        return anySet && metaConfirmed;
    }

    hydrateEmailRolesFromExtractedInfo() {
        try {
            const prefs = this.consultation_data?.extracted_information?.email_preferences;
            if (!prefs || typeof prefs !== 'object') return;
            const roles = prefs.roles || prefs.email_roles || prefs;
            if (!roles || typeof roles !== 'object') return;
            if (!this.state.email_roles || typeof this.state.email_roles !== 'object') {
                this.state.email_roles = this.buildDefaultEmailRoleMap();
            }
            Object.keys(roles).forEach(k => {
                const val = roles[k];
                if (typeof val === 'string' && val.trim()) {
                    this.state.email_roles[k] = val.trim();
                }
            });
            if (prefs.roles_confirmed === true) {
                this.state.email_roles_meta = this.state.email_roles_meta || this.buildDefaultEmailMeta();
                this.state.email_roles_meta.roles_confirmed = true;
            }
        } catch (_) {}
    }

    promptPhase2EmailRoles() {
        const enforcedDomain = this.getEnforcedDomain() || 'yourdomain.com';
        const msg = `📧 **Before we lock Phase 2, please confirm your 7 professional business email addresses** (these will power your website forms, contact page, and email routing).

Here are the 7 recommended email addresses:

1. 📧 **info@${enforcedDomain}** — General inquiries (powers: Contact form, website header)
2. 📧 **support@${enforcedDomain}** — Customer support (powers: Support tickets, help desk)
3. 📧 **billing@${enforcedDomain}** — Invoices & payments (powers: Order confirmations, receipts)
4. 📧 **customercare@${enforcedDomain}** — Customer care & feedback (powers: Feedback forms, reviews)
5. 📧 **partnerships@${enforcedDomain}** — Business development (powers: Partnership request form)
6. 📧 **marketing@${enforcedDomain}** — Marketing campaigns (powers: Promotions, campaigns)
7. 📧 **newsletter@${enforcedDomain}** — Subscriber updates (powers: Blog subscription, mailing list)

You can reply:
- **1** — Accept all 7 email addresses as shown above
- **Remove** specific ones (e.g., "remove newsletter and customercare")
- **Add more** (e.g., "add jerry@${enforcedDomain}")
- **Change** prefixes (e.g., "use hello@ instead of info@")
- Or specify your own custom list

Reply with your choice now.`;
        this.addMessageToUI('ai', msg);
        this.saveMessageToDatabase('ai', msg);
        this._awaitingPhase2EmailRoles = true;
    }

    applyPhase2EmailRolesFromText(rawText) {
        const enforcedDomain = (this.getEnforcedDomain() || 'yourdomain.com').toLowerCase();
        const text = String(rawText || '').trim();
        if (!text) return null;

        // Language-agnostic default selection: allow numeric choice.
        const wantsDefaults = /^1$/.test(text) || /\b(use defaults|defaults|default|whatever works|you decide|auto)\b/i.test(text);
        const result = {
            general: `info@${enforcedDomain}`,
            support: `support@${enforcedDomain}`,
            transactions: `billing@${enforcedDomain}`,
            customercare: `customercare@${enforcedDomain}`,
            bizdev: `partnerships@${enforcedDomain}`,
            marketing: `marketing@${enforcedDomain}`,
            newsletter: `newsletter@${enforcedDomain}`
        };

        const normalizeRole = (r) => {
            const k = String(r || '').toLowerCase().trim();
            if (!k) return null;
            if (k === 'info' || k === 'general' || k === 'inquiries' || k === 'contact') return 'general';
            if (k === 'support' || k === 'help' || k === 'customer' || k === 'customercare') return 'support';
            if (k === 'marketing' || k === 'promotions' || k === 'campaigns') return 'marketing';
            if (k === 'billing' || k === 'transactions' || k === 'accounts' || k === 'payments' || k === 'invoice' || k === 'invoices') return 'transactions';
            if (k === 'customercare' || k === 'customer care' || k === 'feedback' || k === 'care') return 'customercare';
            if (k === 'partnerships' || k === 'partners' || k === 'bizdev' || k === 'business' || k === 'bd' || k === 'business development') return 'bizdev';
            if (k === 'newsletter' || k === 'newsletters' || k === 'news' || k === 'updates' || k === 'subscribers') return 'newsletter';
            return null;
        };

        const coerceEmail = (v) => {
            const s = String(v || '').trim();
            if (!s) return null;
            if (s.includes('@')) return s;
            // alias-only
            const alias = s.replace(/[^a-z0-9._+-]/ig, '');
            if (!alias) return null;
            return `${alias}@${enforcedDomain}`;
        };

        if (!wantsDefaults) {
            // Parse role: value pairs and/or loose emails.
            const chunks = text.split(/[\n,;]+/).map(s => s.trim()).filter(Boolean);
            const emails = [];
            chunks.forEach(chunk => {
                const m = chunk.match(/^([a-zA-Z _-]{2,20})\s*[:=-]\s*(.+)$/);
                if (m) {
                    const role = normalizeRole(m[1]);
                    const email = coerceEmail(m[2]);
                    if (role && email) result[role] = email;
                    return;
                }
                const em = chunk.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig);
                if (em && em.length) {
                    // Try infer role from surrounding text
                    const inferred = normalizeRole(chunk.replace(em[0], '').trim());
                    if (inferred) result[inferred] = em[0];
                    else emails.push(em[0]);
                    return;
                }
                // alias-only token: map if it matches a known role keyword
                const tokenRole = normalizeRole(chunk);
                if (tokenRole) result[tokenRole] = coerceEmail(tokenRole === 'general' ? 'info' : tokenRole);
            });
            if (emails.length === 1) {
                result.general = emails[0];
            }
        }

        // Persist into state + consultation_data
        this.ensureProvisioningContainers();
        this.state.email_roles = this.state.email_roles || this.buildDefaultEmailRoleMap();
        Object.keys(result).forEach(k => {
            this.state.email_roles[k] = result[k];
        });
        this.state.email_roles_meta = this.state.email_roles_meta || this.buildDefaultEmailMeta();
        this.state.email_roles_meta.roles_confirmed = true;

        this.consultation_data.email_roles = { ...(this.consultation_data.email_roles || {}), ...result };
        this.consultation_data.email_roles_meta = { ...(this.consultation_data.email_roles_meta || {}), roles_confirmed: true };
        this.consultation_data.extracted_information = this.consultation_data.extracted_information || {};
        this.consultation_data.extracted_information.email_preferences = {
            roles: result,
            roles_confirmed: true,
            domain: enforcedDomain
        };

        if (window.vmo_consultant_context_v1) {
            this.saveConsultationData({
                email_roles: this.consultation_data.email_roles,
                email_roles_meta: this.consultation_data.email_roles_meta,
                extracted_information: this.consultation_data.extracted_information
            });
        }
        this.scheduleStateSave();

        return result;
    }

    async applyPhase1PaymentQuickReply(rawText) {
        const msg = String(rawText || '').trim();
        if (!msg) return null;

        // Language-agnostic shortcut: only accept numeric choices.
        // (The server-side multilingual intent classifier handles free-text confirmations.)
        const pick = (msg.match(/^\s*([123])\b/) || [])[1] || null;
        if (!pick) return null;
        const isProducts = pick === '1';
        const isDonations = pick === '2';
        const isNo = pick === '3';

        const ex = (this.consultation_data.extracted_information = this.consultation_data.extracted_information || {});
        ex.payment_collection = (ex.payment_collection && typeof ex.payment_collection === 'object')
            ? ex.payment_collection
            : { collects_payments: null, mode: null, confirmed: false };
        ex.ecommerce = (ex.ecommerce && typeof ex.ecommerce === 'object')
            ? ex.ecommerce
            : { collection_mode: null, donation_funds: [] };

        if (isNo) {
            ex.payment_collection.collects_payments = false;
            ex.payment_collection.mode = 'none';
            ex.payment_collection.confirmed = true;
            ex.ecommerce.collection_mode = null;
            ex.needsEcommerce = false;
        } else if (isProducts) {
            ex.payment_collection.collects_payments = true;
            ex.payment_collection.mode = 'products';
            ex.payment_collection.confirmed = true;
            ex.ecommerce.collection_mode = 'products';
            ex.needsEcommerce = true;
        } else {
            ex.payment_collection.collects_payments = true;
            ex.payment_collection.mode = 'donations';
            ex.payment_collection.confirmed = true;
            ex.ecommerce.collection_mode = 'donations';
            if (!Array.isArray(ex.ecommerce.donation_funds)) ex.ecommerce.donation_funds = [];
            ex.needsEcommerce = true;
        }

        // Keep legacy reference in sync
        this.collectedInformation = ex;

        // Persist immediately so the consultation can advance naturally.
        if (window.vmo_consultant_context_v1) {
            await this.saveConsultationData({
                extracted_information: this._toCanonicalExtracted(),
                documents: this.consultation_data.documents || [],
                media_files: this._toCanonicalMediaFiles(),
                consultation_flow: this.consultation_data.consultation_flow,
                blueprintRaw: this.blueprintRaw,
                blueprintSummary: this.blueprintSummary
            });
        }

        if (ex.payment_collection.mode === 'products') {
            return 'PAYMENT COLLECTION CONFIRMED: collects_payments=true; mode=products (shop + checkout).';
        }
        if (ex.payment_collection.mode === 'donations') {
            return 'PAYMENT COLLECTION CONFIRMED: collects_payments=true; mode=donations (fund + amount).';
        }
        return 'PAYMENT COLLECTION CONFIRMED: collects_payments=false; mode=none.';
    }

    async drainPendingVoiceMessage() {
        if (this._drainingVoiceQueue) return;
        if (!this.isVoiceEnabled) return;
        const next = (this._pendingVoiceMessage || '').trim();
        if (!next) return;
        if (this.isProcessing || this.isSpeaking) return;

        this._drainingVoiceQueue = true;
        this._pendingVoiceMessage = '';
        try {
            // Small delay to let UI settle and avoid deep recursion
            await this.delay(120);
            if (this.isVoiceEnabled) {
                await this.handleUserMessage(next, true);
            }
        } finally {
            this._drainingVoiceQueue = false;
        }
    }

    delay(ms = 0) {
        return new Promise(resolve => setTimeout(resolve, Math.max(0, ms)));
    }

    buildInitialState() {
        return {
            session_id: this.sessionId,
            consultation_flow: {
                phases: [],
                current_phase_index: 0,
                status: 'in_progress'
            },
            business_profile: {},
            research_profile: {},
            recommendations_profile: {},
            website_plan: {},
            systems_plan: {},
            roadmap_json: {},
            daily_tasks_json: {},
            phone_provisioning_status: null,
            email_roles: this.buildDefaultEmailRoleMap(),
            email_roles_meta: this.buildDefaultEmailMeta(),
            document_store: {
                blueprint_raw: '',
                blueprint_summary: '',
                uploads: []
            },
            approvals: {
                phase1_profile_confirmed: false,
                recommendations: { accepted: [], rejected: [] },
                artifacts_generated: false
            }
        };
    }

    ensureDocumentStoreContainer() {
        if (!this.state) {
            this.state = this.buildInitialState();
        }
        if (!this.state.document_store) {
            this.state.document_store = this.buildInitialState().document_store;
        }
        if (!Array.isArray(this.state.document_store.uploads)) {
            this.state.document_store.uploads = [];
        }
        return this.state.document_store;
    }

    buildDefaultEmailRoleMap() {
        const defaults = {};
        const roles = Array.isArray(this.roleMailboxes) && this.roleMailboxes.length
            ? this.roleMailboxes
            : Array.isArray(VMO_PROVISIONING_ROLES) ? VMO_PROVISIONING_ROLES : [];
        roles.forEach(role => {
            defaults[role] = null;
        });
        return defaults;
    }

    buildDefaultEmailMeta() {
        return {
            locked_domain: this.getProvisioningHostFallback(),
            warnings: [],
            roles_confirmed: false,
            roles_confirmed_at: null,
            // Updated: User-provided IMAP/SMTP instead of legacy auto-provisioning
            provisioning_mode: 'user_provided',
            provisioning_status: 'not_configured',
            // User's email accounts with IMAP/SMTP credentials
            email_accounts: {},
            // Bulk email provider configuration (Brevo, MailerLite)
            bulk_email_provider: null,
            bulk_email_api_key: null,
            bulk_email_verified: false,
            phase6_skip_approved: false,
            // Legacy fields for backward compatibility
            alias_preferences: {},
            dns_host_type: null,
            hosted_zone_id: null,
            manual_dns_confirmed_at: null,
            manual_dns_notes: '',
            existing_mailbox_validations: {},
            dns_nameservers: [],
            fallback_validation_status: null,
            last_dns_detection_at: null,
            automation_readiness: 'deprecated',
            automation_reason: 'Legacy provisioning removed',
            provisioning_consent: false
        };
    }
    
    // Add email account with IMAP/SMTP credentials
    addEmailAccount(category, accountConfig) {
        this.ensureProvisioningContainers();
        const enforcedDomain = this.getEnforcedDomain();
        
        // Validate email matches enforced domain
        const emailValidation = this.validateEmailDomain(accountConfig.email);
        if (!emailValidation.valid) {
            return { success: false, error: emailValidation.error };
        }
        
        if (!this.state.email_roles_meta.email_accounts) {
            this.state.email_roles_meta.email_accounts = {};
        }
        
        // Store encrypted credentials (actual encryption happens server-side)
        this.state.email_roles_meta.email_accounts[category] = {
            email: accountConfig.email,
            display_name: accountConfig.display_name || '',
            provider_preset: accountConfig.provider_preset || 'custom',
            imap: {
                host: accountConfig.imap_host,
                port: accountConfig.imap_port || 993,
                encryption: accountConfig.imap_encryption || 'ssl',
                username: accountConfig.email
            },
            smtp: {
                host: accountConfig.smtp_host,
                port: accountConfig.smtp_port || 587,
                encryption: accountConfig.smtp_encryption || 'tls',
                username: accountConfig.email
            },
            // Password stored securely server-side, not in JS state
            password_stored: !!accountConfig.password,
            verified: false,
            added_at: new Date().toISOString()
        };
        
        // Update email_roles mapping
        this.state.email_roles[category] = accountConfig.email;
        this.state.email_roles_meta.roles_confirmed = true;
        if (!this.state.email_roles_meta.roles_confirmed_at) {
            this.state.email_roles_meta.roles_confirmed_at = new Date().toISOString();
        }

        // A configured account supersedes any prior skip approval.
        if (this.hasPhase6SkipApproval()) {
            this.setPhase6SkipApproval(false);
        }
        
        this.scheduleStateSave();
        return { success: true };
    }
    
    // Set bulk email provider configuration
    setBulkEmailProvider(providerKey, apiKey) {
        if (!VMO_BULK_EMAIL_PROVIDERS[providerKey]) {
            return { success: false, error: 'Unknown bulk email provider' };
        }
        
        this.ensureProvisioningContainers();
        this.state.email_roles_meta.bulk_email_provider = providerKey;
        // Never persist API keys in JS state; backend stores encrypted.
        this.state.email_roles_meta.bulk_email_api_key = null;
        this.state.email_roles_meta.bulk_email_verified = false;
        
        this.scheduleStateSave();
        return { success: true, provider: VMO_BULK_EMAIL_PROVIDERS[providerKey] };
    }

    // Save + verify bulk email provider via backend (novice-friendly)
    async saveAndVerifyBulkEmailProvider(providerKey, apiKey) {
        if (!VMO_BULK_EMAIL_PROVIDERS[providerKey]) {
            return { success: false, error: 'Unknown bulk email provider' };
        }
        const ajaxUrl = (typeof vmoAjax !== 'undefined' ? vmoAjax.ajaxurl : vmo_ajax?.ajaxurl) || '/wp-admin/admin-ajax.php';
        const nonce = (typeof vmoAjax !== 'undefined' ? vmoAjax.nonce : vmo_ajax?.nonce) || '';

        try {
            const saveResp = await fetch(ajaxUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_save_bulk_email_provider',
                    nonce,
                    provider: providerKey,
                    api_key: apiKey
                })
            });
            const saveData = await saveResp.json();
            if (!saveData.success) {
                return { success: false, error: saveData.data?.message || 'Failed to save provider' };
            }

            const verifyResp = await fetch(ajaxUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_verify_bulk_email_provider',
                    nonce
                })
            });
            const verifyData = await verifyResp.json();

            if (!verifyData.success) {
                return { success: false, error: verifyData.data?.message || 'API key verification failed' };
            }

            // Store only provider + verified state locally (no secrets)
            this.setBulkEmailProvider(providerKey, null);
            this.state.email_roles_meta.bulk_email_verified = true;
            this.scheduleStateSave();

            return { success: true, provider: VMO_BULK_EMAIL_PROVIDERS[providerKey], verified: true };
        } catch (err) {
            console.error('Bulk email provider save/verify error:', err);
            return { success: false, error: 'Connection error' };
        }
    }
    
    // Get email provider preset settings
    getEmailProviderPreset(presetKey) {
        return VMO_EMAIL_PROVIDER_PRESETS[presetKey] || VMO_EMAIL_PROVIDER_PRESETS.custom;
    }

    forceModalOnTop(modalElement, zIndex = 3000000) {
        if (!modalElement || !modalElement.style) {
            return;
        }
        modalElement.style.setProperty('position', 'fixed', 'important');
        modalElement.style.setProperty('top', '0', 'important');
        modalElement.style.setProperty('right', '0', 'important');
        modalElement.style.setProperty('bottom', '0', 'important');
        modalElement.style.setProperty('left', '0', 'important');
        modalElement.style.setProperty('display', 'flex', 'important');
        modalElement.style.setProperty('align-items', 'center', 'important');
        modalElement.style.setProperty('justify-content', 'center', 'important');
        modalElement.style.setProperty('z-index', String(zIndex), 'important');
        modalElement.style.setProperty('pointer-events', 'auto', 'important');
    }
    
    // Show secure email setup modal for credential entry
    showEmailSetupModal(category, onSuccess = null) {
        const enforcedDomain = this.getEnforcedDomain();
        const categoryLabel = VMO_EMAIL_CATEGORY_LABELS[category] || category;
        const currentPhaseIndex = Number(
            this.consultation_data?.consultation_flow?.current_index ??
            this.state?.consultation_flow?.current_index ??
            -1
        );
        const openedFromPhase6 = currentPhaseIndex === 5;
        let setupCompleted = false;

        const handleEmailSetupSuccess = (result = null) => {
            setupCompleted = true;
            if (typeof onSuccess === 'function') {
                onSuccess(result);
            }
        };

        const handleEmailSetupDismiss = () => {
            if (!openedFromPhase6 || setupCompleted) {
                return;
            }

            if (this.getConfiguredEmailAccountCount() > 0 || this.hasPhase6SkipApproval()) {
                return;
            }

            this.setPhase6SkipApproval(true);
            this.addMessageToUI('system', 'Email setup was closed. I marked it as skip for now so the Continue to Website Generation button works again. You can reopen email setup later.');
        };
        
        // Remove existing modal if any
        const existingModal = document.getElementById('vmo-email-setup-modal');
        if (existingModal) existingModal.remove();
        
        // Create loading modal first while we detect hosting
        const modal = document.createElement('div');
        modal.id = 'vmo-email-setup-modal';
        modal.className = 'vmo-modal-overlay';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0, 0, 0, 0.85); z-index: 10000000;
            display: flex; align-items: center; justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        `;
        this.forceModalOnTop(modal, 3000006);
        
        // Show loading state
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.3);
                border-radius: 16px; max-width: 520px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 40px; text-align: center;">
                    <div style="font-size: 48px; margin-bottom: 16px;">🔍</div>
                    <h3 style="color: #f1f5f9; font-size: 18px; margin: 0 0 8px 0;">Detecting Hosting Environment</h3>
                    <p style="color: #94a3b8; font-size: 14px; margin: 0;">Checking for auto-provisioning capabilities...</p>
                    <div style="margin-top: 20px;">
                        <div style="width: 40px; height: 40px; border: 3px solid #334155; border-top-color: #3b82f6; border-radius: 50%; margin: 0 auto; animation: vmo-spin 1s linear infinite;"></div>
                    </div>
                </div>
            </div>
            <style>
                @keyframes vmo-spin { to { transform: rotate(360deg); } }
            </style>
        `;
        
        document.body.appendChild(modal);
        this.forceModalOnTop(modal, 3000006);
        
        // Detect hosting environment
        this.detectHostingEnvironment().then(hosting => {
            console.log('VMO Email: Hosting detection result:', hosting);
            this.renderEmailSetupModal(modal, hosting, category, categoryLabel, enforcedDomain, handleEmailSetupSuccess, handleEmailSetupDismiss);
        }).catch(err => {
            console.error('VMO Email: Hosting detection failed:', err);
            // Fall back to manual setup
            this.renderEmailSetupModal(modal, { supports_auto_provision: false }, category, categoryLabel, enforcedDomain, handleEmailSetupSuccess, handleEmailSetupDismiss);
        });
    }
    
    // Detect hosting environment for email auto-provisioning
    async detectHostingEnvironment() {
        const AX = this.getAjax();
        
        try {
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_detect_hosting_panel',
                    security: AX.nonce
                })
            });
            
            const data = await response.json();
            return data.success ? data.data : { supports_auto_provision: false };
        } catch (err) {
            console.error('Hosting detection error:', err);
            return { supports_auto_provision: false };
        }
    }
    
    // Render the appropriate email setup modal based on hosting detection
    renderEmailSetupModal(modal, hosting, category, categoryLabel, enforcedDomain, onSuccess, onDismiss = null) {
        this.forceModalOnTop(modal, 3000006);
        let modalClosed = false;
        const closeModal = () => {
            if (modalClosed) {
                return;
            }
            modalClosed = true;
            try {
                if (modal && modal.parentNode) {
                    modal.parentNode.removeChild(modal);
                }
            } catch (_) {}
            if (typeof onDismiss === 'function') {
                onDismiss();
            }
        };

        const routeReady = this.isVultrCloudflareMxrouteRouteReady(hosting);

        // QuickLaunch users (Vultr + Cloudflare + MXroute) — full auto-provision
        if (routeReady && hosting.supports_auto_provision) {
            this.renderAutoProvisionEmailModal(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
            return;
        }

        // Self-hosted users with cPanel/Plesk/DirectAdmin — can still auto-provision via panel API
        // even without the Vultr/Cloudflare/MXroute route being active
        if (!routeReady && hosting?.supports_auto_provision && hosting?.panel_type) {
            console.log('VMO Email: Panel-based auto-provision available:', hosting.panel_type);
            this.renderAutoProvisionEmailModal(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
            return;
        }

        if (hosting?.route_reason) {
            console.log('VMO Email: Falling back to Zoho route:', hosting.route_reason);
        }

        this.renderManagedHostEmailModal(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
    }

    isVultrCloudflareMxrouteRouteReady(hosting) {
        if (!hosting || typeof hosting !== 'object') {
            return false;
        }
        if (hosting.route_mode === 'vultr_cloudflare_mxroute' && hosting.route_ready === true) {
            return true;
        }
        return hosting.route_ready === true;
    }

    // Novice-friendly choice: Zoho (recommended) vs connect existing inbox
    renderEasyEmailSetupChoiceModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess) {
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.3);
                border-radius: 16px; max-width: 560px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px;">📧 Set Up ${categoryLabel}</h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">@${enforcedDomain}</p>
                        </div>
                        <button id="vmo-email-modal-close" style="background: transparent; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;">&times;</button>
                    </div>

                    <div style="background: rgba(34, 197, 94, 0.12); border: 1px solid rgba(34, 197, 94, 0.25); border-radius: 10px; padding: 14px; margin-bottom: 14px;">
                        <p style="margin: 0; color: #86efac; font-size: 13px; line-height: 1.5;">
                            <strong>Recommended (easiest):</strong> Create a free business inbox with Zoho Mail.
                            VMO will guide you step-by-step.
                        </p>
                    </div>

                    <div style="display: grid; gap: 10px;">
                        <button id="vmo-easy-zoho" style="
                            width: 100%; padding: 14px; border-radius: 10px;
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white; font-weight: 700; font-size: 14px; cursor: pointer;
                        ">✨ Create Free Business Email (Zoho) →</button>

                        <button id="vmo-easy-existing" style="
                            width: 100%; padding: 12px; border-radius: 10px;
                            background: transparent; border: 1px solid #475569; color: #cbd5e1;
                            font-size: 14px; cursor: pointer;
                        ">I already have email — connect it</button>
                    </div>

                    <div style="text-align: center; margin-top: 14px;">
                        <button id="vmo-easy-ask-jarvis" style="background: transparent; border: none; color: #8b5cf6; font-size: 13px; cursor: pointer;">
                            🤖 Not sure what to pick? Ask Jarvis
                        </button>
                    </div>
                </div>
            </div>
        `;

        document.getElementById('vmo-email-modal-close').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });

        document.getElementById('vmo-easy-zoho').addEventListener('click', () => {
            // Reuse managed-host Zoho wizard even if host is unknown.
            this.renderManagedHostEmailModal(modal, { is_managed_host: true, managed_host_name: null }, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
        });
        document.getElementById('vmo-easy-existing').addEventListener('click', () => {
            this.renderManualEmailSetupModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
        });
        document.getElementById('vmo-easy-ask-jarvis').addEventListener('click', () => {
            closeModal();
            this.askJarvisAboutEmail(category, enforcedDomain, null);
        });
    }
    
    // Render modal for managed hosts (WP Engine, Kinsta, etc.)
    renderManagedHostEmailModal(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess) {
        // First check if Zoho is already configured
        this.checkZohoSetupStatus().then(status => {
            if (status.setup_complete) {
                // Zoho is ready - go straight to email creation
                this.renderZohoCreateEmailModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess, status);
            } else {
                // Show the Zoho setup wizard
                this.renderZohoSetupWizard(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess, status);
            }
        }).catch(() => {
            // If check fails, show the wizard from step 1
            this.renderZohoSetupWizard(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess, {});
        });
    }

    // Check Zoho setup status
    async checkZohoSetupStatus() {
        try {
            const response = await fetch(vmoGetAdminAjaxUrl(), {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_zoho_get_setup_status',
                    security: vmo_ajax.nonce
                })
            });
            const data = await response.json();
            return data.success ? data.data : {};
        } catch (e) {
            console.error('Zoho status check failed:', e);
            return {};
        }
    }

    // Main Zoho Setup Wizard
    renderZohoSetupWizard(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess, status) {
        // Determine current step based on status
        let currentStep = 1;
        if (status.has_credentials && status.has_refresh_token) {
            currentStep = status.has_verified_domain ? 4 : 3;
        } else if (status.has_credentials) {
            currentStep = 2;
        }

        // ✅ NEW: "Skip entire email setup" callback — closes modal, marks skip, advances consultation
        const skipAllAndContinue = async () => {
            closeModal();
            this.setPhase6SkipApproval(true);
            this.addMessageToUI('ai', '✅ Email setup skipped for now. You can configure accounts later from your dashboard. Continuing with website generation...');
            await this.advancePhase();
        };

        const renderStep = (step) => {
            switch(step) {
                case 1: this.renderZohoStep1_CreateAccount(modal, enforcedDomain, closeModal, () => renderStep(2), skipAllAndContinue); break;
                case 2: this.renderZohoStep2_GetAPICredentials(modal, enforcedDomain, closeModal, () => renderStep(3), skipAllAndContinue); break;
                case 3: this.renderZohoStep3_SetupDNS(modal, enforcedDomain, closeModal, () => renderStep(4), skipAllAndContinue, category, categoryLabel, onSuccess); break;
                case 4: this.renderZohoCreateEmailModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess, status); break;
            }
        };

        renderStep(currentStep);
    }

    // Step 1: Create Zoho Account
    renderZohoStep1_CreateAccount(modal, enforcedDomain, closeModal, onNext, skipAllAndContinue) {
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(234, 88, 12, 0.4);
                border-radius: 16px; max-width: 600px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header with Progress -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px; display: flex; align-items: center; gap: 8px;">
                                <span style="font-size: 22px;">📧</span> Set Up Free Business Email
                            </h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">Powered by Zoho Mail • @${enforcedDomain}</p>
                        </div>
                        <button id="vmo-zoho-close" style="background: transparent; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;">&times;</button>
                    </div>

                    <!-- Progress Bar -->
                    <div style="display: flex; gap: 8px; margin-bottom: 24px;">
                        <div style="flex: 1; height: 4px; background: #ea580c; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                    </div>

                    <!-- Step Content -->
                    <div style="background: rgba(234, 88, 12, 0.1); border: 1px solid rgba(234, 88, 12, 0.3); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <h4 style="margin: 0 0 12px 0; color: #fb923c; font-size: 16px;">Step 1: Create Your Zoho Mail Account</h4>
                        <p style="margin: 0 0 16px 0; color: #d1d5db; font-size: 14px; line-height: 1.6;">
                            Zoho Mail provides <strong style="color: #22c55e;">FREE</strong> professional email for up to 5 users.
                            Click below to create your organization (opens in new window).
                        </p>
                        
                        <div style="background: #0f172a; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
                            <p style="margin: 0 0 8px 0; color: #94a3b8; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px;">Free Forever Plan includes:</p>
                            <ul style="margin: 0; padding-left: 20px; color: #e2e8f0; font-size: 13px; line-height: 1.8;">
                                <li>✅ 5 users with @${enforcedDomain} email</li>
                                <li>✅ 5 GB mail storage per user</li>
                                <li>✅ Web & mobile apps</li>
                                <li>✅ Professional custom-domain addresses</li>
                            </ul>
                            <p style="margin: 8px 0 0 0; color: #94a3b8; font-size: 11px; line-height: 1.5;">
                                <strong>Note:</strong> The free plan does NOT include IMAP / POP / ActiveSync access.
                                Need desktop-client access? Choose <a href="https://www.zoho.com/mail/zohomail-pricing.html" target="_blank" style="color: #60a5fa;">Mail Lite ($1/user/mo)</a> instead.
                                Free plan availability varies by region.
                            </p>
                        </div>

                        <a href="https://workplace.zoho.com/signup?type=org&plan=free" target="_blank" id="vmo-zoho-signup-link" style="
                            display: flex; align-items: center; justify-content: center; gap: 10px;
                            width: 100%; padding: 14px; border-radius: 8px;
                            background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
                            color: white; text-decoration: none; font-weight: 600; font-size: 15px;
                        ">
                            <span>🚀</span> Create Free Zoho Account
                            <span style="font-size: 12px;">↗</span>
                        </a>
                        <p style="margin: 6px 0 0 0; text-align: center;">
                            <a href="https://www.zoho.com/mail/zohomail-pricing.html" target="_blank" style="color: #60a5fa; font-size: 12px; text-decoration: none;">
                                Compare all plans ↗
                            </a>
                        </p>
                    </div>

                    <!-- Instructions -->
                    <div style="background: #0f172a; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
                        <p style="margin: 0 0 10px 0; color: #94a3b8; font-size: 12px; text-transform: uppercase;">After creating your account:</p>
                        <ol style="margin: 0; padding-left: 20px; color: #cbd5e1; font-size: 13px; line-height: 2;">
                            <li>Sign up with your email at Zoho Workplace (Free plan)</li>
                            <li>During setup, add your domain: <strong style="color: #fbbf24;">${enforcedDomain}</strong></li>
                            <li>Choose the data center closest to you (US, EU, IN, etc.)</li>
                            <li>Come back here and click "Continue"</li>
                        </ol>
                    </div>

                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 10px;">
                        <button id="vmo-zoho-cancel" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: transparent; border: 1px solid #475569; color: #94a3b8;
                            font-size: 14px; cursor: pointer;
                        ">Cancel</button>
                        <button id="vmo-zoho-next" style="
                            flex: 2; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white; font-weight: 600; font-size: 14px; cursor: pointer;
                        ">I've Created My Zoho Account → Continue</button>
                    </div>
                </div>
            </div>
        `;

        document.getElementById('vmo-zoho-close').addEventListener('click', closeModal);
        document.getElementById('vmo-zoho-cancel').addEventListener('click', closeModal);
        document.getElementById('vmo-zoho-next').addEventListener('click', onNext);

        // ✅ "Skip entire email setup" link — closes modal and continues consultation
        if (typeof skipAllAndContinue === 'function') {
            const skipBtn = document.createElement('button');
            skipBtn.textContent = 'Skip email setup entirely — continue consultation →';
            skipBtn.style.cssText = 'display: block; width: 100%; margin-top: 14px; background: transparent; border: none; color: #64748b; font-size: 12px; cursor: pointer; text-decoration: underline; text-align: center; padding: 6px 0;';
            skipBtn.addEventListener('click', skipAllAndContinue);
            modal.querySelector('.vmo-email-modal-content > div')?.appendChild(skipBtn);
        }
    }

    // Step 2: Get API Credentials
    renderZohoStep2_GetAPICredentials(modal, enforcedDomain, closeModal, onNext, skipAllAndContinue) {
        const homepageUrl = window.location.origin;
        // ✅ FIX: Start with empty URL — NEVER show a URL until PHP provides the canonical one.
        // This eliminates the redirect_uri race condition where the user copies the JS fallback
        // which may differ from admin_url() due to subdirectories, www, or HTTPS mismatches.
        let redirectUrl = '';
        let redirectUrlLoaded = false;
        
        // ✅ Show loading state while the canonical redirect URI is fetched from PHP
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.4);
                border-radius: 16px; max-width: 650px; width: 95%;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 40px; text-align: center;">
                    <div style="font-size: 36px; margin-bottom: 12px;">🔐</div>
                    <h3 style="color: #f1f5f9; font-size: 16px; margin: 0 0 8px 0;">Preparing Zoho Connection...</h3>
                    <p style="color: #94a3b8; font-size: 13px; margin: 0;">Loading your exact redirect URI from WordPress</p>
                    <div style="margin-top: 16px;">
                        <div style="width: 32px; height: 32px; border: 3px solid #334155; border-top-color: #3b82f6; border-radius: 50%; margin: 0 auto; animation: vmo-spin 1s linear infinite;"></div>
                    </div>
                </div>
            </div>
        `;
        
        const renderContent = (displayRedirectUrl) => {
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.4);
                border-radius: 16px; max-width: 650px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px;">📧 Connect Zoho to VMO</h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">@${enforcedDomain}</p>
                        </div>
                        <button id="vmo-zoho-close" style="background: transparent; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;">&times;</button>
                    </div>

                    <!-- Progress Bar -->
                    <div style="display: flex; gap: 8px; margin-bottom: 24px;">
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #3b82f6; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                    </div>

                    <!-- Instructions -->
                    <div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <h4 style="margin: 0 0 12px 0; color: #60a5fa; font-size: 16px;">Step 2: Get Your Zoho API Credentials</h4>
                        <p style="margin: 0 0 16px 0; color: #d1d5db; font-size: 14px; line-height: 1.6;">
                            This allows VMO to create email accounts automatically. Follow these steps:
                        </p>
                        
                        <ol style="margin: 0 0 16px 0; padding-left: 20px; color: #e2e8f0; font-size: 13px; line-height: 2.2;">
                            <li>Go to <a href="https://api-console.zoho.com/" target="_blank" style="color: #60a5fa;">Zoho API Console</a> (or <a href="https://accounts.zoho.com/developerconsole" target="_blank" style="color: #60a5fa;">Developer Console</a>)</li>
                            <li>Click <strong>"GET STARTED"</strong> (first time) or <strong>"ADD CLIENT"</strong> (if you already have apps)</li>
                            <li>Hover over <strong>"Server-based Applications"</strong> and click <strong>"CREATE NOW"</strong></li>
                            <li>Enter any Client Name (e.g., "VMO Website Builder")</li>
                            <li>For Homepage URL, enter: <code style="background: #0f172a; padding: 2px 6px; border-radius: 4px; color: #fbbf24;">${homepageUrl}</code></li>
                            <li>For Authorized Redirect URI, enter: <code style="background: #0f172a; padding: 2px 6px; border-radius: 4px; color: #fbbf24;">${displayRedirectUrl}</code></li>
                            <li>Click <strong>"CREATE"</strong>, then copy the Client ID and Client Secret below</li>
                        </ol>

                        <div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px;">
                            <button id="vmo-zoho-copy-home" style="padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #e2e8f0; border: 1px solid #334155; font-size: 12px; cursor: pointer;">Copy Homepage URL</button>
                            <button id="vmo-zoho-copy-redirect" style="padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #e2e8f0; border: 1px solid #334155; font-size: 12px; cursor: pointer;">Copy Redirect URI</button>
                            <button id="vmo-zoho-help" style="padding: 8px 12px; border-radius: 6px; background: transparent; color: #8b5cf6; border: 1px solid rgba(139, 92, 246, 0.4); font-size: 12px; cursor: pointer;">🤖 Help me do this</button>
                        </div>

                        <a href="https://api-console.zoho.com/" target="_blank" style="
                            display: inline-flex; align-items: center; gap: 6px;
                            padding: 10px 16px; border-radius: 6px; margin-top: 10px;
                            background: #1e40af; color: white; text-decoration: none; font-size: 13px;
                        ">Open Zoho API Console ↗</a>
                    </div>

                    <!-- Built-in Help Guide (hidden by default) -->
                    <div id="vmo-zoho-help-guide" style="display: none; background: rgba(139, 92, 246, 0.08); border: 1px solid rgba(139, 92, 246, 0.3); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
                            <h4 style="margin: 0; color: #a78bfa; font-size: 15px;">🤖 Step-by-Step Zoho Setup Guide</h4>
                            <button id="vmo-zoho-help-close" style="background: transparent; border: none; color: #64748b; font-size: 18px; cursor: pointer;">&times;</button>
                        </div>
                        <div style="color: #d1d5db; font-size: 13px; line-height: 1.8;">
                            <p style="margin: 0 0 12px 0; color: #c4b5fd; font-weight: 600;">Part A: Create a Zoho API Client</p>
                            <ol style="margin: 0 0 16px 0; padding-left: 20px;">
                                <li>Open <a href="https://api-console.zoho.com/" target="_blank" style="color: #60a5fa;">api-console.zoho.com</a> and sign in with your Zoho account</li>
                                <li>Click <strong>"GET STARTED"</strong> (first time) or the blue <strong>"ADD CLIENT"</strong> button (top-right if you already have apps)</li>
                                <li>Hover over <strong>"Server-based Applications"</strong> and click <strong>"CREATE NOW"</strong></li>
                                <li>Fill in the form:
                                    <ul style="list-style: disc; padding-left: 16px; margin: 4px 0;">
                                        <li><strong>Client Name:</strong> VMO Website Builder (or any name you like)</li>
                                        <li><strong>Homepage URL:</strong> <code style="background: #0f172a; padding: 1px 4px; border-radius: 3px; color: #fbbf24;">${homepageUrl}</code></li>
                                        <li><strong>Authorized Redirect URI:</strong> <code style="background: #0f172a; padding: 1px 4px; border-radius: 3px; color: #fbbf24;">${displayRedirectUrl}</code></li>
                                    </ul>
                                </li>
                                <li>Click <strong>"CREATE"</strong></li>
                            </ol>
                            <p style="margin: 0 0 12px 0; color: #c4b5fd; font-weight: 600;">Part B: Copy Your Credentials</p>
                            <ol start="6" style="margin: 0 0 16px 0; padding-left: 20px;">
                                <li>After creation, go to the <strong>Client Secret</strong> tab</li>
                                <li>Copy the <strong>Client ID</strong> and <strong>Client Secret</strong> and paste them into the fields below</li>
                            </ol>
                            <p style="margin: 0 0 12px 0; color: #c4b5fd; font-weight: 600;">Part C: Generate the Refresh Token</p>
                            <ol start="8" style="margin: 0 0 8px 0; padding-left: 20px;">
                                <li>After pasting your Client ID and Secret, click the purple <strong>"Generate Refresh Token Automatically"</strong> button below</li>
                                <li>A Zoho popup will open — sign in if prompted, then click <strong>"Accept"</strong></li>
                                <li>The popup will close automatically and you'll see a green ✅ confirmation</li>
                                <li>Finally, click <strong>"Verify & Save Credentials"</strong> to continue</li>
                            </ol>
                            <div style="margin-top: 12px; padding: 10px; background: rgba(59, 130, 246, 0.1); border-radius: 8px; border: 1px solid rgba(59, 130, 246, 0.2);">
                                <strong style="color: #60a5fa;">💡 Tips:</strong>
                                <ul style="margin: 6px 0 0 0; padding-left: 16px; list-style: disc; color: #d1d5db; font-size: 12px; line-height: 1.7;">
                                    <li>The <strong>Redirect URI</strong> in Zoho's console must match <em>exactly</em> — including https:// and no trailing slash. Use the "Copy Redirect URI" button above.</li>
                                    <li>Make sure you select the <strong>same data center region</strong> in the dropdown below as where your Zoho organization is located.</li>
                                    <li>Alternative console URL: <a href="https://accounts.zoho.com/developerconsole" target="_blank" style="color: #60a5fa;">accounts.zoho.com/developerconsole</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>

                    <!-- Credential Form -->
                    <div style="background: #0f172a; border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Data Center Region</label>
                            <select id="vmo-zoho-datacenter" style="
                                width: 100%; padding: 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 14px;
                            ">
                                <option value="us">🇺🇸 United States (zoho.com)</option>
                                <option value="eu">🇪🇺 Europe (zoho.eu)</option>
                                <option value="in">🇮🇳 India (zoho.in)</option>
                                <option value="au">🇦🇺 Australia (zoho.com.au)</option>
                                <option value="jp">🇯🇵 Japan (zoho.jp)</option>
                                <option value="ca">🇨🇦 Canada (zohocloud.ca)</option>
                                <option value="cn">🇨🇳 China (zoho.com.cn)</option>
                                <option value="ae">🇦🇪 UAE (zoho.ae)</option>
                                <option value="sa">🇸🇦 Saudi Arabia (zoho.sa)</option>
                            </select>
                        </div>
                        
                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Client ID</label>
                            <input type="text" id="vmo-zoho-client-id" placeholder="1000.XXXXXXXXXX..." style="
                                width: 100%; padding: 12px; border-radius: 8px; box-sizing: border-box;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 14px;
                            ">
                        </div>
                        
                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Client Secret</label>
                            <input type="password" id="vmo-zoho-client-secret" placeholder="••••••••••••••" style="
                                width: 100%; padding: 12px; border-radius: 8px; box-sizing: border-box;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 14px;
                            ">
                        </div>

                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Refresh Token</label>
                            <div id="vmo-zoho-token-status" style="
                                padding: 14px; border-radius: 8px; margin-bottom: 10px;
                                background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.3);
                                color: #fbbf24; font-size: 13px;
                            ">
                                ⏳ Not yet generated — click the button below after entering your Client ID and Client Secret
                            </div>
                            <button id="vmo-zoho-generate-token" type="button" style="
                                width: 100%; padding: 14px; border-radius: 8px;
                                background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
                                border: none; color: white; font-weight: 600; font-size: 14px; cursor: pointer;
                                display: flex; align-items: center; justify-content: center; gap: 8px;
                            ">🔑 Generate Refresh Token Automatically</button>
                            <p style="margin: 8px 0 0 0; color: #64748b; font-size: 11px; line-height: 1.5;">
                                This will open a Zoho authorization page in a new tab. Log in, click "Accept", and the refresh token will be saved automatically. You'll be redirected back here.
                            </p>
                            <details style="margin-top: 10px;">
                                <summary style="color: #64748b; font-size: 11px; cursor: pointer;">Advanced: Paste token manually</summary>
                                <div style="margin-top: 8px;">
                                    <input type="password" id="vmo-zoho-refresh-token" placeholder="1000.XXXXXXXXXX..." style="
                                        width: 100%; padding: 10px; border-radius: 8px; box-sizing: border-box;
                                        background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 13px;
                                    ">
                                    <p style="margin: 4px 0 0 0; color: #475569; font-size: 10px;">Only use this if the automatic method didn't work.</p>
                                </div>
                            </details>
                        </div>

                        <div id="vmo-zoho-cred-error" style="display: none; color: #f87171; font-size: 13px; margin-bottom: 12px;"></div>
                    </div>

                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 10px;">
                        <button id="vmo-zoho-back" style="
                            padding: 12px 20px; border-radius: 8px;
                            background: transparent; border: 1px solid #475569; color: #94a3b8;
                            font-size: 14px; cursor: pointer;
                        ">← Back</button>
                        <button id="vmo-zoho-save-creds" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                            border: none; color: white; font-weight: 600; font-size: 14px; cursor: pointer;
                        ">Verify & Save Credentials</button>
                    </div>
                </div>
            </div>
        `;

        const cleanupZohoWatcher = () => {
            if (typeof this._zohoOAuthCleanup === 'function') {
                try {
                    this._zohoOAuthCleanup();
                } catch (_) {}
            }
            this._zohoOAuthCleanup = null;
        };

        document.getElementById('vmo-zoho-close').addEventListener('click', () => {
            cleanupZohoWatcher();
            closeModal();
        });
        document.getElementById('vmo-zoho-back').addEventListener('click', () => {
            cleanupZohoWatcher();
            this.renderZohoStep1_CreateAccount(modal, enforcedDomain, closeModal, () => this.renderZohoStep2_GetAPICredentials(modal, enforcedDomain, closeModal, onNext, skipAllAndContinue), skipAllAndContinue);
        });
        
        const copyToClipboard = async (text, btn) => {
            try {
                if (navigator.clipboard?.writeText) {
                    await navigator.clipboard.writeText(text);
                } else {
                    const ta = document.createElement('textarea');
                    ta.value = text;
                    document.body.appendChild(ta);
                    ta.select();
                    document.execCommand('copy');
                    ta.remove();
                }
                if (btn) {
                    const old = btn.textContent;
                    btn.textContent = 'Copied ✓';
                    setTimeout(() => (btn.textContent = old), 1200);
                }
            } catch (e) {
                console.warn('Copy failed', e);
            }
        };

        const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;'
        }[char] || char));

        document.getElementById('vmo-zoho-copy-home')?.addEventListener('click', () => copyToClipboard(homepageUrl, document.getElementById('vmo-zoho-copy-home')));
        document.getElementById('vmo-zoho-copy-redirect')?.addEventListener('click', () => copyToClipboard(redirectUrl, document.getElementById('vmo-zoho-copy-redirect')));
        
        // ✅ "Help me do this" — shows a built-in step-by-step guide inline (no AI dependency)
        document.getElementById('vmo-zoho-help')?.addEventListener('click', () => {
            const guide = document.getElementById('vmo-zoho-help-guide');
            if (guide) {
                guide.style.display = guide.style.display === 'none' ? 'block' : 'none';
                if (guide.style.display === 'block') {
                    guide.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                }
            }
        });
        document.getElementById('vmo-zoho-help-close')?.addEventListener('click', () => {
            const guide = document.getElementById('vmo-zoho-help-guide');
            if (guide) guide.style.display = 'none';
        });

        // ✅ Generate Refresh Token button — opens Zoho OAuth in popup, auto-saves token
        document.getElementById('vmo-zoho-generate-token')?.addEventListener('click', async () => {
            cleanupZohoWatcher();

            const clientId = document.getElementById('vmo-zoho-client-id')?.value?.trim();
            const clientSecret = document.getElementById('vmo-zoho-client-secret')?.value?.trim();
            const dataCenter = document.getElementById('vmo-zoho-datacenter')?.value || 'us';
            const errorDiv = document.getElementById('vmo-zoho-cred-error');
            const tokenStatus = document.getElementById('vmo-zoho-token-status');
            const genBtn = document.getElementById('vmo-zoho-generate-token');
            
            if (!clientId || !clientSecret) {
                if (errorDiv) {
                    errorDiv.textContent = 'Please enter your Client ID and Client Secret first, then click this button.';
                    errorDiv.style.display = 'block';
                }
                return;
            }
            
            // First, save credentials so the PHP backend can use them for the exchange
            genBtn.disabled = true;
            genBtn.innerHTML = '<span class="vmo-spinner"></span> Preparing authorization...';
            if (errorDiv) errorDiv.style.display = 'none';
            
            try {
                // Save credentials first
                const saveResp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_save_credentials',
                        security: vmo_ajax.nonce,
                        client_id: clientId,
                        client_secret: clientSecret,
                        refresh_token: '',
                        data_center: dataCenter
                    })
                });
                const saveData = await saveResp.json();
                if (!saveData.success) throw new Error(saveData.data?.message || 'Failed to save credentials');
                
                // Get the authorization URL from the backend
                const authResp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_get_auth_url',
                        security: vmo_ajax.nonce
                    })
                });
                const authData = await authResp.json();
                if (!authData.success) throw new Error(authData.data?.message || 'Failed to generate authorization URL');
                
                // Open the Zoho authorization page in a popup
                const authUrl = authData.data.auth_url;
                const phpRedirectUri = authData.data.redirect_uri || redirectUrl;
                const oauthState = authData.data.oauth_state || '';
                console.log('🔑 Zoho OAuth redirect_uri from PHP:', phpRedirectUri);
                
                // ✅ PRE-FLIGHT: Show the exact redirect URI being used so the user can verify
                // This is critical — if this doesn't match Zoho Console, the popup will show "Invalid Redirect Uri"
                if (tokenStatus) {
                    tokenStatus.style.background = 'rgba(59,130,246,0.1)';
                    tokenStatus.style.borderColor = 'rgba(59,130,246,0.3)';
                    tokenStatus.style.color = '#60a5fa';
                    tokenStatus.innerHTML = `🔗 Opening Zoho authorization...<br><span style="font-size:11px;color:#94a3b8;margin-top:4px;display:block;">Redirect URI being used: <code style="background:#0f172a;padding:2px 6px;border-radius:4px;color:#fbbf24;word-break:break-all;">${phpRedirectUri}</code></span>`;
                }
                
                const popup = window.open(authUrl, 'vmo_zoho_auth', 'width=600,height=700,scrollbars=yes,resizable=yes');
                
                if (!popup || popup.closed || typeof popup.closed === 'undefined') {
                    // Popup was blocked
                    if (tokenStatus) {
                        tokenStatus.style.background = 'rgba(239,68,68,0.1)';
                        tokenStatus.style.borderColor = 'rgba(239,68,68,0.3)';
                        tokenStatus.style.color = '#f87171';
                        tokenStatus.innerHTML = '❌ Popup blocked by your browser. Please allow popups for this site, then try again.';
                    }
                    genBtn.innerHTML = '🔑 Generate Refresh Token Automatically';
                    genBtn.disabled = false;
                    return;
                }
                
                if (tokenStatus) {
                    tokenStatus.style.background = 'rgba(59,130,246,0.1)';
                    tokenStatus.style.borderColor = 'rgba(59,130,246,0.3)';
                    tokenStatus.style.color = '#60a5fa';
                    tokenStatus.innerHTML = `🔄 Waiting for you to authorize in the Zoho popup window...<br><span style="font-size:11px;color:#94a3b8;margin-top:4px;display:block;">If Zoho says "Invalid Redirect Uri", make sure this <strong>exact</strong> URI is in your Zoho API Console: <code style="background:#0f172a;padding:2px 6px;border-radius:4px;color:#fbbf24;word-break:break-all;">${phpRedirectUri}</code></span>`;
                }
                genBtn.innerHTML = '⏳ Waiting for authorization...';
                
                // Listen for the postMessage from the OAuth callback page
                const tokenResult = await new Promise((resolve) => {
                    let pollInterval = null;
                    let timeoutId = null;
                    let settled = false;

                    const finish = (result) => {
                        if (settled) {
                            return;
                        }
                        settled = true;
                        if (pollInterval) {
                            clearInterval(pollInterval);
                            pollInterval = null;
                        }
                        if (timeoutId) {
                            clearTimeout(timeoutId);
                            timeoutId = null;
                        }
                        window.removeEventListener('message', messageHandler);
                        this._zohoOAuthCleanup = null;
                        resolve(result);
                    };

                    // Method 1: Listen for postMessage from popup
                    const messageHandler = (event) => {
                        if (event.origin !== window.location.origin) {
                            return;
                        }
                        if (event.data?.type !== 'vmo_zoho_token_obtained') {
                            return;
                        }
                        if (oauthState && event.data?.state && event.data.state !== oauthState) {
                            return;
                        }

                        finish({
                            success: !!event.data?.success,
                            message: event.data?.message || ''
                        });
                    };
                    window.addEventListener('message', messageHandler);

                    this._zohoOAuthCleanup = () => finish({ success: false, message: '' });

                    // Method 2: Poll the backend in case postMessage doesn't work
                    pollInterval = setInterval(async () => {
                        try {
                            const statusResp = await fetch(vmoGetAdminAjaxUrl(), {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                                body: new URLSearchParams({
                                    action: 'vmo_zoho_check_token_status',
                                    security: vmo_ajax.nonce,
                                    oauth_state: oauthState
                                })
                            });
                            const statusData = await statusResp.json();
                            if (statusData.success) {
                                const pollStatus = statusData.data?.status || '';
                                if (pollStatus === 'success') {
                                    finish({
                                        success: true,
                                        message: statusData.data?.message || ''
                                    });
                                    return;
                                }
                                if (pollStatus === 'error') {
                                    finish({
                                        success: false,
                                        message: statusData.data?.message || ''
                                    });
                                    return;
                                }
                            }
                        } catch (e) {
                            console.debug('Zoho token status poll failed:', e);
                        }

                        // Also detect if the popup was closed without completing
                        if (popup && popup.closed) {
                            finish({ success: false, message: '' });
                        }
                    }, 2000);

                    // Timeout after 5 minutes
                    timeoutId = setTimeout(() => finish({ success: false, message: '' }), 300000);
                });
                
                if (tokenResult.success) {
                    if (tokenStatus) {
                        tokenStatus.style.background = 'rgba(34,197,94,0.1)';
                        tokenStatus.style.borderColor = 'rgba(34,197,94,0.3)';
                        tokenStatus.style.color = '#22c55e';
                        tokenStatus.innerHTML = '✅ Refresh token obtained and saved! Click "Verify & Save" to proceed.';
                    }
                    genBtn.innerHTML = '✅ Token Generated Successfully';
                    genBtn.style.background = 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)';
                } else {
                    if (tokenStatus) {
                        const failureMessage = tokenResult.message
                            ? `<div style="margin:8px 0 0 0;font-size:12px;color:#fecaca;line-height:1.6;">${escapeHtml(tokenResult.message)}</div>`
                            : '';
                        tokenStatus.style.background = 'rgba(239,68,68,0.08)';
                        tokenStatus.style.borderColor = 'rgba(239,68,68,0.3)';
                        tokenStatus.style.color = '#f87171';
                        tokenStatus.innerHTML = `
                            ❌ <strong>Authorization failed or window was closed.</strong><br>
                            ${failureMessage}
                            <div style="margin-top:8px;font-size:12px;color:#d1d5db;line-height:1.7;">
                                <strong>If Zoho said "Invalid Redirect Uri":</strong><br>
                                Go to <a href="https://api-console.zoho.com/" target="_blank" style="color:#60a5fa;">Zoho API Console</a> → click your app → <strong>Settings</strong> tab → update the <strong>Authorized Redirect URI</strong> to this <em>exact</em> value:<br>
                                <code id="vmo-zoho-debug-uri" style="display:block;margin:6px 0;padding:8px 12px;background:#0f172a;border-radius:6px;color:#fbbf24;font-size:13px;word-break:break-all;cursor:pointer;" title="Click to copy">${phpRedirectUri}</code>
                                <span id="vmo-zoho-uri-copied" style="color:#22c55e;font-size:11px;display:none;">✓ Copied!</span>
                                <span style="color:#94a3b8;font-size:11px;">Click the URI above to copy it. Then paste it in Zoho and try again.</span>
                            </div>`;
                        // Make the URI clickable to copy
                        document.getElementById('vmo-zoho-debug-uri')?.addEventListener('click', async () => {
                            try {
                                await navigator.clipboard.writeText(phpRedirectUri);
                                const copiedEl = document.getElementById('vmo-zoho-uri-copied');
                                if (copiedEl) { copiedEl.style.display = 'inline'; setTimeout(() => copiedEl.style.display = 'none', 2000); }
                            } catch(_) {}
                        });
                    }
                    genBtn.innerHTML = '🔑 Generate Refresh Token Automatically';
                    genBtn.disabled = false;
                }
            } catch (e) {
                console.error('❌ Zoho OAuth error:', e);
                if (errorDiv) {
                    let msg = e.message || 'Unknown error';
                    // Provide actionable guidance for common errors
                    if (msg.includes('Failed to save')) {
                        msg += ' — Please check your Client ID and Client Secret are correct.';
                    } else if (msg.includes('Failed to generate')) {
                        msg += ' — Please ensure your Client ID is saved and valid.';
                    } else if (msg.includes('invalid_redirect_uri') || msg.includes('redirect_uri')) {
                        msg += ' — The Redirect URI in your Zoho API Console must match exactly: ' + redirectUrl;
                    }
                    errorDiv.textContent = msg;
                    errorDiv.style.display = 'block';
                }
                genBtn.innerHTML = '🔑 Generate Refresh Token Automatically';
                genBtn.disabled = false;
            }
        });

        document.getElementById('vmo-zoho-save-creds').addEventListener('click', async () => {
            const btn = document.getElementById('vmo-zoho-save-creds');
            const errorDiv = document.getElementById('vmo-zoho-cred-error');
            
            const dataCenter = document.getElementById('vmo-zoho-datacenter').value;
            const clientId = document.getElementById('vmo-zoho-client-id').value.trim();
            const clientSecret = document.getElementById('vmo-zoho-client-secret').value.trim();
            const refreshToken = document.getElementById('vmo-zoho-refresh-token').value.trim();

            if (!clientId || !clientSecret) {
                errorDiv.textContent = 'Please enter both Client ID and Client Secret';
                errorDiv.style.display = 'block';
                return;
            }

            btn.disabled = true;
            btn.innerHTML = '<span class="vmo-spinner"></span> Verifying...';
            errorDiv.style.display = 'none';

            try {
                // Save credentials (only send refresh_token if manually entered — otherwise preserve the OAuth-obtained token)
                const saveBody = {
                    action: 'vmo_zoho_save_credentials',
                    security: vmo_ajax.nonce,
                    client_id: clientId,
                    client_secret: clientSecret,
                    data_center: dataCenter
                };
                // Only send refresh_token if user typed one in the advanced manual field
                if (refreshToken) {
                    saveBody.refresh_token = refreshToken;
                }
                const saveResp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams(saveBody)
                });
                const saveData = await saveResp.json();

                if (!saveData.success) {
                    throw new Error(saveData.data?.message || 'Failed to save credentials');
                }

                // Verify connection
                const verifyResp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_verify_connection',
                        security: vmo_ajax.nonce
                    })
                });
                const verifyData = await verifyResp.json();

                if (verifyData.success && verifyData.data.connected) {
                    onNext();
                } else if (!refreshToken) {
                    errorDiv.innerHTML = 'Credentials saved! Now click the <strong>"Generate Refresh Token Automatically"</strong> button above to complete the connection.';
                    errorDiv.style.display = 'block';
                    errorDiv.style.color = '#fbbf24';
                    btn.disabled = false;
                    btn.textContent = 'Verify & Save Credentials';
                } else {
                    throw new Error(verifyData.data?.message || 'Connection verification failed');
                }
            } catch (e) {
                errorDiv.textContent = e.message;
                errorDiv.style.display = 'block';
                btn.disabled = false;
                btn.textContent = 'Verify & Save Credentials';
            }
        });

        // ✅ "Skip entire email setup" link — closes modal and continues consultation
        if (typeof skipAllAndContinue === 'function') {
            const skipBtn = document.createElement('button');
            skipBtn.textContent = 'Skip email setup entirely — continue consultation →';
            skipBtn.style.cssText = 'display: block; width: 100%; margin-top: 14px; background: transparent; border: none; color: #64748b; font-size: 12px; cursor: pointer; text-decoration: underline; text-align: center; padding: 6px 0;';
            skipBtn.addEventListener('click', skipAllAndContinue);
            modal.querySelector('.vmo-email-modal-content > div')?.appendChild(skipBtn);
        }
        }; // end renderContent()

        // ✅ FIX: Fetch the canonical redirect URI from PHP FIRST, then render.
        // This prevents the race condition where the user copies a JS-fallback URL
        // that differs from what PHP's admin_url() generates.
        (async () => {
            try {
                const uriResp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_get_redirect_uri',
                        security: vmo_ajax.nonce
                    })
                });
                const uriData = await uriResp.json();
                if (uriData.success && uriData.data?.redirect_uri) {
                    redirectUrl = uriData.data.redirect_uri;
                    redirectUrlLoaded = true;
                    console.log('✅ Zoho redirect URI loaded from PHP:', redirectUrl);
                } else {
                    // PHP fetch succeeded but no data — fall back to constructed URL
                    redirectUrl = window.location.origin + '/wp-admin/admin-post.php?action=vmo_zoho_oauth_callback';
                    redirectUrlLoaded = true;
                    console.warn('⚠️ PHP redirect URI response missing data, using fallback');
                }
            } catch (e) {
                // Network error — use the best-guess fallback
                redirectUrl = window.location.origin + '/wp-admin/admin-post.php?action=vmo_zoho_oauth_callback';
                redirectUrlLoaded = true;
                console.warn('⚠️ Could not fetch canonical redirect URI, using fallback:', e);
            }
            // Now render with the correct (or fallback) URL
            renderContent(redirectUrl);
        })();
    }

    // Step 3: Setup DNS Records
    renderZohoStep3_SetupDNS(modal, enforcedDomain, closeModal, onNext, skipAllAndContinue, category, categoryLabel, onSuccess) {
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(168, 85, 247, 0.4);
                border-radius: 16px; max-width: 700px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px;">📧 Configure DNS Records</h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">@${enforcedDomain}</p>
                        </div>
                        <button id="vmo-zoho-close" style="background: transparent; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;">&times;</button>
                    </div>

                    <!-- Progress Bar -->
                    <div style="display: flex; gap: 8px; margin-bottom: 24px;">
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #a855f7; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #334155; border-radius: 2px;"></div>
                    </div>

                    <!-- Instructions -->
                    <div style="background: rgba(168, 85, 247, 0.1); border: 1px solid rgba(168, 85, 247, 0.3); border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <h4 style="margin: 0 0 12px 0; color: #c084fc; font-size: 16px;">Step 3: Add DNS Records to Your Domain</h4>
                        <p style="margin: 0; color: #d1d5db; font-size: 14px; line-height: 1.6;">
                            Add these records at your domain registrar (Namecheap, GoDaddy, Cloudflare, etc.) 
                            to point your email to Zoho Mail.
                        </p>
                    </div>

                    <!-- DNS Records Table — populated dynamically from backend based on selected data center -->
                    <div style="background: #0f172a; border-radius: 12px; padding: 16px; margin-bottom: 20px; overflow-x: auto;">
                        <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
                            <thead>
                                <tr style="color: #94a3b8; text-align: left; border-bottom: 1px solid #334155;">
                                    <th style="padding: 8px 6px;">Type</th>
                                    <th style="padding: 8px 6px;">Host/Name</th>
                                    <th style="padding: 8px 6px;">Value/Points To</th>
                                    <th style="padding: 8px 6px;">Priority</th>
                                </tr>
                            </thead>
                            <tbody id="vmo-zoho-dns-records" style="color: #e2e8f0;">
                                <tr><td colspan="4" style="padding: 20px; text-align: center; color: #94a3b8;">
                                    <span class="vmo-spinner" style="display: inline-block; margin-right: 8px;"></span>
                                    Loading DNS records for your data center...
                                </td></tr>
                            </tbody>
                        </table>
                    </div>

                    <!-- Important Note about MX records -->
                    <div style="background: rgba(59, 130, 246, 0.08); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; padding: 12px; margin-bottom: 16px;">
                        <p style="margin: 0; color: #93c5fd; font-size: 12px; line-height: 1.6;">
                            <strong>Important:</strong> Only add Zoho's MX records. Remove any MX records from previous email providers to avoid delivery conflicts.
                            After adding MX records, check with <a href="https://www.zoho.com/toolkit/mx-lookup.html" target="_blank" style="color: #60a5fa;">Zoho's MX Lookup Tool</a> to confirm they're correct.
                        </p>
                    </div>

                    <!-- Registrar Quick Links -->
                    <div style="background: #1e293b; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
                        <p style="margin: 0 0 10px 0; color: #94a3b8; font-size: 12px;">Quick links to DNS settings:</p>
                        <div style="display: flex; flex-wrap: wrap; gap: 8px;">
                            <a href="https://ap.www.namecheap.com/Domains/DomainControlPanel/${enforcedDomain}/advancedns" target="_blank" style="
                                padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #60a5fa;
                                text-decoration: none; font-size: 12px; border: 1px solid #334155;
                            ">Namecheap DNS ↗</a>
                            <a href="https://dcc.godaddy.com/manage/${enforcedDomain}/dns" target="_blank" style="
                                padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #60a5fa;
                                text-decoration: none; font-size: 12px; border: 1px solid #334155;
                            ">GoDaddy DNS ↗</a>
                            <a href="https://dash.cloudflare.com/" target="_blank" style="
                                padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #60a5fa;
                                text-decoration: none; font-size: 12px; border: 1px solid #334155;
                            ">Cloudflare DNS ↗</a>
                            <a href="https://account.squarespace.com/" target="_blank" style="
                                padding: 8px 12px; border-radius: 6px; background: #0f172a; color: #60a5fa;
                                text-decoration: none; font-size: 12px; border: 1px solid #334155;
                            ">Squarespace DNS ↗</a>
                        </div>
                    </div>

                    <div style="text-align: center; margin-bottom: 16px;">
                        <button id="vmo-zoho-dns-help" style="background: transparent; border: 1px solid rgba(139, 92, 246, 0.4); color: #8b5cf6; padding: 10px 14px; border-radius: 8px; font-size: 12px; cursor: pointer;">
                            🤖 I don’t know where to add DNS — help me
                        </button>
                    </div>

                    <!-- Verification Status -->
                    <div id="vmo-zoho-verify-status" style="background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.3); border-radius: 8px; padding: 14px; margin-bottom: 20px;">
                        <p style="margin: 0; color: #fbbf24; font-size: 13px; display: flex; align-items: center; gap: 8px;">
                            <span>⏳</span> DNS changes can take 15 minutes to 48 hours to propagate. Click "Verify Domain" after adding records.
                        </p>
                    </div>

                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 10px;">
                        <button id="vmo-zoho-back" style="
                            padding: 12px 20px; border-radius: 8px;
                            background: transparent; border: 1px solid #475569; color: #94a3b8;
                            font-size: 14px; cursor: pointer;
                        ">← Back</button>
                        <button id="vmo-zoho-verify-domain" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%);
                            border: none; color: white; font-weight: 600; font-size: 14px; cursor: pointer;
                        ">Verify Domain & Continue</button>
                    </div>
                </div>
            </div>
        `;

        document.getElementById('vmo-zoho-close').addEventListener('click', closeModal);
        document.getElementById('vmo-zoho-back').addEventListener('click', () => {
            this.renderZohoStep2_GetAPICredentials(modal, enforcedDomain, closeModal, onNext, skipAllAndContinue);
        });

        // Load accurate DNS records from backend when available
        (async () => {
            try {
                const resp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_get_dns_records',
                        security: vmo_ajax.nonce,
                        domain: enforcedDomain
                    })
                });
                const data = await resp.json();
                if (!data.success || !data.data) return;
                const records = data.data.records || data.data || [];
                const tbody = document.getElementById('vmo-zoho-dns-records');
                if (!tbody || !Array.isArray(records) || !records.length) return;

                const badge = (type) => {
                    const t = (type || '').toUpperCase();
                    const bg = t === 'TXT' ? '#059669' : '#7c3aed';
                    return `<span style="background: ${bg}; padding: 2px 8px; border-radius: 4px;">${t || 'DNS'}</span>`;
                };

                tbody.innerHTML = records.map(rec => {
                    const type = (rec.type || rec.record_type || '').toUpperCase();
                    const host = rec.host || rec.name || rec.key || '@';
                    const value = rec.value || rec.points_to || rec.target || '';
                    const priority = rec.priority || rec.pri || '—';
                    return `
                        <tr style="border-bottom: 1px solid #1e293b;">
                            <td style="padding: 10px 6px;">${badge(type)}</td>
                            <td style="padding: 10px 6px;">${host}</td>
                            <td style="padding: 10px 6px; font-family: monospace; color: #fbbf24; word-break: break-all;">${value}</td>
                            <td style="padding: 10px 6px;">${priority}</td>
                        </tr>
                    `;
                }).join('');
            } catch (e) {
                console.warn('Zoho DNS record load failed:', e);
            }
        })();

        document.getElementById('vmo-zoho-dns-help')?.addEventListener('click', () => {
            closeModal();
            const prompt = `I need to add Zoho Mail DNS records for ${enforcedDomain}, but I don't know where DNS settings are. Please ask me who my domain provider is and then guide me step-by-step.`;
            this.addMessageToUI('user', prompt);
            this.handleUserMessage(prompt);
        });

        document.getElementById('vmo-zoho-verify-domain').addEventListener('click', async () => {
            const btn = document.getElementById('vmo-zoho-verify-domain');
            const statusDiv = document.getElementById('vmo-zoho-verify-status');

            btn.disabled = true;
            btn.innerHTML = '<span class="vmo-spinner"></span> Checking DNS...';

            try {
                const resp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_check_domain_verification',
                        security: vmo_ajax.nonce,
                        domain: enforcedDomain
                    })
                });
                const data = await resp.json();

                if (data.success && data.data.verified) {
                    statusDiv.style.background = 'rgba(34, 197, 94, 0.1)';
                    statusDiv.style.borderColor = 'rgba(34, 197, 94, 0.3)';
                    statusDiv.innerHTML = '<p style="margin: 0; color: #22c55e; font-size: 13px; display: flex; align-items: center; gap: 8px;"><span>✅</span> Domain verified! Auto-creating your email account...</p>';
                    
                    // ✅ POST-DNS AUTOMATION: After domain verification, auto-create the email
                    // Determine the default prefix for this email category
                    const suggestedPrefixes = {
                        'customer_support': 'support', 'support': 'support',
                        'sales': 'sales', 'general': 'info', 'info': 'info',
                        'newsletter': 'newsletter', 'billing': 'billing',
                        'transactions': 'billing', 'marketing': 'marketing',
                        'customercare': 'care', 'bizdev': 'partnerships'
                    };
                    const autoPrefix = suggestedPrefixes[category] || 'info';
                    const autoEmail = autoPrefix + '@' + enforcedDomain;
                    const autoDisplayName = (categoryLabel || category || 'General') + ' - ' + enforcedDomain;
                    
                    try {
                        const createResp = await fetch(vmoGetAdminAjaxUrl(), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                            body: new URLSearchParams({
                                action: 'vmo_zoho_create_email',
                                security: vmo_ajax.nonce,
                                email_prefix: autoPrefix,
                                domain: enforcedDomain,
                                display_name: autoDisplayName,
                                password: '' // Let Zoho generate
                            })
                        });
                        const createData = await createResp.json();
                        
                        if (createData.success) {
                            statusDiv.innerHTML = `<p style="margin: 0; color: #22c55e; font-size: 13px;">✅ Domain verified & <strong>${autoEmail}</strong> created automatically! Completing setup...</p>`;
                            // Call the onSuccess callback with the created email data
                            if (typeof onSuccess === 'function') {
                                onSuccess({
                                    email: autoEmail,
                                    display_name: autoDisplayName,
                                    provider_preset: 'zoho',
                                    smtp_settings: createData.data?.smtp_settings || {}
                                });
                            }
                            // Close modal after a brief delay
                            setTimeout(closeModal, 2000);
                        } else {
                            // Auto-creation failed — fall through to manual Step 4
                            console.warn('Auto email creation failed:', createData.data?.message);
                            statusDiv.innerHTML = '<p style="margin: 0; color: #22c55e; font-size: 13px;">✅ Domain verified! Please create your email account on the next screen.</p>';
                            setTimeout(onNext, 1500);
                        }
                    } catch (autoErr) {
                        console.warn('Auto email creation error:', autoErr);
                        statusDiv.innerHTML = '<p style="margin: 0; color: #22c55e; font-size: 13px;">✅ Domain verified! Proceeding to email creation...</p>';
                        setTimeout(onNext, 1500);
                    }
                } else {
                    statusDiv.style.background = 'rgba(251, 191, 36, 0.1)';
                    statusDiv.style.borderColor = 'rgba(251, 191, 36, 0.3)';
                    statusDiv.innerHTML = `<p style="margin: 0; color: #fbbf24; font-size: 13px; display: flex; align-items: center; gap: 8px;"><span>⏳</span> ${data.data?.message || 'DNS not yet propagated. Try again in a few minutes.'}</p>`;
                    btn.disabled = false;
                    btn.textContent = 'Verify Domain & Continue';
                }
            } catch (e) {
                statusDiv.innerHTML = `<p style="margin: 0; color: #f87171; font-size: 13px;">${e.message}</p>`;
                btn.disabled = false;
                btn.textContent = 'Verify Domain & Continue';
            }
        });

        // ✅ "Skip email setup entirely" — closes modal and continues consultation immediately
        if (typeof skipAllAndContinue === 'function') {
            const skipBtn = document.createElement('button');
            skipBtn.textContent = 'Skip email setup entirely — continue consultation →';
            skipBtn.style.cssText = 'display: block; width: 100%; margin-top: 14px; background: transparent; border: none; color: #64748b; font-size: 12px; cursor: pointer; text-decoration: underline; text-align: center; padding: 6px 0;';
            skipBtn.addEventListener('click', skipAllAndContinue);
            modal.querySelector('.vmo-email-modal-content > div')?.appendChild(skipBtn);
        }
    }

    // Step 4: Create Email Account
    renderZohoCreateEmailModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess, status) {
        const suggestedEmails = {
            'customer_support': ['support', 'help', 'customercare'],
            'sales': ['sales', 'orders', 'shop'],
            'general': ['info', 'hello', 'contact'],
            'newsletter': ['newsletter', 'updates', 'news'],
            'billing': ['billing', 'accounts', 'payments']
        };
        const suggestions = suggestedEmails[category] || suggestedEmails['general'];

        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(34, 197, 94, 0.4);
                border-radius: 16px; max-width: 550px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px;">✅ Create Email Account</h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">${categoryLabel} • @${enforcedDomain}</p>
                        </div>
                        <button id="vmo-zoho-close" style="background: transparent; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;">&times;</button>
                    </div>

                    <!-- Progress Bar - Complete -->
                    <div style="display: flex; gap: 8px; margin-bottom: 24px;">
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                        <div style="flex: 1; height: 4px; background: #22c55e; border-radius: 2px;"></div>
                    </div>

                    <!-- Success Banner -->
                    <div style="background: rgba(34, 197, 94, 0.15); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; padding: 16px; margin-bottom: 20px; text-align: center;">
                        <p style="margin: 0; color: #22c55e; font-size: 14px;">🎉 Zoho Mail is connected! Create your email account below.</p>
                    </div>

                    <!-- Email Form -->
                    <div style="background: #0f172a; border-radius: 12px; padding: 20px; margin-bottom: 20px;">
                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Email Address</label>
                            <div style="display: flex; align-items: center; gap: 0;">
                                <input type="text" id="vmo-zoho-email-prefix" placeholder="support" style="
                                    flex: 1; padding: 12px; border-radius: 8px 0 0 8px;
                                    background: #1e293b; border: 1px solid #334155; border-right: none;
                                    color: #f1f5f9; font-size: 14px;
                                ">
                                <span style="padding: 12px 14px; background: #334155; color: #94a3b8; font-size: 14px; border-radius: 0 8px 8px 0;">@${enforcedDomain}</span>
                            </div>
                            <div style="display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap;">
                                ${suggestions.map(s => `<button class="vmo-email-suggestion" data-prefix="${s}" style="
                                    padding: 4px 10px; border-radius: 12px; background: #1e293b;
                                    border: 1px solid #475569; color: #94a3b8; font-size: 11px; cursor: pointer;
                                ">${s}@</button>`).join('')}
                            </div>
                        </div>

                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">Display Name</label>
                            <input type="text" id="vmo-zoho-display-name" placeholder="Customer Support" style="
                                width: 100%; padding: 12px; border-radius: 8px; box-sizing: border-box;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 14px;
                            ">
                        </div>

                        <div style="margin-bottom: 16px;">
                            <label style="display: block; color: #94a3b8; font-size: 12px; margin-bottom: 6px; text-transform: uppercase;">
                                Password <span style="color: #64748b;">(leave empty to auto-generate)</span>
                            </label>
                            <input type="password" id="vmo-zoho-password" placeholder="••••••••••" style="
                                width: 100%; padding: 12px; border-radius: 8px; box-sizing: border-box;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9; font-size: 14px;
                            ">
                        </div>

                        <div id="vmo-zoho-create-error" style="display: none; color: #f87171; font-size: 13px; margin-bottom: 12px;"></div>
                        <div id="vmo-zoho-create-success" style="display: none; background: rgba(34, 197, 94, 0.1); border-radius: 8px; padding: 12px;"></div>
                    </div>

                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 10px;">
                        <button id="vmo-zoho-cancel" style="
                            padding: 12px 20px; border-radius: 8px;
                            background: transparent; border: 1px solid #475569; color: #94a3b8;
                            font-size: 14px; cursor: pointer;
                        ">Cancel</button>
                        <button id="vmo-zoho-create-email" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white; font-weight: 600; font-size: 14px; cursor: pointer;
                        ">Create Email Account</button>
                    </div>
                </div>
            </div>
        `;

        document.getElementById('vmo-zoho-close').addEventListener('click', closeModal);
        document.getElementById('vmo-zoho-cancel').addEventListener('click', closeModal);

        // Suggestion buttons
        document.querySelectorAll('.vmo-email-suggestion').forEach(btn => {
            btn.addEventListener('click', () => {
                document.getElementById('vmo-zoho-email-prefix').value = btn.dataset.prefix;
            });
        });

        // Create email
        document.getElementById('vmo-zoho-create-email').addEventListener('click', async () => {
            const btn = document.getElementById('vmo-zoho-create-email');
            const errorDiv = document.getElementById('vmo-zoho-create-error');
            const successDiv = document.getElementById('vmo-zoho-create-success');

            const prefix = document.getElementById('vmo-zoho-email-prefix').value.trim();
            const displayName = document.getElementById('vmo-zoho-display-name').value.trim();
            const password = document.getElementById('vmo-zoho-password').value;

            if (!prefix) {
                errorDiv.textContent = 'Please enter an email address';
                errorDiv.style.display = 'block';
                return;
            }

            btn.disabled = true;
            btn.innerHTML = '<span class="vmo-spinner"></span> Creating...';
            errorDiv.style.display = 'none';
            successDiv.style.display = 'none';

            try {
                const resp = await fetch(vmoGetAdminAjaxUrl(), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_zoho_create_email',
                        security: vmo_ajax.nonce,
                        email_prefix: prefix,
                        display_name: displayName,
                        password: password,
                        category: category
                    })
                });
                const data = await resp.json();

                if (data.success) {
                    successDiv.style.display = 'block';
                    successDiv.innerHTML = `
                        <p style="margin: 0 0 10px 0; color: #22c55e; font-weight: 600;">✅ Email created successfully!</p>
                        <p style="margin: 0 0 6px 0; color: #e2e8f0; font-size: 13px;"><strong>Email:</strong> ${data.data.email}</p>
                        <p style="margin: 0 0 6px 0; color: #e2e8f0; font-size: 13px;"><strong>Password:</strong> <code style="background: #1e293b; padding: 2px 6px; border-radius: 4px;">${data.data.password}</code></p>
                        <p style="margin: 0 0 6px 0; color: #e2e8f0; font-size: 13px;"><strong>Webmail:</strong> <a href="${data.data.webmail_url}" target="_blank" style="color: #60a5fa;">${data.data.webmail_url}</a></p>
                        <div style="margin: 14px 0 0 0; background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.25); border-radius: 8px; padding: 12px;">
                            <p style="margin: 0 0 6px 0; color: #fbbf24; font-size: 12px; font-weight: 600;">⚠️ Important: Zoho Free Plan Limitations</p>
                            <p style="margin: 0 0 6px 0; color: #e2e8f0; font-size: 12px; line-height: 1.5;">The <strong>Free plan does NOT include IMAP/POP/ActiveSync</strong>. Your email works via <strong>Zoho webmail only</strong>. Automated email features (sending from dashboard, email integrations) require IMAP/SMTP access.</p>
                            <p style="margin: 0; color: #94a3b8; font-size: 11px;">To unlock full IMAP/SMTP, upgrade to <a href="https://www.zoho.com/mail/zohomail-pricing.html" target="_blank" style="color: #60a5fa; text-decoration: underline;">Mail Lite ($1/user/month)</a> — enables desktop clients, dashboard sending, and automations.</p>
                        </div>
                        <p style="margin: 12px 0 0 0; color: #fbbf24; font-size: 12px;">⚠️ Save this password now - it won't be shown again!</p>
                    `;

                    btn.textContent = 'Done!';
                    btn.style.background = '#22c55e';

                    // Call success callback with email details
                    if (onSuccess) {
                        setTimeout(() => {
                            onSuccess({
                                email: data.data.email,
                                password: data.data.password,
                                smtp_settings: data.data.smtp_settings,
                                category: category
                            });
                        }, 3000);
                    }
                } else {
                    throw new Error(data.data?.message || 'Failed to create email');
                }
            } catch (e) {
                errorDiv.textContent = e.message;
                errorDiv.style.display = 'block';
                btn.disabled = false;
                btn.textContent = 'Create Email Account';
            }
        });
    }
    
    // Render modal for auto-provisioning (cPanel, Plesk, DirectAdmin)
    renderAutoProvisionEmailModal(modal, hosting, category, categoryLabel, enforcedDomain, closeModal, onSuccess) {
        const panelNames = {
            'cpanel': 'cPanel',
            'plesk': 'Plesk',
            'directadmin': 'DirectAdmin',
            'cloudflare': 'Cloudflare + MXroute Route'
        };
        const panelName = panelNames[hosting.panel_type] || hosting.panel_type;
        
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(34, 197, 94, 0.3);
                border-radius: 16px; max-width: 560px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px; display: flex; align-items: center; gap: 8px;">
                                <span style="font-size: 22px;">✨</span> Auto-Provision Email Account
                            </h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">
                                ${categoryLabel} — @${enforcedDomain}
                            </p>
                        </div>
                        <button id="vmo-email-modal-close" style="
                            background: transparent; border: none; color: #94a3b8;
                            font-size: 24px; cursor: pointer; padding: 4px;
                        ">&times;</button>
                    </div>
                    
                    <!-- Success Detection Notice -->
                    <div style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 12px; padding: 16px; margin-bottom: 20px;">
                        <div style="display: flex; gap: 12px; align-items: flex-start;">
                            <span style="font-size: 24px;">🎉</span>
                            <div>
                                <h4 style="margin: 0 0 4px 0; color: #86efac; font-size: 14px;">${panelName} Control Panel Detected!</h4>
                                <p style="margin: 0; color: #a7f3d0; font-size: 12px;">
                                    We can automatically create email accounts on your server. No manual SMTP configuration needed!
                                </p>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Credentials Setup Section -->
                    <div id="vmo-panel-credentials-section">
                        <div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; padding: 14px; margin-bottom: 16px;">
                            <p style="margin: 0; color: #93c5fd; font-size: 12px; line-height: 1.6;">
                                <strong>One-time setup:</strong> Enter your ${panelName} API credentials below. 
                                These will be securely encrypted and used to create all your business email accounts automatically.
                            </p>
                        </div>
                        
                        <!-- Panel URL -->
                        <div style="margin-bottom: 14px;">
                            <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                                ${panelName} URL
                            </label>
                            <input type="text" id="vmo-panel-url" value="${hosting.panel_url || ''}" placeholder="https://yourdomain.com:2083" style="
                                width: 100%; padding: 10px 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                font-size: 14px; box-sizing: border-box;
                            ">
                        </div>
                        
                        <!-- Username -->
                        <div style="margin-bottom: 14px;">
                            <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                                ${panelName} Username
                            </label>
                            <input type="text" id="vmo-panel-username" placeholder="Your cPanel/hosting username" style="
                                width: 100%; padding: 10px 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                font-size: 14px; box-sizing: border-box;
                            ">
                        </div>
                        
                        <!-- API Token -->
                        <div style="margin-bottom: 14px;">
                            <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                                API Token / Key
                            </label>
                            <input type="password" id="vmo-panel-token" placeholder="Your API token" style="
                                width: 100%; padding: 10px 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                font-size: 14px; box-sizing: border-box;
                            ">
                            <p style="margin: 6px 0 0 0; color: #64748b; font-size: 11px;">
                                ${hosting.panel_type === 'cpanel' ? 'Find this in cPanel → Security → Manage API Tokens' : 
                                  hosting.panel_type === 'plesk' ? 'Find this in Plesk → Tools & Settings → API' :
                                  'Find this in DirectAdmin → Account Manager → Login Keys'}
                            </p>
                        </div>
                        
                        <div id="vmo-panel-error" style="display: none; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: 8px; padding: 12px; margin-bottom: 14px;">
                            <p style="margin: 0; color: #f87171; font-size: 12px;"></p>
                        </div>
                        
                        <button id="vmo-test-connection" style="
                            width: 100%; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                            border: none; color: white;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                            margin-bottom: 12px;
                        ">🔗 Test Connection & Save Credentials</button>
                    </div>
                    
                    <!-- Email Creation Section (hidden until credentials work) -->
                    <div id="vmo-email-creation-section" style="display: none;">
                        <div style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: 8px; padding: 12px; margin-bottom: 16px;">
                            <p style="margin: 0; color: #86efac; font-size: 12px;">✅ Connected to ${panelName} successfully!</p>
                        </div>
                        
                        <!-- Email Address -->
                        <div style="margin-bottom: 14px;">
                            <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                                Email Address
                            </label>
                            <div style="display: flex; gap: 8px; align-items: center;">
                                <input type="text" id="vmo-email-local" value="${category}" placeholder="${category}" style="
                                    flex: 1; padding: 10px 12px; border-radius: 8px;
                                    background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                    font-size: 14px; box-sizing: border-box;
                                ">
                                <span style="color: #94a3b8; font-size: 14px;">@${enforcedDomain}</span>
                            </div>
                        </div>
                        
                        <!-- Password (optional - auto-generated if not provided) -->
                        <div style="margin-bottom: 14px;">
                            <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                                Password <span style="color: #64748b; font-weight: 400;">(leave blank to auto-generate)</span>
                            </label>
                            <input type="password" id="vmo-email-password" placeholder="Auto-generated if empty" style="
                                width: 100%; padding: 10px 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                font-size: 14px; box-sizing: border-box;
                            ">
                        </div>
                        
                        <div id="vmo-provision-error" style="display: none; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: 8px; padding: 12px; margin-bottom: 14px;">
                            <p style="margin: 0; color: #f87171; font-size: 12px;"></p>
                        </div>
                        
                        <button id="vmo-create-email" style="
                            width: 100%; padding: 14px; border-radius: 8px;
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                        ">✨ Create Email Account</button>
                    </div>
                    
                    <!-- Footer Actions -->
                    <div style="display: flex; gap: 10px; margin-top: 16px; padding-top: 16px; border-top: 1px solid #334155;">
                        <button id="vmo-setup-manual-instead" style="
                            flex: 1; padding: 10px; border-radius: 8px;
                            background: transparent; border: 1px solid #475569; color: #94a3b8;
                            font-size: 12px; cursor: pointer;
                        ">Setup Manually Instead</button>
                        <button id="vmo-email-cancel" style="
                            flex: 1; padding: 10px; border-radius: 8px;
                            background: #374151; border: none; color: #d1d5db;
                            font-size: 12px; cursor: pointer;
                        ">Cancel</button>
                    </div>
                </div>
            </div>
        `;

        const isCloudflareRoute = hosting.panel_type === 'cloudflare' && this.isVultrCloudflareMxrouteRouteReady(hosting);
        if (isCloudflareRoute) {
            const credentialsSection = document.getElementById('vmo-panel-credentials-section');
            const emailCreationSection = document.getElementById('vmo-email-creation-section');
            if (credentialsSection) {
                credentialsSection.style.display = 'none';
            }
            if (emailCreationSection) {
                emailCreationSection.style.display = 'block';
            }
        }
        
        // Event handlers
        const self = this;
        document.getElementById('vmo-email-modal-close').addEventListener('click', closeModal);
        document.getElementById('vmo-email-cancel').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
        
        document.getElementById('vmo-setup-manual-instead').addEventListener('click', () => {
            this.renderManualEmailSetupModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess);
        });
        
        // Test connection handler
        document.getElementById('vmo-test-connection').addEventListener('click', async function() {
            const btn = this;
            const errorDiv = document.getElementById('vmo-panel-error');
            const panelUrl = document.getElementById('vmo-panel-url').value.trim();
            const username = document.getElementById('vmo-panel-username').value.trim();
            const token = document.getElementById('vmo-panel-token').value.trim();
            
            if (!panelUrl || !username || !token) {
                errorDiv.querySelector('p').textContent = 'Please fill in all fields';
                errorDiv.style.display = 'block';
                return;
            }
            
            btn.textContent = '⏳ Testing connection...';
            btn.disabled = true;
            errorDiv.style.display = 'none';
            
            try {
                const result = await self.testAndSavePanelCredentials(hosting.panel_type, panelUrl, username, token);
                
                if (result.success) {
                    document.getElementById('vmo-panel-credentials-section').style.display = 'none';
                    document.getElementById('vmo-email-creation-section').style.display = 'block';
                } else {
                    errorDiv.querySelector('p').textContent = result.message || 'Connection failed';
                    errorDiv.style.display = 'block';
                    btn.textContent = '🔗 Test Connection & Save Credentials';
                    btn.disabled = false;
                }
            } catch (err) {
                errorDiv.querySelector('p').textContent = 'Connection error: ' + err.message;
                errorDiv.style.display = 'block';
                btn.textContent = '🔗 Test Connection & Save Credentials';
                btn.disabled = false;
            }
        });
        
        // Create email handler
        document.getElementById('vmo-create-email').addEventListener('click', async function() {
            const btn = this;
            const errorDiv = document.getElementById('vmo-provision-error');
            const localPart = document.getElementById('vmo-email-local').value.trim();
            const password = document.getElementById('vmo-email-password').value;
            
            if (!localPart) {
                errorDiv.querySelector('p').textContent = 'Please enter an email address';
                errorDiv.style.display = 'block';
                return;
            }
            
            btn.textContent = '⏳ Creating email account...';
            btn.disabled = true;
            errorDiv.style.display = 'none';
            
            try {
                const email = localPart + '@' + enforcedDomain;
                const result = await self.provisionEmailAccount(email, password, category);
                
                if (result.success) {
                    closeModal();
                    
                    // Show success message with credentials
                    const credsMsg = result.password 
                        ? `\n\n📋 **Your login credentials:**\n• Email: ${email}\n• Password: \`${result.password}\`\n\n⚠️ Save this password now - it won't be shown again!`
                        : '';
                    
                    self.addMessageToUI('ai', `✅ **Email account created successfully!**\n\n📧 **${email}** is now ready to use for ${categoryLabel}.${credsMsg}\n\n🌐 **Webmail:** ${result.smtp_settings?.webmail || 'Check your hosting panel'}`);
                    
                    if (onSuccess) onSuccess(result);
                } else {
                    errorDiv.querySelector('p').textContent = result.message || 'Failed to create email';
                    errorDiv.style.display = 'block';
                    btn.textContent = '✨ Create Email Account';
                    btn.disabled = false;
                }
            } catch (err) {
                errorDiv.querySelector('p').textContent = 'Error: ' + err.message;
                errorDiv.style.display = 'block';
                btn.textContent = '✨ Create Email Account';
                btn.disabled = false;
            }
        });
    }
    
    // Test and save panel credentials
    async testAndSavePanelCredentials(panelType, panelUrl, username, token) {
        const AX = this.getAjax();
        
        const response = await fetch(AX.url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action: 'vmo_save_panel_credentials',
                security: AX.nonce,
                panel_type: panelType,
                panel_url: panelUrl,
                username: username,
                api_token: token
            })
        });
        
        const data = await response.json();
        return data.success ? { success: true, ...data.data } : { success: false, message: data.data?.message || 'Failed to save credentials' };
    }
    
    // Provision email account via control panel
    async provisionEmailAccount(email, password, category) {
        const AX = this.getAjax();
        
        const response = await fetch(AX.url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action: 'vmo_provision_email_account',
                security: AX.nonce,
                email: email,
                password: password,
                category: category
            })
        });
        
        const data = await response.json();
        return data.success ? { success: true, ...data.data } : { success: false, message: data.data?.message || 'Provisioning failed' };
    }
    
    // Ask Jarvis AI about email setup
    askJarvisAboutEmail(category, domain, hostName) {
        const prompt = hostName 
            ? `I need to set up a ${category} email address for my business domain ${domain}. My website is hosted on ${hostName}, which doesn't include email hosting. What are my best options and how do I set them up step by step?`
            : `I need to set up a ${category} email address for my business domain ${domain}. What are the best email hosting options and how do I connect them to this plugin?`;
        
        // Add the question to the chat and trigger AI response
        this.addMessageToUI('user', prompt);
        this.handleUserMessage(prompt);
    }
    
    // Render manual email setup modal (existing provider-based setup)
    renderManualEmailSetupModal(modal, category, categoryLabel, enforcedDomain, closeModal, onSuccess) {
        modal.innerHTML = `
            <div class="vmo-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.3);
                border-radius: 16px; max-width: 520px; width: 95%;
                max-height: 90vh; overflow-y: auto;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px; display: flex; align-items: center; gap: 8px;">
                                <span style="font-size: 22px;">📧</span> Connect Email Account
                            </h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">
                                ${categoryLabel} — @${enforcedDomain}
                            </p>
                        </div>
                        <button id="vmo-email-modal-close" style="
                            background: transparent; border: none; color: #94a3b8;
                            font-size: 24px; cursor: pointer; padding: 4px;
                        ">&times;</button>
                    </div>
                    
                    <!-- Info Notice -->
                    <div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; padding: 12px; margin-bottom: 16px;">
                        <p style="margin: 0; color: #93c5fd; font-size: 12px; line-height: 1.5;">
                            Connect an existing email account to enable sending emails from your business. 
                            Your credentials are encrypted and stored securely.
                        </p>
                    </div>
                    
                    <!-- Provider Selection -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            Email Provider
                        </label>
                        <select id="vmo-email-provider-select" style="
                            width: 100%; padding: 10px 12px; border-radius: 8px;
                            background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                            font-size: 14px; cursor: pointer;
                        ">
                            <option value="hosting">My Web Hosting (cPanel/Plesk)</option>
                            <option value="gmail">Gmail / Google Workspace</option>
                            <option value="outlook">Outlook / Microsoft 365</option>
                            <option value="yahoo">Yahoo Mail</option>
                            <option value="zoho">Zoho Mail</option>
                            <option value="custom">Custom / Other Provider</option>
                        </select>
                        <p id="vmo-provider-note" style="margin: 6px 0 0 0; color: #94a3b8; font-size: 11px;"></p>
                    </div>
                    
                    <!-- Email Address -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            Email Address
                        </label>
                        <input type="email" id="vmo-email-address" placeholder="${category}@${enforcedDomain}" style="
                            width: 100%; padding: 10px 12px; border-radius: 8px;
                            background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                            font-size: 14px; box-sizing: border-box;
                        ">
                        <p id="vmo-email-error" style="margin: 6px 0 0 0; color: #f87171; font-size: 11px; display: none;"></p>
                    </div>
                    
                    <!-- Display Name -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            Display Name <span style="color: #64748b; font-weight: 400;">(optional)</span>
                        </label>
                        <input type="text" id="vmo-email-display-name" placeholder="Your Business Name" style="
                            width: 100%; padding: 10px 12px; border-radius: 8px;
                            background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                            font-size: 14px; box-sizing: border-box;
                        ">
                    </div>
                    
                    <!-- Password -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            Password / App Password
                        </label>
                        <div style="position: relative;">
                            <input type="password" id="vmo-email-password" placeholder="Enter password" style="
                                width: 100%; padding: 10px 40px 10px 12px; border-radius: 8px;
                                background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                font-size: 14px; box-sizing: border-box;
                            ">
                            <button type="button" id="vmo-toggle-password" style="
                                position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
                                background: transparent; border: none; color: #64748b;
                                cursor: pointer; font-size: 16px;
                            ">👁️</button>
                        </div>
                    </div>
                    
                    <!-- Advanced Settings (hidden unless needed) -->
                    <div style="margin: 10px 0 14px 0; text-align: center;">
                        <button type="button" id="vmo-show-advanced" style="background: transparent; border: none; color: #94a3b8; font-size: 12px; cursor: pointer; text-decoration: underline;">
                            Having trouble? Show advanced settings
                        </button>
                    </div>

                    <div id="vmo-server-settings" style="display: none; margin-bottom: 16px;">
                        <div style="padding: 12px; background: rgba(0,0,0,0.2); border-radius: 8px;">
                            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
                                <div>
                                    <label style="display: block; color: #94a3b8; font-size: 11px; margin-bottom: 4px;">IMAP Host</label>
                                    <input type="text" id="vmo-imap-host" style="
                                        width: 100%; padding: 8px; border-radius: 6px;
                                        background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                        font-size: 12px; box-sizing: border-box;
                                    ">
                                </div>
                                <div>
                                    <label style="display: block; color: #94a3b8; font-size: 11px; margin-bottom: 4px;">IMAP Port</label>
                                    <input type="number" id="vmo-imap-port" value="993" style="
                                        width: 100%; padding: 8px; border-radius: 6px;
                                        background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                        font-size: 12px; box-sizing: border-box;
                                    ">
                                </div>
                            </div>
                            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
                                <div>
                                    <label style="display: block; color: #94a3b8; font-size: 11px; margin-bottom: 4px;">SMTP Host</label>
                                    <input type="text" id="vmo-smtp-host" style="
                                        width: 100%; padding: 8px; border-radius: 6px;
                                        background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                        font-size: 12px; box-sizing: border-box;
                                    ">
                                </div>
                                <div>
                                    <label style="display: block; color: #94a3b8; font-size: 11px; margin-bottom: 4px;">SMTP Port</label>
                                    <input type="number" id="vmo-smtp-port" value="587" style="
                                        width: 100%; padding: 8px; border-radius: 6px;
                                        background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                                        font-size: 12px; box-sizing: border-box;
                                    ">
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 12px; margin-top: 20px;">
                        <button id="vmo-email-cancel" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: #374151; border: none; color: #d1d5db;
                            font-size: 14px; font-weight: 500; cursor: pointer;
                        ">Cancel</button>
                        <button id="vmo-email-save" style="
                            flex: 2; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                            border: none; color: white;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                        ">✅ Test & Save</button>
                    </div>
                    
                    <!-- Jarvis Help Option -->
                    <div style="text-align: center; margin-top: 16px; padding-top: 16px; border-top: 1px solid #334155;">
                        <button id="vmo-ask-jarvis-help" style="
                            background: transparent; border: none; color: #8b5cf6;
                            font-size: 13px; cursor: pointer;
                        ">🤖 Need help? Ask Jarvis AI</button>
                    </div>
                </div>
            </div>
        `;
        
        // Setup event handlers
        const self = this;
        const providerSelect = document.getElementById('vmo-email-provider-select');
        const providerNote = document.getElementById('vmo-provider-note');
        const imapHost = document.getElementById('vmo-imap-host');
        const imapPort = document.getElementById('vmo-imap-port');
        const smtpHost = document.getElementById('vmo-smtp-host');
        const smtpPort = document.getElementById('vmo-smtp-port');
        const serverSettings = document.getElementById('vmo-server-settings');
        const emailInput = document.getElementById('vmo-email-address');
        const emailError = document.getElementById('vmo-email-error');
        const showAdvancedBtn = document.getElementById('vmo-show-advanced');

        const revealAdvanced = () => {
            if (serverSettings) serverSettings.style.display = 'block';
            if (showAdvancedBtn) showAdvancedBtn.style.display = 'none';
        };
        showAdvancedBtn?.addEventListener('click', revealAdvanced);
        
        const updateProviderSettings = () => {
            const preset = this.getEmailProviderPreset(providerSelect.value);
            providerNote.textContent = preset.note || '';
            const shouldUseDomainMailHost = !!preset.hostingBased && (preset.imap.host === 'mail.' || preset.smtp.host === 'mail.');
            const inferredMailHost = enforcedDomain ? `mail.${enforcedDomain}` : '';

            imapHost.value = shouldUseDomainMailHost ? inferredMailHost : preset.imap.host;
            imapPort.value = preset.imap.port;
            smtpHost.value = shouldUseDomainMailHost ? inferredMailHost : preset.smtp.host;
            smtpPort.value = preset.smtp.port;
            
            if (providerSelect.value === 'custom') {
                revealAdvanced();
            }
        };
        
        const validateEmail = () => {
            const email = emailInput.value.trim();
            const validation = this.validateEmailDomain(email);
            if (!validation.valid) {
                emailError.textContent = validation.error;
                emailError.style.display = 'block';
                return false;
            }
            emailError.style.display = 'none';
            return true;
        };
        
        providerSelect.addEventListener('change', updateProviderSettings);
        emailInput.addEventListener('blur', validateEmail);
        updateProviderSettings();
        
        // Toggle password visibility
        document.getElementById('vmo-toggle-password').addEventListener('click', () => {
            const pwd = document.getElementById('vmo-email-password');
            pwd.type = pwd.type === 'password' ? 'text' : 'password';
        });
        
        // Close handlers
        document.getElementById('vmo-email-modal-close').addEventListener('click', closeModal);
        document.getElementById('vmo-email-cancel').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
        
        // Jarvis help
        document.getElementById('vmo-ask-jarvis-help').addEventListener('click', () => {
            closeModal();
            this.askJarvisAboutEmail(category, enforcedDomain, null);
        });
        
        // Save handler
        document.getElementById('vmo-email-save').addEventListener('click', async () => {
            if (!validateEmail()) return;
            
            const email = emailInput.value.trim();
            const password = document.getElementById('vmo-email-password').value;
            const displayName = document.getElementById('vmo-email-display-name').value.trim();
            
            if (!password) {
                emailError.textContent = 'Password is required';
                emailError.style.display = 'block';
                return;
            }
            
            const saveBtn = document.getElementById('vmo-email-save');
            saveBtn.textContent = '⏳ Testing...';
            saveBtn.disabled = true;
            
            try {
                const result = await this.saveEmailCredentials(category, {
                    email,
                    password,
                    display_name: displayName,
                    provider_preset: providerSelect.value,
                    imap_host: imapHost.value,
                    imap_port: parseInt(imapPort.value) || 993,
                    smtp_host: smtpHost.value,
                    smtp_port: parseInt(smtpPort.value) || 587
                });
                
                if (result.success) {
                    closeModal();
                    if (onSuccess) onSuccess(result);
                    this.addMessageToUI('ai', `✅ Email account set up successfully for ${categoryLabel}! Your ${email} is now configured.`);
                } else {
                    if (result.needs_advanced) {
                        revealAdvanced();
                    }
                    emailError.textContent = result.error || 'Failed to save credentials';
                    emailError.style.display = 'block';
                }
            } catch (err) {
                emailError.textContent = 'Connection error. Please try again.';
                emailError.style.display = 'block';
            } finally {
                saveBtn.textContent = '✅ Test & Save';
                saveBtn.disabled = false;
            }
        });
        
        // Focus email input
        emailInput.focus();
    }
    
    // Save email credentials securely to server
    async saveEmailCredentials(category, config) {
        const ajaxUrl = (typeof vmoAjax !== 'undefined' ? vmoAjax.ajaxurl : vmo_ajax?.ajaxurl) || '/wp-admin/admin-ajax.php';
        const nonce = (typeof vmoAjax !== 'undefined' ? vmoAjax.nonce : vmo_ajax?.nonce) || '';
        
        try {
            // Test + save to server (novice-friendly)
            const response = await fetch(ajaxUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_test_and_save_email_credentials',
                    nonce: nonce,
                    session_id: this.sessionId,
                    category: category,
                    email: config.email,
                    password: config.password, // Will be encrypted server-side
                    display_name: config.display_name || '',
                    provider_preset: config.provider_preset,
                    imap_host: config.imap_host,
                    imap_port: config.imap_port,
                    smtp_host: config.smtp_host,
                    smtp_port: config.smtp_port
                })
            });
            
            const data = await response.json();
            if (!data.success) {
                return {
                    success: false,
                    error: data.data?.message || data.message || 'Failed to save credentials',
                    needs_advanced: !!data.data?.needs_advanced
                };
            }

            // Add to local state only after backend accepted
            const localResult = this.addEmailAccount(category, config);
            if (!localResult.success) {
                return localResult;
            }
            // Mark verified state in local metadata when available
            if (this.state?.email_roles_meta?.email_accounts?.[category]) {
                this.state.email_roles_meta.email_accounts[category].verified = (data.data?.verified === true);
                this.scheduleStateSave();
            }

            return { success: true, verified: data.data?.verified };
        } catch (err) {
            console.error('Email credential save error:', err);
            return { success: false, error: 'Connection error' };
        }
    }
    
    // Show bulk email provider setup modal
    showBulkEmailSetupModal(onSuccess = null) {
        const existingModal = document.getElementById('vmo-bulk-email-modal');
        if (existingModal) existingModal.remove();
        
        const modal = document.createElement('div');
        modal.id = 'vmo-bulk-email-modal';
        modal.className = 'vmo-modal-overlay';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0, 0, 0, 0.85); z-index: 100000;
            display: flex; align-items: center; justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        `;
        this.forceModalOnTop(modal, 3000005);
        
        modal.innerHTML = `
            <div class="vmo-bulk-email-modal-content" style="
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(147, 51, 234, 0.3);
                border-radius: 16px; max-width: 480px; width: 95%;
                box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            ">
                <div style="padding: 24px;">
                    <!-- Header -->
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px;">
                        <div>
                            <h3 style="margin: 0; color: #f1f5f9; font-size: 18px; display: flex; align-items: center; gap: 8px;">
                                <span style="font-size: 22px;">📬</span> Bulk Email Marketing
                            </h3>
                            <p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 13px;">
                                Connect your email marketing provider
                            </p>
                        </div>
                        <button id="vmo-bulk-modal-close" style="
                            background: transparent; border: none; color: #94a3b8;
                            font-size: 24px; cursor: pointer; padding: 4px;
                        ">&times;</button>
                    </div>
                    
                    <!-- Provider Selection -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            Email Marketing Provider
                        </label>
                        <select id="vmo-bulk-provider-select" style="
                            width: 100%; padding: 10px 12px; border-radius: 8px;
                            background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                            font-size: 14px; cursor: pointer;
                        ">
                            <option value="brevo">Brevo (formerly Sendinblue)</option>
                            <option value="mailerlite">MailerLite</option>
                        </select>
                    </div>
                    
                    <!-- API Key -->
                    <div style="margin-bottom: 16px;">
                        <label style="display: block; color: #e2e8f0; font-size: 13px; font-weight: 500; margin-bottom: 6px;">
                            API Key
                        </label>
                        <input type="password" id="vmo-bulk-api-key" placeholder="Enter your API key" style="
                            width: 100%; padding: 10px 12px; border-radius: 8px;
                            background: #1e293b; border: 1px solid #334155; color: #f1f5f9;
                            font-size: 14px; box-sizing: border-box;
                        ">
                        <p id="vmo-bulk-api-help" style="margin: 6px 0 0 0; color: #94a3b8; font-size: 11px;"></p>
                    </div>
                    
                    <p id="vmo-bulk-error" style="margin: 0 0 12px 0; color: #f87171; font-size: 12px; display: none;"></p>
                    
                    <!-- Action Buttons -->
                    <div style="display: flex; gap: 12px;">
                        <button id="vmo-bulk-cancel" style="
                            flex: 1; padding: 12px; border-radius: 8px;
                            background: #374151; border: none; color: #d1d5db;
                            font-size: 14px; font-weight: 500; cursor: pointer;
                        ">Cancel</button>
                        <button id="vmo-bulk-save" style="
                            flex: 2; padding: 12px; border-radius: 8px;
                            background: linear-gradient(135deg, #9333ea 0%, #7c3aed 100%);
                            border: none; color: white;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                        ">Connect Provider</button>
                    </div>
                </div>
            </div>
        `;
        
        document.body.appendChild(modal);
        this.forceModalOnTop(modal, 3000005);
        
        const closeModal = () => modal.remove();
        const providerSelect = document.getElementById('vmo-bulk-provider-select');
        const apiHelp = document.getElementById('vmo-bulk-api-help');
        const errorEl = document.getElementById('vmo-bulk-error');
        
        const updateHelp = () => {
            const provider = VMO_BULK_EMAIL_PROVIDERS[providerSelect.value];
            apiHelp.textContent = provider?.apiKeyHelp || '';
        };
        
        providerSelect.addEventListener('change', updateHelp);
        updateHelp();
        
        document.getElementById('vmo-bulk-modal-close').addEventListener('click', closeModal);
        document.getElementById('vmo-bulk-cancel').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => {
            if (e.target === modal) closeModal();
        });
        
        document.getElementById('vmo-bulk-save').addEventListener('click', async () => {
            const apiKey = document.getElementById('vmo-bulk-api-key').value.trim();
            const providerKey = providerSelect.value;
            
            if (!apiKey) {
                errorEl.textContent = 'API key is required';
                errorEl.style.display = 'block';
                return;
            }
            
            const saveBtn = document.getElementById('vmo-bulk-save');
            saveBtn.textContent = '⏳ Connecting...';
            saveBtn.disabled = true;
            
            try {
                const result = await this.saveAndVerifyBulkEmailProvider(providerKey, apiKey);
                if (result.success) {
                    closeModal();
                    if (onSuccess) onSuccess(result);
                    this.addMessageToUI('ai', `✅ Successfully connected to ${result.provider.name}! You can now create email marketing campaigns.`);
                } else {
                    errorEl.textContent = result.error;
                    errorEl.style.display = 'block';
                }
            } finally {
                saveBtn.textContent = 'Connect Provider';
                saveBtn.disabled = false;
            }
        });
    }

    getProvisioningHostFallback() {
        if (typeof window === 'undefined') {
            return 'example.com';
        }
        const host = (window.vmoProvisioningDomain || window.location?.hostname || '').toLowerCase().replace(/^https?:\/\//, '');
        const cleanHost = host ? host.replace(/^www\./, '') : 'example.com';

        // Detect common staging/dev environments
        const stagingPatterns = [
            'kinsta.cloud', 'wpengine.com', 'flywheel.com', 'pantheonsite.io', 
            'azurewebsites.net', 'herokuapp.com', 'vercel.app', 'netlify.app', 
            'ngrok.io', 'localhost', '127.0.0.1', 'test', 'staging', 'dev'
        ];

        if (stagingPatterns.some(pattern => cleanHost.includes(pattern))) {
            return null; // Signal that we need to ask the user for their real domain
        }

        return cleanHost;
    }

    getProvisioningDomain() {
        this.ensureProvisioningContainers();
        const domain = this.state.email_roles_meta.locked_domain || this.getProvisioningHostFallback();
        return domain ? domain.replace(/^www\./, '') : this.getProvisioningHostFallback();
    }

    ensureProvisioningContainers() {
        if (!this.state) {
            this.state = this.buildInitialState();
        }
        if (!this.state.email_roles || typeof this.state.email_roles !== 'object') {
            this.state.email_roles = this.buildDefaultEmailRoleMap();
        } else {
            this.roleMailboxes.forEach(role => {
                if (typeof this.state.email_roles[role] === 'undefined') {
                    this.state.email_roles[role] = null;
                }
            });
        }

        if (!this.state.email_roles_meta || typeof this.state.email_roles_meta !== 'object') {
            this.state.email_roles_meta = this.buildDefaultEmailMeta();
        } else {
            const meta = this.state.email_roles_meta;
            if (!Array.isArray(meta.warnings)) {
                meta.warnings = [];
            }
            if (typeof meta.roles_confirmed === 'undefined') {
                meta.roles_confirmed = false;
            }
            if (typeof meta.roles_confirmed_at === 'undefined') {
                meta.roles_confirmed_at = null;
            }
            if (!meta.email_accounts || typeof meta.email_accounts !== 'object') {
                meta.email_accounts = {};
            }
            if (typeof meta.bulk_email_provider === 'undefined') {
                meta.bulk_email_provider = null;
            }
            if (typeof meta.bulk_email_api_key === 'undefined') {
                meta.bulk_email_api_key = null;
            }
            if (typeof meta.bulk_email_verified === 'undefined') {
                meta.bulk_email_verified = false;
            }
            if (typeof meta.phase6_skip_approved === 'undefined') {
                meta.phase6_skip_approved = false;
            }
            if (!meta.alias_preferences || typeof meta.alias_preferences !== 'object') {
                meta.alias_preferences = {};
            }
            if (typeof meta.provisioning_mode !== 'string' || !meta.provisioning_mode) {
                meta.provisioning_mode = 'existing_mailbox';
            }
            if (typeof meta.automation_readiness !== 'string' || !meta.automation_readiness) {
                meta.automation_readiness = 'unknown';
            }
            if (typeof meta.provisioning_consent === 'undefined') {
                meta.provisioning_consent = false;
            }
        }

        return this.state.email_roles_meta;
    }

    bindProvisioningEvents() {
        if (typeof document === 'undefined') {
            return;
        }
        const panel = document.getElementById('vmo-provisioning-panel');
        if (!panel || panel.dataset.vmoProvisioningBound === 'true') {
            if (panel) {
                this.provisioningPanel = panel;
            }
            return;
        }
        this.provisioningPanel = panel;
        panel.dataset.vmoProvisioningBound = 'true';

        const modeInputs = panel.querySelectorAll('input[name="vmo-provisioning-mode"]');
        modeInputs.forEach(input => {
            input.addEventListener('change', (event) => {
                this.handleProvisioningModeChange(event.target.value);
            });
        });

        const consent = panel.querySelector('#vmo-provisioning-consent');
        if (consent) {
            consent.addEventListener('change', () => {
                this.handleProvisioningConsentToggle(!!consent.checked);
            });
        }

        const refreshBtn = document.getElementById('vmo-provisioning-refresh');
        if (refreshBtn) {
            refreshBtn.addEventListener('click', () => this.refreshProvisioningStatusFromServer());
        }
    }

    refreshProvisioningUI() {
        if (typeof document === 'undefined') {
            return;
        }
        const panel = document.getElementById('vmo-provisioning-panel');
        if (!panel) {
            return;
        }
        this.ensureProvisioningContainers();
        if (!panel.dataset.vmoProvisioningBound) {
            this.bindProvisioningEvents();
        }

        const meta = this.state.email_roles_meta;
        const snapshot = meta.provisioning_snapshot || {};
        const domain = this.getProvisioningDomain();
        const statusText = this.formatProvisioningStatus(meta.provisioning_status || snapshot.status || 'not_requested');
        const statusEl = panel.querySelector('[data-provisioning-status]');
        if (statusEl) {
            statusEl.textContent = statusText;
        }
        const statusChip = panel.querySelector('[data-provisioning-status-chip]');
        if (statusChip) {
            statusChip.textContent = statusText;
        }
        const domainEl = panel.querySelector('[data-provisioning-domain]');
        if (domainEl) {
            domainEl.textContent = domain || '—';
        }
        const automation = this.formatAutomationReadiness(meta);
        const automationEl = panel.querySelector('[data-provisioning-automation]');
        if (automationEl) {
            automationEl.textContent = automation.label;
        }
        const automationReasonEl = panel.querySelector('[data-provisioning-automation-reason]');
        if (automationReasonEl) {
            automationReasonEl.textContent = automation.reason || '';
        }
        const updatedEl = panel.querySelector('[data-provisioning-updated]');
        if (updatedEl) {
            const updatedText = this.formatProvisioningTimestamp(snapshot.updated_at || meta.provisioning_snapshot?.updated_at);
            updatedEl.textContent = updatedText;
        }

        const consent = panel.querySelector('#vmo-provisioning-consent');
        if (consent) {
            consent.checked = !!meta.provisioning_consent;
        }
        const modeInputs = panel.querySelectorAll('input[name="vmo-provisioning-mode"]');
        modeInputs.forEach(input => {
            input.checked = input.value === (meta.provisioning_mode || 'existing_mailbox');
        });

        const warningsHost = document.getElementById('vmo-provisioning-warnings');
        if (warningsHost) {
            warningsHost.innerHTML = '';
            if (meta.warnings && meta.warnings.length) {
                meta.warnings.forEach(item => {
                    const warning = document.createElement('div');
                    warning.className = 'vmo-provisioning-warning';
                    warning.textContent = item;
                    warningsHost.appendChild(warning);
                });
                warningsHost.hidden = false;
            } else {
                warningsHost.hidden = true;
            }
        }

        this.renderProvisioningAliasEditor(meta.provisioning_mode || 'existing_mailbox');
        this.renderProvisioningStatusCards(snapshot);
    }

    renderProvisioningAliasEditor(mode) {
        const container = document.getElementById('vmo-provisioning-aliases');
        if (!container) {
            return;
        }
        this.ensureProvisioningContainers();
        const meta = this.state.email_roles_meta;
        const roles = this.state.email_roles;
        const domain = this.getProvisioningDomain();
        container.innerHTML = '';

        this.roleMailboxes.forEach(role => {
            const row = document.createElement('div');
            row.className = 'vmo-provisioning-row';

            const label = document.createElement('div');
            label.className = 'vmo-provisioning-row-label';
            const title = document.createElement('strong');
            title.textContent = VMO_PROVISIONING_ROLE_LABELS[role] || role.toUpperCase();
            const slug = document.createElement('small');
            if (mode === 'auto') {
                const preferred = meta.alias_preferences?.[role] || this.extractLocalPart(roles[role]) || role;
                slug.textContent = this.buildAliasPreview(preferred, domain, role);
            } else {
                const existingEmail = roles[role] || '';
                slug.textContent = existingEmail || `${role}@${domain}`;
            }
            label.appendChild(title);
            label.appendChild(slug);

            const input = document.createElement('input');
            input.dataset.role = role;
            if (mode === 'auto') {
                input.type = 'text';
                input.placeholder = `${role}`;
                input.value = meta.alias_preferences?.[role] || this.extractLocalPart(roles[role]) || '';
                input.addEventListener('change', (event) => this.handleProvisioningAliasInput(role, event.target.value));
            } else {
                input.type = 'email';
                input.placeholder = `${role}@${domain}`;
                input.value = roles[role] || '';
                input.addEventListener('change', (event) => this.handleProvisioningExistingInput(role, event.target.value));
            }

            row.appendChild(label);
            row.appendChild(input);
            container.appendChild(row);
        });

        if (meta.mailbox_summary && typeof meta.mailbox_summary === 'object') {
            const summary = document.createElement('div');
            summary.className = 'vmo-provisioning-mailbox-summary';
            const total = meta.mailbox_summary.total || 0;
            const active = meta.mailbox_summary.active || 0;
            const pending = meta.mailbox_summary.pending || 0;
            summary.textContent = `Mailboxes ready: ${active}/${total} · Pending: ${pending}`;
            container.appendChild(summary);
        }
    }

    renderProvisioningStatusCards(snapshot) {
        const host = document.getElementById('vmo-provisioning-status-cards');
        if (!host) {
            return;
        }
        host.innerHTML = '';
        const steps = snapshot?.steps;
        if (!steps || typeof steps !== 'object') {
            const empty = document.createElement('div');
            empty.className = 'vmo-provisioning-status-empty';
            empty.textContent = 'Provisioning steps will appear here once a request is in progress.';
            host.appendChild(empty);
            return;
        }
        Object.keys(steps).forEach(key => {
            const step = steps[key];
            if (!step) {
                return;
            }
            const card = document.createElement('article');
            const safeStatus = (step.status || 'pending').toLowerCase().replace(/[^a-z0-9_-]/g, '');
            card.className = `vmo-provisioning-status-card vmo-provisioning-status-${safeStatus}`;

            const header = document.createElement('header');
            const label = document.createElement('span');
            label.className = 'vmo-status-card-label';
            label.textContent = step.label || key;
            const status = document.createElement('span');
            status.className = 'vmo-status-card-state';
            status.textContent = this.formatProvisioningStatus(step.status || 'pending');
            header.appendChild(label);
            header.appendChild(status);

            const body = document.createElement('p');
            body.textContent = step.message || '';

            card.appendChild(header);
            card.appendChild(body);

            if (step.counts && typeof step.counts === 'object') {
                const counts = document.createElement('div');
                counts.className = 'vmo-status-card-counts';
                counts.textContent = `Total: ${step.counts.total || 0} · Active: ${step.counts.active || 0} · Pending: ${step.counts.pending || 0}`;
                card.appendChild(counts);
            }

            if (step.domain) {
                const detail = document.createElement('small');
                detail.textContent = `Domain: ${step.domain}`;
                card.appendChild(detail);
            }

            host.appendChild(card);
        });
    }

    formatProvisioningStatus(status) {
        const normalized = (status || '').toString().toLowerCase();
        const labels = {
            queued: 'Queued',
            pending_dns: 'Pending DNS',
            verifying: 'Verifying',
            active: 'Active',
            failed: 'Failed',
            suspended: 'Suspended',
            awaiting_credentials: 'Needs Credentials',
            not_requested: 'Not Requested',
            manual_dns: 'Manual DNS',
            existing_mailbox: 'Existing Mailboxes'
        };
        return labels[normalized] || normalized.replace(/_/g, ' ').replace(/^\w/, (m) => m.toUpperCase()) || 'Unknown';
    }

    formatAutomationReadiness(meta) {
        const readiness = (meta.automation_readiness || 'unknown').toLowerCase();
        const result = { label: 'Unknown', reason: meta.automation_reason || '' };
        if (readiness === 'auto_ready') {
            result.label = 'Auto Ready';
            result.reason = meta.automation_reason || 'Automation is disabled. Continue with manual mailbox setup.';
        } else if (readiness === 'manual_only') {
            result.label = 'Manual Only';
            result.reason = meta.automation_reason || 'DNS host not supported for automation; provide existing inboxes.';
        } else {
            result.label = 'Detecting…';
            result.reason = meta.automation_reason || '';
        }
        return result;
    }

    formatProvisioningTimestamp(value) {
        if (!value) {
            return '';
        }
        const date = new Date(value);
        if (Number.isNaN(date.getTime())) {
            return '';
        }
        return `Updated ${date.toLocaleString()}`;
    }

    handleProvisioningModeChange(mode) {
        this.ensureProvisioningContainers();
        const normalized = mode === 'auto' ? 'auto' : 'existing_mailbox';
        if (this.state.email_roles_meta.provisioning_mode === normalized) {
            return;
        }
        this.state.email_roles_meta.provisioning_mode = normalized;
        this.refreshProvisioningUI();
        this.scheduleProvisioningSave();
    }

    handleProvisioningConsentToggle(consent) {
        this.ensureProvisioningContainers();
        this.state.email_roles_meta.provisioning_consent = !!consent;
        this.scheduleProvisioningSave();
    }

    handleProvisioningAliasInput(role, value) {
        this.ensureProvisioningContainers();
        const meta = this.state.email_roles_meta;
        if (!meta.alias_preferences) {
            meta.alias_preferences = {};
        }
        const sanitized = this.sanitizeAliasLocal(value || '') || role;
        meta.alias_preferences[role] = sanitized;
        this.state.email_roles[role] = this.buildAliasPreview(sanitized, this.getProvisioningDomain(), role);
        this.refreshProvisioningUI();
        this.scheduleProvisioningSave();
    }

    handleProvisioningExistingInput(role, value) {
        this.ensureProvisioningContainers();
        const trimmed = (value || '').trim();
        this.state.email_roles[role] = trimmed || null;
        this.scheduleProvisioningSave();
    }

    sanitizeAliasLocal(value) {
        return (value || '').toLowerCase().replace(/[^a-z0-9._-]+/g, '').slice(0, 64);
    }

    extractLocalPart(email) {
        if (!email || typeof email !== 'string' || !email.includes('@')) {
            return '';
        }
        const [local] = email.split('@');
        return local || '';
    }

    buildAliasPreview(local, domain, fallbackKey = 'team') {
        const sanitized = this.sanitizeAliasLocal(local);
        const fallback = this.sanitizeAliasLocal(fallbackKey);
        return `${sanitized || fallback || 'team'}@${domain}`;
    }

    syncProvisioningStateToConsultationData() {
        this.ensureProvisioningContainers();
        this.consultation_data = this.consultation_data || {};
        this.consultation_data.extracted_information = this.consultation_data.extracted_information || {};

        const emailRoles = this.deepMerge({}, this.state.email_roles || this.buildDefaultEmailRoleMap());
        const emailMeta = this.deepMerge({}, this.state.email_roles_meta || this.buildDefaultEmailMeta());

        this.consultation_data.email_roles = emailRoles;
        this.consultation_data.email_roles_meta = emailMeta;
        this.consultation_data.extracted_information.email_preferences = {
            ...(this.consultation_data.extracted_information.email_preferences || {}),
            roles: emailRoles,
            email_roles: emailRoles,
            roles_confirmed: !!emailMeta.roles_confirmed,
            phase6_skip_approved: !!emailMeta.phase6_skip_approved,
            domain: emailMeta.locked_domain || this.getEnforcedDomain() || this.getProvisioningHostFallback()
        };
        this.collectedInformation = this.consultation_data.extracted_information;
    }

    scheduleStateSave(options = {}) {
        this.scheduleProvisioningSave(options);
    }

    scheduleProvisioningSave(options = {}) {
        const immediate = !!options.immediate;
        this.syncProvisioningStateToConsultationData();
        if (this.provisioningSaveTimer) {
            clearTimeout(this.provisioningSaveTimer);
            this.provisioningSaveTimer = null;
        }
        if (immediate) {
            this.persistProvisioningSettings();
            return;
        }
        this.provisioningSaveTimer = setTimeout(() => this.persistProvisioningSettings(), 700);
    }

    async persistProvisioningSettings() {
        if (this.provisioningSaveInFlight) {
            this.provisioningSavePending = true;
            return;
        }
        this.ensureProvisioningContainers();
        const AX = this.getAjax();
        const payload = {
            email_roles: this.state.email_roles,
            email_roles_meta: this.state.email_roles_meta
        };
        this.provisioningSaveInFlight = true;
        this.provisioningSavePending = false;
        try {
            const res = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_update_consultation_context',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    session_id: this.sessionId,
                    context_action: 'update_email_provisioning',
                    payload: JSON.stringify(payload)
                })
            });
            const data = await res.json();
            if (data?.success && data.data?.context) {
                this.mergeConsultationState(data.data.context);
            }
        } catch (err) {
            console.warn('Unable to persist provisioning settings', err);
        } finally {
            this.provisioningSaveInFlight = false;
            if (this.provisioningSavePending) {
                this.provisioningSavePending = false;
                this.scheduleProvisioningSave({ immediate: true });
            }
        }
    }

    applyProvisioningSnapshot(snapshot) {
        if (!snapshot || typeof snapshot !== 'object') {
            return;
        }
        this.ensureProvisioningContainers();
        const meta = this.state.email_roles_meta;
        meta.provisioning_snapshot = snapshot;
        meta.provisioning_status = snapshot.status || meta.provisioning_status;
        if (snapshot.dns_host_type) {
            meta.dns_host_type = snapshot.dns_host_type;
        }
        if (snapshot.hosted_zone_id) {
            meta.hosted_zone_id = snapshot.hosted_zone_id;
        }
        if (snapshot.mailbox_summary) {
            meta.mailbox_summary = snapshot.mailbox_summary;
        }
        if (snapshot.steps) {
            meta.provisioning_steps = snapshot.steps;
        }
        if (snapshot.metadata?.dns_records?.mode) {
            meta.dns_records_mode = snapshot.metadata.dns_records.mode;
        }
        if (snapshot.manual_dns_confirmed_at) {
            meta.manual_dns_confirmed_at = snapshot.manual_dns_confirmed_at;
        }
        this.refreshProvisioningUI();
    }

    async refreshProvisioningStatusFromServer() {
        if (this.provisioningRefreshing) {
            return;
        }
        this.provisioningRefreshing = true;
        const refreshBtn = document.getElementById('vmo-provisioning-refresh');
        if (refreshBtn) {
            refreshBtn.disabled = true;
            refreshBtn.textContent = 'Refreshing…';
        }
        try {
            const AX = this.getAjax();
            const res = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_get_provisioning_status',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce
                })
            });
            const data = await res.json();
            if (data?.success && data.data) {
                this.applyProvisioningSnapshot(data.data);
                this.scheduleProvisioningSave({ immediate: true });
                if (window.showToast) {
                    window.showToast('Provisioning status refreshed.', 'success');
                }
            } else if (window.showToast) {
                const msg = data?.data?.message || data?.message || 'Unable to refresh status.';
                window.showToast(msg, 'error');
            }
        } catch (err) {
            console.warn('Provisioning status refresh failed', err);
            if (window.showToast) {
                window.showToast('Unable to refresh status right now.', 'error');
            }
        } finally {
            this.provisioningRefreshing = false;
            if (refreshBtn) {
                refreshBtn.disabled = false;
                refreshBtn.textContent = '🔄 Refresh Status';
            }
        }
    }

    normalizeDocumentUpload(record = {}) {
        if (!record || (typeof record !== 'object')) {
            return null;
        }
        const url = record.url || record.link || '';
        const filename = record.filename || record.original_name || record.title || '';
        const fallbackId = record.id || record.hash || record.uuid || filename || url || null;
        const normalized = {
            id: fallbackId || `doc_${Math.random().toString(36).slice(2)}`,
            title: record.title || filename || 'Uploaded Document',
            filename,
            url,
            mime: record.mime || record.mime_type || record.type || '',
            length: Number(record.length || record.size || 0) || 0,
            chunks: Array.isArray(record.chunks) ? record.chunks : [],
            saved_at: record.saved_at || record.ingested_at || record.created_at || new Date().toISOString()
        };
        if (!normalized.url && !normalized.filename) {
            return null;
        }
        return normalized;
    }

    collectDocumentUploads() {
        const uploads = [];
        const seen = new Set();
        const push = (record) => {
            const normalized = this.normalizeDocumentUpload(record);
            if (!normalized) return;
            const sig = `${normalized.id || ''}|${normalized.url || ''}|${normalized.filename || ''}`;
            if (seen.has(sig)) return;
            seen.add(sig);
            uploads.push(normalized);
        };
        const store = this.state?.document_store;
        if (store && Array.isArray(store.uploads)) {
            store.uploads.forEach(push);
        }
        const docs = this.consultation_data?.documents;
        if (Array.isArray(docs)) {
            docs.forEach(push);
        }
        return uploads;
    }

    buildDocumentStorePayload(uploadsOverride = null) {
        const store = this.ensureDocumentStoreContainer();
        const uploads = uploadsOverride || this.collectDocumentUploads();
        if (!store.blueprint_raw && this.blueprintRaw) {
            store.blueprint_raw = this.blueprintRaw;
        }
        if (!store.blueprint_summary && this.blueprintSummary) {
            store.blueprint_summary = this.blueprintSummary;
        }
        return {
            blueprint_raw: store.blueprint_raw || this.blueprintRaw || '',
            blueprint_summary: store.blueprint_summary || this.blueprintSummary || '',
            uploads
        };
    }

    upsertDocumentUpload(record, options = {}) {
        const normalized = this.normalizeDocumentUpload(record);
        if (!normalized) return null;
        if (!Array.isArray(this.consultation_data.documents)) {
            this.consultation_data.documents = [];
        }
        const docIdx = this.consultation_data.documents.findIndex(doc => (doc.id && normalized.id && doc.id === normalized.id) || (doc.url && normalized.url && doc.url === normalized.url));
        if (docIdx >= 0) {
            this.consultation_data.documents[docIdx] = { ...this.consultation_data.documents[docIdx], ...normalized };
        } else {
            this.consultation_data.documents.push(normalized);
        }
        if (!Array.isArray(this.collectedInformation.uploadedFiles)) {
            this.collectedInformation.uploadedFiles = [];
        }
        const docLegacy = {
            id: normalized.id,
            url: normalized.url,
            filename: normalized.filename,
            type: normalized.mime,
            title: normalized.title
        };
        const docLegacyIdx = this.collectedInformation.uploadedFiles.findIndex(item => item.url === docLegacy.url);
        if (docLegacyIdx >= 0) {
            this.collectedInformation.uploadedFiles[docLegacyIdx] = { ...this.collectedInformation.uploadedFiles[docLegacyIdx], ...docLegacy };
        } else {
            this.collectedInformation.uploadedFiles.push(docLegacy);
        }
        const store = this.ensureDocumentStoreContainer();
        const signature = (doc) => `${doc.id || ''}|${doc.url || ''}|${doc.filename || ''}`;
        let updated = false;
        store.uploads = store.uploads || [];
        for (let i = 0; i < store.uploads.length; i++) {
            const existing = store.uploads[i];
            if (signature(existing) === signature(normalized) || (existing.url && normalized.url && existing.url === normalized.url)) {
                store.uploads[i] = { ...existing, ...normalized };
                updated = true;
                break;
            }
        }
        if (!updated) {
            store.uploads.push(normalized);
        }
        this.state.document_store = store;
        const shouldPersist = options.persist !== false;
        if (shouldPersist && window.vmo_consultant_context_v1) {
            this.saveConsultationData({
                documents: this.consultation_data.documents,
                document_store: this.buildDocumentStorePayload(store.uploads)
            });
        }
        this.refreshInsightsUI();
        return normalized;
    }

    isVisualMedia(fileInfo = {}) {
        const type = (fileInfo.type || fileInfo.mime || '').toLowerCase();
        const filename = (fileInfo.filename || fileInfo.title || '').toLowerCase();
        const imageExt = /(\.png|\.jpe?g|\.gif|\.bmp|\.webp|\.svg|\.heic)$/;
        const videoExt = /(\.mp4|\.mov|\.webm|\.avi|\.mkv)$/;
        const audioExt = /(\.mp3|\.wav|\.m4a|\.aac|\.flac)$/;
        if (type.startsWith('image') || type.startsWith('video') || type.startsWith('audio')) return true;
        if (imageExt.test(filename) || videoExt.test(filename) || audioExt.test(filename)) return true;
        return false;
    }

    upsertMediaRecord(fileInfo = {}, options = {}) {
        if (!fileInfo || !fileInfo.url) {
            return null;
        }
        const normalized = {
            id: fileInfo.id || `media_${Math.random().toString(36).slice(2)}`,
            url: fileInfo.url,
            title: fileInfo.title || fileInfo.filename || 'Uploaded Media',
            filename: fileInfo.filename || fileInfo.title || '',
            mime: (fileInfo.type || fileInfo.mime || '').toLowerCase(),
            role: options.role || fileInfo.role || null,
            meta: fileInfo.meta || options.meta || {},
            uploaded_at: fileInfo.uploaded_at || new Date().toISOString()
        };
        if (!Array.isArray(this.consultation_data.media_files)) {
            this.consultation_data.media_files = [];
        }
        const mediaIdx = this.consultation_data.media_files.findIndex(item => (item.id && normalized.id && item.id === normalized.id) || (item.url && normalized.url && item.url === normalized.url));
        if (mediaIdx >= 0) {
            this.consultation_data.media_files[mediaIdx] = { ...this.consultation_data.media_files[mediaIdx], ...normalized };
        } else {
            this.consultation_data.media_files.push(normalized);
        }
        if (!Array.isArray(this.collectedInformation.uploadedFiles)) {
            this.collectedInformation.uploadedFiles = [];
        }
        const legacyIdx = this.collectedInformation.uploadedFiles.findIndex(item => item.url === normalized.url);
        const legacyEntry = {
            id: normalized.id,
            url: normalized.url,
            filename: normalized.filename,
            type: normalized.mime,
            title: normalized.title
        };
        if (legacyIdx >= 0) {
            this.collectedInformation.uploadedFiles[legacyIdx] = { ...this.collectedInformation.uploadedFiles[legacyIdx], ...legacyEntry };
        } else {
            this.collectedInformation.uploadedFiles.push(legacyEntry);
        }
        if (normalized.role) {
            if (!Array.isArray(this.collectedInformation.mediaRoles)) {
                this.collectedInformation.mediaRoles = [];
            }
            this.collectedInformation.mediaRoles.push({
                url: normalized.url,
                role: normalized.role,
                title: normalized.title,
                type: normalized.mime
            });
        }
        this.refreshInsightsUI();
        return normalized;
    }

    registerUploadedAsset(fileInfo = {}, options = {}) {
        if (!fileInfo || !fileInfo.url) {
            return { kind: 'unknown', record: null };
        }
        const isMedia = this.isVisualMedia(fileInfo);
        if (isMedia) {
            const record = this.upsertMediaRecord(fileInfo, options);
            return { kind: 'media', record };
        }
        const record = this.upsertDocumentUpload({
            id: fileInfo.id,
            url: fileInfo.url,
            filename: fileInfo.filename,
            title: fileInfo.title,
            mime: fileInfo.type || fileInfo.mime,
            length: fileInfo.length || fileInfo.size || 0
        }, { persist: options.persist !== false });
        return { kind: 'document', record };
    }

    hydrateMediaCollections(mediaFiles) {
        if (!Array.isArray(mediaFiles)) {
            return;
        }
        this.consultation_data.media_files = mediaFiles.slice();
        if (!Array.isArray(this.collectedInformation.uploadedFiles)) {
            this.collectedInformation.uploadedFiles = [];
        }
        const legacyMap = new Map(this.collectedInformation.uploadedFiles.map(entry => [entry.url, entry]));
        mediaFiles.forEach(file => {
            if (!file || !file.url) return;
            legacyMap.set(file.url, {
                id: file.id,
                url: file.url,
                filename: file.filename || file.title || file.url,
                type: file.mime || file.type || '',
                title: file.title || file.filename || file.url
            });
        });
        this.collectedInformation.uploadedFiles = Array.from(legacyMap.values());
        const roles = [];
        mediaFiles.forEach(file => {
            const fileRoles = Array.isArray(file.roles) ? file.roles : (file.role ? [file.role] : []);
            fileRoles.forEach(role => {
                roles.push({
                    url: file.url,
                    role,
                    title: file.title || file.filename || file.url,
                    type: file.mime || file.type || ''
                });
            });
        });
        this.collectedInformation.mediaRoles = roles;
    }

    refreshAllowedActions() {
        const flowIndex = this.consultation_data?.consultation_flow?.current_index || 0;
        const phaseFlags = this.consultation_data?.phase_flags || {};
        const awaitingSummaryApproval = this.consultation_data?.doc_locked && flowIndex === 1 && !phaseFlags.phase2_confirmed;
        const allowed = new Set(['uploadDocument', 'saveContext', 'ingestMedia']);
        if (true) { // ✅ FIX: Always allow user to chat, or they cant approve phase 2!
            allowed.add('askQuestions');
        }
        if (flowIndex >= 2) {
            allowed.add('triggerResearch');
        }
        if (flowIndex >= 4) {
            allowed.add('generateWebsitePlan');
        }
        if (flowIndex >= 5) {
            allowed.add('configureSystems');
        }
        this.allowedActions = allowed;
    }

    syncAutomationTriggers() {
        const flags = this.consultation_data?.phase_flags || {};
        // If phase 2 confirmed but research not done, show confirmation (don't auto-trigger)
        if (flags.phase2_confirmed && !this.phase3ResearchRequested && !this.consultation_data.research_results) {
            // Show confirmation instead of auto-triggering
            this.showResearchConfirmation();
        }
        if (flags.phase4_presented && this.consultation_data.recommendation_checklist) {
            this.phase4ChecklistPresented = true;
        }
    }
    
    // Safe ajax helper - handles mixed globals (vmoAjax vs vmo_ajax) and missing tokens
    getAjax() {
        // Some templates expose VMO_AJAX as a string endpoint.
        try {
            if (typeof window !== 'undefined' && typeof window.VMO_AJAX === 'string' && window.VMO_AJAX) {
                const token = window.vmo_builder_nonce
                    || window.vmoBuilderNonce
                    || window.VMO_SECURITY_TOKEN
                    || '';
                if (!token && !this._ajaxWarnedMissingNonce) {
                    console.warn('⚠️ VMO consultation found VMO_AJAX but no nonce; requests may fail.');
                    this._ajaxWarnedMissingNonce = true;
                }
                return { url: window.VMO_AJAX, nonce: token };
            }
        } catch (_) {}

        const sources = [
            typeof window !== 'undefined' ? window.vmoAjax : null,
            typeof window !== 'undefined' ? window.vmo_ajax : null,
            typeof window !== 'undefined' ? window.vmoAdminAjax : null,
            typeof window !== 'undefined' ? window.VMOAjax : null,
            typeof window !== 'undefined' ? window.ajax_object : null
        ];

        for (const source of sources) {
            if (!source || typeof source !== 'object') {
                continue;
            }

            const url = source.ajaxurl || source.ajax_url || source.url || source.endpoint || source.rest_url;
            const token = source.security || source.nonce || source._ajax_nonce || source._nonce;

            if (url) {
                if (!token && !this._ajaxWarnedMissingNonce) {
                    console.warn('⚠️ VMO consultation located AJAX endpoint but missing nonce; requests may fail.');
                    this._ajaxWarnedMissingNonce = true;
                }
                return { url, nonce: token || '' };
            }
        }

        const fallbackUrl = (typeof window !== 'undefined' && typeof window.ajaxurl === 'string')
            ? window.ajaxurl
            : '/wp-admin/admin-ajax.php';
        const fallbackNonce = (typeof window !== 'undefined' && (window.vmoBuilderNonce || window.VMO_SECURITY_TOKEN))
            ? (window.vmoBuilderNonce || window.VMO_SECURITY_TOKEN)
            : '';

        // Final attempt: many templates define this exact global.
        const fallbackNonce2 = (typeof window !== 'undefined' && window.vmo_builder_nonce)
            ? window.vmo_builder_nonce
            : '';

        if (!this._ajaxFallbackWarned) {
            console.warn('⚠️ VMO consultation could not locate localized AJAX data; using fallback endpoint.');
            this._ajaxFallbackWarned = true;
        }

        return { url: fallbackUrl, nonce: fallbackNonce || fallbackNonce2 };
    }

    getPluginAssetUrl(assetPath = '') {
        const source = (typeof window !== 'undefined' && (window.vmoAjax || window.vmo_ajax))
            ? (window.vmoAjax || window.vmo_ajax)
            : {};
        const pluginBase = String(source.pluginUrl || source.plugin_url || '').replace(/\/+$/, '');
        const normalizedPath = String(assetPath || '').replace(/^\/+/, '');
        if (!pluginBase || !normalizedPath) {
            return '';
        }
        return `${pluginBase}/${normalizedPath}`;
    }

    async bootstrapSessionState(force = false) {
        if (this.bootstraptingState && !force) {
            return this.bootstraptingState;
        }
        const AX = this.getAjax();
        const payload = new URLSearchParams({
            action: 'vmo_get_consultation_context',
            security: AX.nonce,
            nonce: AX.nonce,
            _ajax_nonce: AX.nonce,
            session_id: this.sessionId
        });
        const pending = fetch(AX.url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: payload
        })
            .then(res => res.json())
            .then(data => {
                if (data?.success && data.data) {
                    // Backend returns: { success:true, data:{ session_id, data:<context>, ... } }
                    const wrapper = data.data;
                    const returnedSessionId = (wrapper && typeof wrapper.session_id === 'string') ? wrapper.session_id : '';
                    if (returnedSessionId) {
                        this.sessionId = returnedSessionId;
                        try { window.vmoCurrentConsultationSessionId = returnedSessionId; } catch (_) {}
                        try { if (typeof localStorage !== 'undefined') localStorage.setItem('vmoConsultationSessionId', returnedSessionId); } catch (_) {}
                    }
                    const context = (wrapper && wrapper.data && typeof wrapper.data === 'object') ? wrapper.data : wrapper;
                    this.mergeConsultationState(context);

                    // ✅ CRITICAL FIX: Restore conversation history from DB on page refresh
                    // Backend now includes conversation_log in the response
                    const restoredLog = wrapper.conversation_log;
                    if (Array.isArray(restoredLog) && restoredLog.length > 0 && this.conversationHistory.length === 0) {
                        this.conversationHistory = restoredLog.filter(
                            entry => entry && entry.role && entry.content
                        );
                        console.log(`✅ VMO: Restored ${this.conversationHistory.length} conversation history messages from DB`);
                    }
                }
                this.refreshPhaseUI();
                
                // ✅ CRITICAL FIX: Re-show Phase 3 research confirmation on session resume
                // When a user returns to a session already at Phase 3, onPhaseAdvanced(2) 
                // never fires (it only fires on transition). Without this, the user is stuck
                // in Phase 3 with no research confirmation UI and no way to start research.
                const resumedPhaseIndex = this.consultation_data?.consultation_flow?.current_index ?? 0;
                const phase3Done = this.consultation_data?.phase_flags?.phase3_completed || 
                                  this.state?.phase_flags?.phase3_completed;
                const researchAlreadyStarted = this.phase3ResearchRequested || this._deepResearchInProgress;
                
                if (resumedPhaseIndex === 2 && !phase3Done && !researchAlreadyStarted) {
                    console.log('🔍 Session resumed at Phase 3 — showing research confirmation after short delay');
                    setTimeout(() => {
                        // Double-check conditions haven't changed during the delay
                        if (!this.phase3ResearchRequested && !this._deepResearchInProgress && !this._awaitingResearchConfirmation) {
                            this.showResearchConfirmation();
                        }
                    }, 2000); // 2s delay to let the welcome message appear first
                }
                
                // ✅ Phase 4: If Phase 3 completed and checklist not yet shown, present it
                if (resumedPhaseIndex >= 3 && phase3Done && !this.phase4ChecklistPresented 
                    && this.consultation_data.research_results) {
                    console.log('📊 Session resumed post-Phase 3 — presenting recommendations checklist');
                    setTimeout(() => {
                        if (!this.phase4ChecklistPresented) {
                            this.presentPhase4Checklist(this.consultation_data.research_results);
                        }
                    }, 2500);
                }
            })
            .catch(err => console.warn('⚠️ Unable to hydrate consultation state', err))
            .finally(() => {
                this.bootstraptingState = null;
            });
        this.bootstraptingState = pending;
        return pending;
    }

    mergeConsultationState(serverData) {
        if (!serverData || typeof serverData !== 'object') {
            return;
        }
        
        // ═══════════════════════════════════════════════════════════════════════════════
        // ✅ RETURNING USER DETECTION - Check multiple indicators
        // If ANY of these are true, user is returning with existing progress
        // ═══════════════════════════════════════════════════════════════════════════════
        const hasExistingDocuments = Array.isArray(serverData.documents) && serverData.documents.length > 0;
        
        // ✅ IMPROVED: Check for meaningful extracted information (not just empty object)
        const extractedInfo = serverData.extracted_information || {};
        const meaningfulFields = [
            'business_name', 'businessName',
            'business_type', 'businessType',
            'target_market', 'targetAudience', 'target_audience',
            'offerings',
            'location',
            'goals', 'businessGoals', 'business_goals',
            'industry',
            'successDefinition', 'success_definition',
            'scaleIntent', 'scale_intent',
            'automationPreference', 'automation_preference'
        ];
        const hasExistingInfo = meaningfulFields.some(field => {
            const value = extractedInfo[field];
            return value && (typeof value === 'string' ? value.trim().length > 0 : true);
        });
        
        const hasExistingBlueprint = serverData.document_store?.blueprint_raw || serverData.document_store?.blueprint_summary;
        
        // ✅ Check for phase progress
        const currentPhaseIndex = serverData.consultation_flow?.current_index 
            ?? serverData.consultation_flow?.current_phase_index 
            ?? 0;
        const hasPhaseProgress = currentPhaseIndex > 0;
        
        // ✅ Check for phase flags (e.g., phase1_completed, phase2_confirmed)
        const phaseFlags = serverData.phase_flags || {};
        const hasPhaseFlags = Object.values(phaseFlags).some(v => v === true);
        
        // ✅ Check for research results
        const hasResearchResults = serverData.research_profile && Object.keys(serverData.research_profile).length > 0;
        
        // ✅ Check for media files
        const hasMediaFiles = Array.isArray(serverData.media_files) && serverData.media_files.length > 0;
        
        // Determine returning user status
        const isReturning = hasExistingDocuments || hasExistingInfo || hasExistingBlueprint || 
                           hasPhaseProgress || hasPhaseFlags || hasResearchResults || hasMediaFiles;
        
        if (isReturning) {
            this._isReturningUser = true;
            this._restoredDocumentCount = Array.isArray(serverData.documents) ? serverData.documents.length : 0;
            this._restoredPhaseIndex = currentPhaseIndex;
            this._restoredExtractedInfo = extractedInfo;
            console.log('📚 RETURNING USER DETECTED:', {
                documents: hasExistingDocuments,
                extractedInfo: hasExistingInfo,
                blueprint: !!hasExistingBlueprint,
                phaseProgress: hasPhaseProgress,
                phaseFlags: hasPhaseFlags,
                research: hasResearchResults,
                media: hasMediaFiles,
                phaseIndex: currentPhaseIndex
            });
        } else {
            console.log('🆕 NEW USER: No existing consultation data found');
        }
        
        this.state = this.deepMerge(this.state || this.buildInitialState(), serverData);
        if (Array.isArray(serverData.documents)) {
            this.consultation_data.documents = serverData.documents;
            const store = this.ensureDocumentStoreContainer();
            store.uploads = serverData.documents.map(doc => this.normalizeDocumentUpload(doc)).filter(Boolean);
        }
        if (Array.isArray(serverData.media_files)) {
            this.consultation_data.media_files = serverData.media_files;
            this.hydrateMediaCollections(serverData.media_files);
        }
        if (serverData.extracted_information) {
            this.consultation_data.extracted_information = this.deepMerge(
                this.consultation_data.extracted_information || {},
                serverData.extracted_information
            );
            this.collectedInformation = this.consultation_data.extracted_information;
        }
        if (serverData.research_profile) {
            this.consultation_data.research_results = serverData.research_profile;
        }
        if (serverData.recommendations_profile) {
            this.state.recommendations_profile = serverData.recommendations_profile;
            this.consultation_data.recommendations_profile = serverData.recommendations_profile;
        }
        if (serverData.recommendation_checklist) {
            this.consultation_data.recommendation_checklist = serverData.recommendation_checklist;
        }
        if (serverData.phone_provisioning_status && typeof serverData.phone_provisioning_status === 'object') {
            this.applyPhoneStatusToConsultationContext(
                this.normalizePhoneProvisioningStatus(serverData.phone_provisioning_status)
            );
        }
        // ✅ Handle scanned websites from backend
        if (serverData.scanned_websites && typeof serverData.scanned_websites === 'object') {
            this.consultation_data.scanned_websites = this.deepMerge(
                this.consultation_data.scanned_websites || {},
                serverData.scanned_websites
            );
            console.log('🌐 Scanned websites synced:', Object.keys(this.consultation_data.scanned_websites).length);
        }
        if (this.state.document_store) {
            this.blueprintRaw = this.state.document_store.blueprint_raw || this.blueprintRaw;
            this.blueprintSummary = this.state.document_store.blueprint_summary || this.blueprintSummary;
            
            // ✅ FIX: Mark that documents were loaded from DB - skip paste detection prompts
            // The user's documents are already in the system, no need to keep asking about them
            if (this.blueprintRaw || this.blueprintSummary) {
                this._documentsLoadedFromDB = true;
                this._skipPasteDetection = true;
                console.log('📄 Documents restored from database - paste detection disabled');
            }
        }
        const flow = this.state.consultation_flow || {};
        const hasCurrentIndex = typeof flow.current_index === 'number';
        const hasPhaseIndex = typeof flow.current_phase_index === 'number';
        let resolvedIndex = null;
        if (hasCurrentIndex) {
            resolvedIndex = flow.current_index;
        } else if (hasPhaseIndex) {
            resolvedIndex = flow.current_phase_index;
        }
        if (resolvedIndex !== null) {
            this.state.consultation_flow.current_phase_index = resolvedIndex;
            this.state.consultation_flow.current_index = resolvedIndex;
            this.consultation_data.consultation_flow.current_index = resolvedIndex;
        }
        if (Array.isArray(flow.phases) && flow.phases.length) {
            this.consultation_data.consultation_flow.phases = flow.phases.map(phase => phase?.name || phase?.key || phase);
        }
        this.ensureProvisioningContainers();
        this.refreshProvisioningUI();
        this.syncLegacyConsultationDataFromState();
        this.refreshAllowedActions();
        this.enforceSystemRecommendations();
        this.syncAutomationTriggers();
    }

    syncLegacyConsultationDataFromState() {
        if (!this.state) return;
        const canonical = this.state;
        const flow = canonical.consultation_flow || {};
        if (!this.consultation_data.consultation_flow) {
            this.consultation_data.consultation_flow = { phases: [], current_index: 0, status: 'in_progress' };
        }
        if (Array.isArray(flow.phases) && flow.phases.length) {
            this.consultation_data.consultation_flow.phases = flow.phases.map(phase => phase?.name || phase?.key || phase);
        }
        const legacyIndex = typeof flow.current_index === 'number'
            ? flow.current_index
            : (typeof flow.current_phase_index === 'number' ? flow.current_phase_index : null);
        if (legacyIndex !== null) {
            this.consultation_data.consultation_flow.current_index = legacyIndex;
            this.state.consultation_flow.current_index = legacyIndex;
            this.state.consultation_flow.current_phase_index = legacyIndex;
        }
        if (flow.status) {
            this.consultation_data.consultation_flow.status = flow.status;
        }

        const docStore = canonical.document_store || {};
        if (docStore.blueprint_raw) {
            this.blueprintRaw = docStore.blueprint_raw;
            // ✅ FIX: Mark documents loaded from DB - skip paste detection
            this._documentsLoadedFromDB = true;
            this._skipPasteDetection = true;
        }
        if (docStore.blueprint_summary) {
            this.blueprintSummary = docStore.blueprint_summary;
            // ✅ FIX: Mark documents loaded from DB - skip paste detection
            this._documentsLoadedFromDB = true;
            this._skipPasteDetection = true;
        }
        if (Array.isArray(docStore.uploads) && docStore.uploads.length > 0) {
            this.consultation_data.documents = docStore.uploads.map(upload => ({
                id: upload.id || upload.hash || upload.filename || upload.url || `doc_${Math.random().toString(36).slice(2)}`,
                title: upload.title || upload.filename || upload.original_name || 'Uploaded Document',
                url: upload.url || '',
                mime: upload.mime_type || upload.mime || '',
                length: upload.length || upload.size || 0,
                chunks: upload.chunks || []
            }));
            const store = this.ensureDocumentStoreContainer();
            store.uploads = this.consultation_data.documents.map(doc => ({ ...doc }));
        }

        if (Array.isArray(canonical.media_files)) {
            this.consultation_data.media_files = canonical.media_files.slice();
            this.hydrateMediaCollections(this.consultation_data.media_files);
        }

        if (canonical.research_profile && Object.keys(canonical.research_profile).length) {
            this.consultation_data.research_results = canonical.research_profile;
        }

        if (canonical.recommendations_profile && Object.keys(canonical.recommendations_profile).length) {
            this.state.recommendations_profile = canonical.recommendations_profile;
            this.consultation_data.recommendations_profile = canonical.recommendations_profile;
        }
        if (canonical.recommendation_checklist) {
            this.consultation_data.recommendation_checklist = canonical.recommendation_checklist;
        }
        if (canonical.phone_provisioning_status && typeof canonical.phone_provisioning_status === 'object') {
            this.applyPhoneStatusToConsultationContext(
                this.normalizePhoneProvisioningStatus(canonical.phone_provisioning_status)
            );
        }

        this.consultation_data.website_plan = canonical.website_plan || this.consultation_data.website_plan || {};
        this.consultation_data.systems_plan = canonical.systems_plan || this.consultation_data.systems_plan || {};
        this.consultation_data.roadmap_json = canonical.roadmap_json || this.consultation_data.roadmap_json || {};
        this.consultation_data.daily_tasks_json = canonical.daily_tasks_json || this.consultation_data.daily_tasks_json || {};
        this.consultation_data.approvals = canonical.approvals || this.consultation_data.approvals || {};
        
        // ✅ Sync scanned websites from canonical state
        if (canonical.scanned_websites && typeof canonical.scanned_websites === 'object') {
            this.consultation_data.scanned_websites = this.deepMerge(
                this.consultation_data.scanned_websites || {},
                canonical.scanned_websites
            );
        }

        this.backfillExtractedInformationFromProfile(canonical.business_profile || {});
        this.refreshInsightsUI();
        this.refreshAllowedActions();
    }

    backfillExtractedInformationFromProfile(profile) {
        if (!profile || typeof profile !== 'object') return;
        const info = this.consultation_data.extracted_information || {};
        const identity = profile.identity || {};
        if (identity.business_name) info.businessName = identity.business_name;
        if (identity.legal_structure) info.legalStructure = identity.legal_structure;
        if (identity.hq_location) info.location = identity.hq_location;

        const audience = profile.audience || {};
        if (Array.isArray(audience.primary_segments) && audience.primary_segments.length) {
            info.targetAudience = audience.primary_segments.join(', ');
        }

        if (Array.isArray(profile.offers) && profile.offers.length) {
            info.offerings = profile.offers.map(offer => offer.name || offer.title || offer).filter(Boolean);
        }

        const targets = profile.targets || {};
        if (targets.short_term || targets.mid_term || targets.long_term) {
            info.businessGoals = [targets.short_term, targets.mid_term, targets.long_term].filter(Boolean).join(' • ');
        }

        const web = profile.web_requirements || {};
        info.needsEcommerce = !!web.ecommerce;
        info.needsBooking = !!web.booking;
        info.needsPhoneSystem = !!(profile.operations?.support_hours);
        info.needsEmailMarketing = Array.isArray(web.integrations) && web.integrations.some(integration => /mail|email|crm/i.test(integration));

        if (Array.isArray(web.reference_sites) && web.reference_sites.length) {
            info.websitePreferences = web.reference_sites.join(', ');
        }

        this.consultation_data.extracted_information = info;
        this.collectedInformation = info;
        this.enforceSystemRecommendations();
    }

    applyStructuredDelta(delta) {
        if (!delta || typeof delta !== 'object') return;
        if (!this.state) {
            this.state = this.buildInitialState();
        }
        if (delta.business_profile_delta) {
            this.state.business_profile = this.deepMerge(this.state.business_profile || {}, delta.business_profile_delta);
        }
        if (Array.isArray(delta.offers_profile_delta) && delta.offers_profile_delta.length) {
            const existingOffers = Array.isArray(this.state.offers_profile) ? this.state.offers_profile : [];
            this.state.offers_profile = existingOffers.concat(delta.offers_profile_delta);
        }
        if (delta.payments_profile_delta) {
            this.state.payments_profile = this.deepMerge(this.state.payments_profile || {}, delta.payments_profile_delta);
        }
        this.syncLegacyConsultationDataFromState();
        this.refreshPhaseUI();
    }

    refreshInsightsUI() {
        if (typeof window === 'undefined') return;
        const raf = window.requestAnimationFrame || ((cb) => setTimeout(cb, 16));
        raf(() => this.renderInsightPanels());
    }

    ensureInsightsPanel() {
        const host = document.getElementById('vmo-wizard-conversation');
        if (!host) return null;
        this.injectInsightsStyles();
        let panel = document.getElementById('vmo-insights-panel');
        if (!panel) {
            panel = document.createElement('div');
            panel.id = 'vmo-insights-panel';
            panel.className = 'vmo-insights-panel';
            host.prepend(panel);
        }
        return panel;
    }

    injectInsightsStyles() {
        if (document.getElementById('vmo-insights-styles')) return;
        const style = document.createElement('style');
        style.id = 'vmo-insights-styles';
        style.textContent = `
.vmo-insights-panel { display: grid; gap: 12px; margin-bottom: 18px; }
.vmo-insight-card { border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 14px 16px; background: rgba(4,12,24,0.7); backdrop-filter: blur(8px); }
.vmo-insight-card h5 { margin: 0 0 6px 0; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: #7cd1ff; display:flex; align-items:center; gap:6px; }
.vmo-insight-card ul { margin: 6px 0 0 0; padding-left: 18px; }
.vmo-insight-card li { font-size: 13px; line-height: 1.4; margin-bottom: 4px; }
.vmo-insight-meta { font-size: 11px; color: rgba(255,255,255,0.65); margin-top: 8px; display:flex; gap:12px; flex-wrap:wrap; }
.vmo-accordion { border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; background: rgba(4,12,24,0.7); backdrop-filter: blur(8px); margin-bottom: 10px; overflow: hidden; }
.vmo-accordion-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; cursor: pointer; user-select: none; transition: background 0.2s ease; }
.vmo-accordion-header:hover { background: rgba(255,255,255,0.03); }
.vmo-accordion-title { font-size: 13px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: #7cd1ff; display: flex; align-items: center; gap: 8px; }
.vmo-accordion-toggle { font-size: 12px; color: rgba(255,255,255,0.5); transition: transform 0.3s ease; }
.vmo-accordion.open .vmo-accordion-toggle { transform: rotate(180deg); }
.vmo-accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; }
.vmo-accordion.open .vmo-accordion-content { max-height: 1000px; }
.vmo-accordion-inner { padding: 0 16px 14px 16px; }
`; 
        document.head.appendChild(style);
    }

    // ✅ NEW: Inject styles for recommendation accordions
    injectRecommendationAccordionStyles() {
        if (document.getElementById('vmo-rec-accordion-styles')) return;
        const style = document.createElement('style');
        style.id = 'vmo-rec-accordion-styles';
        style.textContent = `
/* Recommendations Panel */
.vmo-recommendations-panel { max-width: 100%; }

/* Research Sources Panel */
.vmo-rec-sources-panel { 
    margin-bottom: 16px; 
    background: rgba(59, 130, 246, 0.08); 
    border-radius: 10px; 
    border: 1px solid rgba(59, 130, 246, 0.2); 
}
.vmo-rec-source-item { 
    display: flex; 
    flex-wrap: wrap;
    gap: 6px; 
    padding: 8px 0; 
    border-bottom: 1px solid rgba(255,255,255,0.05);
    font-size: 13px;
}
.vmo-rec-source-item:last-child { border-bottom: none; }
.vmo-rec-source-item strong { color: rgba(255,255,255,0.8); min-width: 140px; }
.vmo-rec-source-item span { color: rgba(255,255,255,0.95); flex: 1; }

/* Recommendation Item */
.vmo-rec-item { transition: background 0.2s ease; }
.vmo-rec-item:hover { background: rgba(255,255,255,0.02); }

.vmo-rec-main { display: flex; flex-direction: column; gap: 8px; }

.vmo-rec-header { 
    display: flex; 
    gap: 12px; 
    align-items: flex-start; 
}

.vmo-rec-checkbox { 
    margin-top: 4px; 
    width: 18px; 
    height: 18px; 
    accent-color: #22c55e;
    cursor: pointer;
}

.vmo-rec-title-wrap { 
    display: flex; 
    align-items: center; 
    gap: 10px; 
    flex: 1;
    flex-wrap: wrap;
}

.vmo-rec-title { 
    font-weight: 600; 
    font-size: 15px;
    outline: none; 
    color: #fff;
    line-height: 1.4;
}

/* Impact Badges */
.vmo-rec-impact { 
    padding: 3px 8px; 
    border-radius: 4px; 
    font-size: 10px; 
    font-weight: 700; 
    letter-spacing: 0.5px;
}
.vmo-rec-impact-high { background: rgba(239, 68, 68, 0.25); color: #fca5a5; }
.vmo-rec-impact-medium { background: rgba(251, 191, 36, 0.25); color: #fcd34d; }
.vmo-rec-impact-low { background: rgba(148, 163, 184, 0.25); color: #cbd5e1; }

/* Accordion for Why/Details */
.vmo-rec-item .vmo-rec-accordion,
.vmo-rec-sources-panel .vmo-rec-accordion { 
    margin-left: 30px; 
    border-radius: 8px; 
    overflow: hidden;
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.06);
}

.vmo-rec-item .vmo-rec-accordion-header,
.vmo-rec-sources-panel .vmo-rec-accordion-header { 
    display: flex; 
    justify-content: space-between; 
    align-items: center;
    padding: 10px 14px; 
    cursor: pointer; 
    user-select: none;
    font-size: 13px;
    color: rgba(255,255,255,0.8);
    transition: background 0.2s ease;
}
.vmo-rec-item .vmo-rec-accordion-header:hover,
.vmo-rec-sources-panel .vmo-rec-accordion-header:hover { background: rgba(255,255,255,0.05); }

.vmo-rec-item .vmo-rec-accordion-icon,
.vmo-rec-sources-panel .vmo-rec-accordion-icon { 
    font-size: 10px; 
    transition: transform 0.3s ease;
    color: rgba(255,255,255,0.5);
}
.vmo-rec-item .vmo-rec-accordion.open .vmo-rec-accordion-icon,
.vmo-rec-sources-panel .vmo-rec-accordion.open .vmo-rec-accordion-icon { transform: rotate(90deg); }

.vmo-rec-item .vmo-rec-accordion-content,
.vmo-rec-sources-panel .vmo-rec-accordion-content { 
    max-height: 0; 
    overflow: hidden; 
    transition: max-height 0.4s ease-out;
}
.vmo-rec-item .vmo-rec-accordion.open .vmo-rec-accordion-content,
.vmo-rec-sources-panel .vmo-rec-accordion.open .vmo-rec-accordion-content { 
    max-height: 800px; 
    transition: max-height 0.5s ease-in;
}

.vmo-rec-source-section {
    padding: 6px 0;
}

.vmo-rec-source-section + .vmo-rec-source-section {
    margin-top: 10px;
    padding-top: 12px;
    border-top: 1px solid rgba(255,255,255,0.08);
}

.vmo-rec-source-section-title {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #93c5fd;
    margin-bottom: 8px;
}

.vmo-rec-source-item a {
    color: #bfdbfe;
    text-decoration: none;
    word-break: break-all;
}

.vmo-rec-source-item a:hover {
    text-decoration: underline;
}

.vmo-rec-source-meta {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    color: rgba(255,255,255,0.6);
}

/* Detail Items inside Accordion */
.vmo-rec-detail { 
    padding: 10px 14px; 
    border-top: 1px solid rgba(255,255,255,0.05);
    font-size: 13px;
    line-height: 1.5;
}
.vmo-rec-detail:first-child { border-top: none; }

.vmo-rec-detail-label { 
    display: block;
    font-weight: 600;
    color: rgba(255,255,255,0.7);
    margin-bottom: 4px;
    font-size: 12px;
}
.vmo-rec-detail-value { 
    color: rgba(255,255,255,0.95);
    outline: none;
}

/* Color coding for different detail types */
.vmo-rec-detail-wanted { 
    background: rgba(59, 130, 246, 0.1); 
    border-left: 3px solid #3b82f6;
}
.vmo-rec-detail-research { 
    background: rgba(34, 197, 94, 0.1); 
    border-left: 3px solid #22c55e;
}
.vmo-rec-detail-ai { 
    background: rgba(168, 85, 247, 0.1); 
    border-left: 3px solid #a855f7;
}
.vmo-rec-detail-why { 
    background: rgba(251, 191, 36, 0.08); 
    border-left: 3px solid #fbbf24;
}

/* Options Radio Buttons */
.vmo-rec-options { 
    display: flex; 
    flex-wrap: wrap; 
    gap: 8px; 
    margin-left: 30px;
    margin-top: 4px;
}
.vmo-rec-option { 
    display: flex; 
    align-items: center; 
    gap: 6px; 
    padding: 8px 12px; 
    background: rgba(255,255,255,0.05); 
    border-radius: 6px; 
    cursor: pointer; 
    font-size: 12px;
    transition: background 0.2s ease;
    border: 1px solid rgba(255,255,255,0.08);
}
.vmo-rec-option:hover { background: rgba(255,255,255,0.08); }
.vmo-rec-option:has(input:checked) { 
    background: rgba(34, 197, 94, 0.15); 
    border-color: rgba(34, 197, 94, 0.4);
}
.vmo-rec-option input { accent-color: #22c55e; }
`;
        document.head.appendChild(style);
    }

    renderInsightPanels() {
        const panel = this.ensureInsightsPanel();
        if (!panel) return;
        panel.innerHTML = '';

        // Build accordion helper
        const createAccordion = (id, icon, title, contentHtml) => {
            const accordion = document.createElement('div');
            accordion.className = 'vmo-accordion';
            accordion.id = id;
            accordion.innerHTML = `
                <div class="vmo-accordion-header" onclick="this.parentElement.classList.toggle('open')">
                    <span class="vmo-accordion-title">${icon} ${title}</span>
                    <span class="vmo-accordion-toggle">▼</span>
                </div>
                <div class="vmo-accordion-content">
                    <div class="vmo-accordion-inner">${contentHtml}</div>
                </div>
            `;
            return accordion;
        };

        // === ACCORDION 1: Recommendations & Research ===
        const recommendationsProfile = this.state.recommendations_profile || this.consultation_data.recommendation_checklist;
        const researchProfile = this.state.research_profile || this.consultation_data.research_results;
        
        let recsHtml = '';
        if (Array.isArray(recommendationsProfile?.items)) {
            const items = recommendationsProfile.items.slice(0, 4).map(item => `<li>${item.label || item.title || 'Recommendation'} · <em>${item.reason || item.why || 'Drives growth'}</em></li>`).join('');
            recsHtml = `<ul style="margin:6px 0 0 0;padding-left:18px;">${items}</ul>`;
        } else {
            recsHtml = '<p style="font-size:13px;color:rgba(255,255,255,0.7);margin:6px 0 0 0;">Recommendations ready once Phase 4 checklist is presented.</p>';
        }
        const accepted = this.state?.approvals?.recommendations?.accepted || [];
        const rejected = this.state?.approvals?.recommendations?.rejected || [];
        recsHtml += `<div class="vmo-insight-meta" style="margin-top:10px;"><span>Approved: ${accepted.length}</span><span>Deferred: ${rejected.length}</span></div>`;
        
        panel.appendChild(createAccordion('vmo-accordion-recommendations', '🧭', 'Recommendations', recsHtml));

        // === ACCORDION 2: Deliverables ===
        const websitePlanReady = !!(this.state.website_plan && Object.keys(this.state.website_plan).length);
        const systemsReady = !!(this.state.systems_plan && Object.keys(this.state.systems_plan).length);
        const roadmapReady = !!(this.state.roadmap_json && Object.keys(this.state.roadmap_json).length);
        const tasksReady = !!(this.state.daily_tasks_json && Object.keys(this.state.daily_tasks_json).length);
        const artifactsReady = !!(this.state.approvals?.artifacts_generated);
        
        let deliverablesHtml = `
            <ul style="margin:6px 0 0 0;padding-left:18px;font-size:13px;">
                <li>${websitePlanReady ? '✅' : '⏳'} Website architecture</li>
                <li>${systemsReady ? '✅' : '⏳'} Systems & automations</li>
                <li>${roadmapReady ? '✅' : '⏳'} Launch roadmap</li>
                <li>${tasksReady ? '✅' : '⏳'} Daily execution plan</li>
            </ul>
            <div class="vmo-insight-meta" style="margin-top:10px;"><span>Artifacts generated: ${artifactsReady ? 'Yes' : 'Pending'}</span></div>
        `;
        
        panel.appendChild(createAccordion('vmo-accordion-deliverables', '📦', 'Deliverables', deliverablesHtml));

        // === ACCORDION 3: Consultation Phases (moved from sidebar) ===
        const flow = this.state.consultation_flow || {};
        const phases = Array.isArray(flow.phases) && flow.phases.length
            ? flow.phases
            : (this.consultation_data.consultation_flow?.phases || []).map(name => ({ name }));
        const currentIndex = typeof flow.current_phase_index === 'number'
            ? flow.current_phase_index
            : (this.consultation_data.consultation_flow?.current_index || 0);
        
        let phasesHtml = '';
        const phaseDescriptions = [
            'Capture every core fact about the business in novice-friendly language • Extract answers from uploaded documents and media',
            'Generate professional blueprint summary (vision, mission, goals, positioning) • Ensure user confirms or amends before proceeding',
            'Run automated research scan with progress indicator • Populate research_profile with industry, market, competitor, SEO insights',
            'Convert research insights into actionable levers • Provide checkbox list so user can approve/veto each recommendation',
            'Design site map, page strategy, copy tone, and UX differentiators • Tie every section back to research & stakeholder goals',
            'Define CRM/phone/email/analytics stack • List every legal/compliance document required and where it lives',
            'Translate goals into milestone roadmap and daily task plans • Estimate timeframe to achieve the North Star metric',
            'Summarize everything, confirm approvals, trigger generation of website + docs • Hand off deliverables to dashboard/download center'
        ];
        const phaseNames = [
            'Phase 1 · Business Foundations Intake',
            'Phase 2 · Executive Blueprint Confirmation',
            'Phase 3 · Deep Industry & Market Research',
            'Phase 4 · Strategic Recommendations & Decisions',
            'Phase 5 · Website & Experience Architecture',
            'Phase 6 · Systems, Automations & Compliance',
            'Phase 7 · Roadmap & Daily Execution Model',
            'Phase 8 · Launch Readiness & Deliverables'
        ];
        
        for (let idx = 0; idx < 8; idx++) {
            const status = idx < currentIndex ? 'completed' : (idx === currentIndex ? 'active' : 'pending');
            const statusIcon = status === 'completed' ? '✅' : (status === 'active' ? '🟢' : '⏳');
            const borderColor = status === 'completed' ? 'rgba(66, 245, 152, 0.8)' : (status === 'active' ? 'rgba(124, 209, 255, 0.9)' : 'rgba(255,255,255,0.06)');
            const bgColor = status === 'active' ? 'rgba(124, 209, 255, 0.08)' : 'rgba(255,255,255,0.02)';
            
            // ✅ Make completed phases clickable to go back
            const isClickable = status === 'completed';
            const cursorStyle = isClickable ? 'cursor:pointer;' : '';
            const hoverHint = isClickable ? '<span style="font-size:10px;color:rgba(255,255,255,0.5);margin-left:8px;">← click to redo</span>' : '';
            
            phasesHtml += `
                <div class="vmo-phase-accordion-item" data-phase-index="${idx}" data-phase-status="${status}" style="border-radius:10px;padding:10px 12px;margin-bottom:8px;border:1px solid ${borderColor};background:${bgColor};transition:all 0.2s ease;${cursorStyle}">
                    <div style="font-weight:600;font-size:13px;display:flex;align-items:center;justify-content:space-between;">
                        <span>${idx + 1}. ${phaseNames[idx]}${hoverHint}</span>
                        <span>${statusIcon}</span>
                    </div>
                    <div style="font-size:11px;color:rgba(255,255,255,0.7);margin-top:4px;line-height:1.4;">${phaseDescriptions[idx]}</div>
                </div>
            `;
        }
        
        panel.appendChild(createAccordion('vmo-accordion-phases', '📋', 'Consultation Phases', phasesHtml));
        
        // ✅ Bind click handlers for phase navigation
        this.bindPhaseClickHandlers();

        // Hide the old phase sidebar since we moved it to accordion
        if (this.phaseSidebarEl) {
            this.phaseSidebarEl.style.display = 'none';
        }
    }

    buildResearchCard(researchProfile) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const industry = researchProfile?.industry?.name || researchProfile?.industry || 'Research Highlights';
        const competitors = (researchProfile?.competitors || []).slice(0, 3).map(c => c.name || c).join(', ') || 'Pending competitor scan…';
        const channels = (researchProfile?.channels?.top || researchProfile?.channels || []).slice(0, 3).join(', ') || 'TBD';
        const opportunities = (researchProfile?.opportunities || researchProfile?.gaps || []).slice(0, 2).map(o => `<li>${o}</li>`).join('') || '<li>Insights will appear here once research runs.</li>';
        card.innerHTML = `
            <h5>🔎 Research · ${industry}</h5>
            <ul>${opportunities}</ul>
            <div class="vmo-insight-meta">
                <span>Competitors: ${competitors}</span>
                <span>Top Channels: ${channels}</span>
            </div>
        `;
        return card;
    }

    buildRecommendationsCard(recommendationsProfile) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        let bodyHtml = '';
        if (Array.isArray(recommendationsProfile.items)) {
            const items = recommendationsProfile.items.slice(0, 4).map(item => `<li>${item.label || item.title || 'Recommendation'} · <em>${item.reason || item.why || 'Drives growth'}</em></li>`).join('');
            bodyHtml = `<ul>${items}</ul>`;
        } else if (typeof recommendationsProfile === 'string') {
            bodyHtml = `<p>${recommendationsProfile.substring(0, 280)}${recommendationsProfile.length > 280 ? '…' : ''}</p>`;
        } else {
            bodyHtml = '<p>Recommendations ready once Phase 4 checklist is presented.</p>';
        }
        const accepted = this.state?.approvals?.recommendations?.accepted || [];
        const rejected = this.state?.approvals?.recommendations?.rejected || [];
        card.innerHTML = `
            <h5>🧭 Recommendations</h5>
            ${bodyHtml}
            <div class="vmo-insight-meta">
                <span>Approved: ${accepted.length}</span>
                <span>Deferred: ${rejected.length}</span>
            </div>
        `;
        return card;
    }

    buildDeliverablesCard() {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const websitePlanReady = !!(this.state.website_plan && Object.keys(this.state.website_plan).length);
        const systemsReady = !!(this.state.systems_plan && Object.keys(this.state.systems_plan).length);
        const roadmapReady = !!(this.state.roadmap_json && Object.keys(this.state.roadmap_json).length);
        const tasksReady = !!(this.state.daily_tasks_json && Object.keys(this.state.daily_tasks_json).length);
        const artifactsReady = !!(this.state.approvals?.artifacts_generated);
        card.innerHTML = `
            <h5>📦 Deliverables</h5>
            <ul>
                <li>${websitePlanReady ? '✅' : '⏳'} Website architecture</li>
                <li>${systemsReady ? '✅' : '⏳'} Systems & automations</li>
                <li>${roadmapReady ? '✅' : '⏳'} Launch roadmap</li>
                <li>${tasksReady ? '✅' : '⏳'} Daily execution plan</li>
            </ul>
            <div class="vmo-insight-meta">
                <span>Artifacts generated: ${artifactsReady ? 'Yes' : 'Pending'}</span>
            </div>
        `;
        return card;
    }

    buildWebsitePlanCard(websitePlan = {}) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const pages = Array.isArray(websitePlan.pages)
            ? websitePlan.pages
            : (Array.isArray(websitePlan.sections)
                ? websitePlan.sections
                : (Array.isArray(websitePlan) ? websitePlan : []));
        const items = pages.slice(0, 4).map(page => {
            const name = page.title || page.name || page.slug || 'Page';
            const highlights = [];
            if (Array.isArray(page.sections)) {
                highlights.push(`${page.sections.length} sections`);
            }
            if (page.primary_goal) {
                highlights.push(page.primary_goal);
            }
            if (page.cta || page.primary_cta) {
                highlights.push(`CTA: ${page.cta || page.primary_cta}`);
            }
            return `<li><strong>${name}</strong>${highlights.length ? ` · ${highlights.join(' • ')}` : ''}</li>`;
        }).join('');
        const fallback = '<li>Architecture will appear here after Phase 5 completes.</li>';
        card.innerHTML = `
            <h5>🧱 Website Architecture</h5>
            <ul>${items || fallback}</ul>
        `;
        return card;
    }

    buildSystemsPlanCard(systemsPlan = {}) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const lines = [];
        if (systemsPlan.payments) {
            const provider = systemsPlan.payments.provider || systemsPlan.payments.platform || 'Payments Hub';
            lines.push(`Payments · ${provider}`);
        }
        if (systemsPlan.crm) {
            lines.push(`CRM · ${systemsPlan.crm.name || systemsPlan.crm.platform || 'VMO CRM'}`);
        }
        if (systemsPlan.automations) {
            const automationCount = Array.isArray(systemsPlan.automations) ? systemsPlan.automations.length : Object.keys(systemsPlan.automations).length;
            if (automationCount) {
                lines.push(`Automations · ${automationCount}+ workflows`);
            }
        }
        if (systemsPlan.support) {
            lines.push(`Support · ${systemsPlan.support.channel || systemsPlan.support.tool || 'Omni-channel'}`);
        }
        if (!lines.length && Array.isArray(systemsPlan.systems)) {
            systemsPlan.systems.slice(0, 4).forEach(system => {
                if (!system) return;
                const name = system.name || system.label || 'System';
                const detail = system.summary || system.reason || '';
                lines.push(`${name}${detail ? ` · ${detail}` : ''}`);
            });
        }
        card.innerHTML = `
            <h5>⚙️ Systems & Automations</h5>
            <ul>${lines.length ? lines.map(line => `<li>${line}</li>`).join('') : '<li>Systems blueprint will populate during Phase 6.</li>'}</ul>
        `;
        return card;
    }

    buildRoadmapCard(roadmap = {}, tasks = {}) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const phases = Array.isArray(roadmap.phases)
            ? roadmap.phases
            : (Array.isArray(roadmap.milestones)
                ? roadmap.milestones
                : (Array.isArray(roadmap) ? roadmap : []));
        const phaseItems = phases.slice(0, 3).map(phase => {
            const title = phase.name || phase.title || 'Milestone';
            const eta = phase.timeline || phase.due || phase.target_date || 'TBD';
            return `<li><strong>${title}</strong> · ${eta}</li>`;
        }).join('');
        const dayTasks = Array.isArray(tasks.tasks) ? tasks.tasks : (Array.isArray(tasks.items) ? tasks.items : []);
        const nextTask = dayTasks.find(task => !task.completed) || dayTasks[0];
        card.innerHTML = `
            <h5>🗺️ Roadmap & Daily Tasks</h5>
            <ul>${phaseItems || '<li>Roadmap will unlock after Phase 5 deliverables.</li>'}</ul>
            <div class="vmo-insight-meta">
                <span>Next Task: ${nextTask ? (nextTask.title || nextTask.name || 'Execute daily actions') : 'Pending scheduling'}</span>
                <span>Total Tasks: ${dayTasks.length || 0}</span>
            </div>
        `;
        return card;
    }

    buildDocumentCard(documents) {
        const card = document.createElement('section');
        card.className = 'vmo-insight-card';
        const list = documents.slice(0, 3).map(doc => `<li>${doc.title || doc.filename || 'Document'}${doc.mime ? ` · ${doc.mime}` : ''}</li>`).join('');
        card.innerHTML = `
            <h5>📄 Documents (${documents.length})</h5>
            <ul>${list}</ul>
        `;
        return card;
    }

    refreshPhaseUI() {
        this.updatePhaseProgressBar();
        this.renderPhaseSidebar();
        this.refreshInsightsUI();
    }

    ensurePhaseSidebar() {
        if (this.phaseSidebarEl && document.body.contains(this.phaseSidebarEl)) {
            return this.phaseSidebarEl;
        }
        const host = document.getElementById('vmo-wizard-conversation');
        if (!host) {
            return null;
        }
        this.injectPhaseStyles();
        let container = host.querySelector('.vmo-phase-sidebar');
        if (!container) {
            container = document.createElement('div');
            container.className = 'vmo-phase-sidebar';
            host.prepend(container);
        }
        this.phaseSidebarEl = container;
        return container;
    }

    injectPhaseStyles() {
        if (this.hasInjectedPhaseStyles) {
            return;
        }
        const style = document.createElement('style');
        style.id = 'vmo-phase-styles';
        style.textContent = `
.vmo-phase-sidebar { margin-bottom: 18px; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; background: rgba(3,13,27,0.65); backdrop-filter: blur(6px); overflow: hidden; }
.vmo-phase-sidebar .vmo-accordion-header { padding: 14px 16px; }
.vmo-phase-sidebar .vmo-accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; }
.vmo-phase-sidebar.open .vmo-accordion-content { max-height: 2000px; }
.vmo-phase-sidebar .vmo-accordion-inner { padding: 0 16px 14px 16px; }
.vmo-phase-chip { border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; border: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.02); transition: border-color 0.2s ease; }
.vmo-phase-chip[data-status="completed"] { border-color: rgba(66, 245, 152, 0.8); }
.vmo-phase-chip[data-status="active"] { border-color: rgba(124, 209, 255, 0.9); background: rgba(124, 209, 255, 0.08); }
.vmo-phase-chip-title { font-weight: 600; font-size: 13px; display: flex; align-items: center; justify-content: space-between; }
.vmo-phase-chip-meta { font-size: 11px; color: rgba(255,255,255,0.7); margin-top: 4px; line-height: 1.4; }
`; 
        document.head.appendChild(style);
        this.hasInjectedPhaseStyles = true;
    }

    renderPhaseSidebar() {
        const container = this.ensurePhaseSidebar();
        if (!container) {
            return;
        }
        const flow = this.state.consultation_flow || {};
        const phases = Array.isArray(flow.phases) && flow.phases.length
            ? flow.phases
            : (this.consultation_data.consultation_flow?.phases || []).map(name => ({ name }));
        const currentIndex = typeof flow.current_phase_index === 'number'
            ? flow.current_phase_index
            : (this.consultation_data.consultation_flow?.current_index || 0);
        const fragment = document.createDocumentFragment();
        const title = document.createElement('h4');
        title.textContent = 'Consultation Phases';
        fragment.appendChild(title);
        phases.forEach((phase, idx) => {
            const chip = document.createElement('div');
            const status = idx < currentIndex ? 'completed' : (idx === currentIndex ? 'active' : 'pending');
            chip.className = 'vmo-phase-chip';
            chip.dataset.status = status;
            const name = phase?.name || phase?.key || `Phase ${idx + 1}`;
            const objectives = Array.isArray(phase?.objectives) ? phase.objectives.slice(0, 2).join(' • ') : '';
            chip.innerHTML = `
                <div class="vmo-phase-chip-title">
                    <span>${idx + 1}. ${name}</span>
                    <span>${status === 'completed' ? '✅' : status === 'active' ? '🟢' : '⏳'}</span>
                </div>
                ${objectives ? `<div class="vmo-phase-chip-meta">${objectives}</div>` : ''}
            `;
            fragment.appendChild(chip);
        });
        container.innerHTML = '';
        container.appendChild(fragment);
    }

    // ✅ Bind click handlers for completed phases so user can click to go back
    bindPhaseClickHandlers() {
        const phaseItems = document.querySelectorAll('.vmo-phase-accordion-item[data-phase-status="completed"]');
        phaseItems.forEach(item => {
            // Add hover effect
            item.addEventListener('mouseenter', () => {
                item.style.background = 'rgba(66, 245, 152, 0.15)';
                item.style.borderColor = 'rgba(66, 245, 152, 1)';
                item.style.transform = 'scale(1.02)';
            });
            item.addEventListener('mouseleave', () => {
                item.style.background = 'rgba(255,255,255,0.02)';
                item.style.borderColor = 'rgba(66, 245, 152, 0.8)';
                item.style.transform = 'scale(1)';
            });
            
            // Add click handler
            item.addEventListener('click', (e) => {
                e.stopPropagation();
                const phaseIndex = parseInt(item.dataset.phaseIndex, 10);
                if (!isNaN(phaseIndex)) {
                    this.goToPhase(phaseIndex);
                }
            });
        });
    }
    
    getConsultationPhaseNames() {
        return [
            'Business Foundations Intake',
            'Executive Blueprint Confirmation',
            'Deep Industry & Market Research',
            'Strategic Recommendations & Decisions',
            'Website & Experience Architecture',
            'Systems, Automations & Compliance',
            'Roadmap & Daily Execution Model',
            'Launch Readiness & Deliverables'
        ];
    }

    getRedoPhaseIntent(message = '') {
        const text = String(message || '').toLowerCase().trim();
        if (!text) return null;

        const hasRedoVerb = /\b(redo|re-do|regenerate|rebuild|rerun|refresh|repeat|revisit)\b/.test(text);
        const hasNavigationVerb = /(go back|back to|return to|jump to|take me to|move to)/.test(text);
        const hasRestartPhaseSignal = /\brestart\b/.test(text)
            && /(phase|foundation|intake|blueprint|summary|research|recommendation|architecture|systems|compliance|roadmap|launch|deliverables?)/.test(text);

        if (!hasRedoVerb && !hasNavigationVerb && !hasRestartPhaseSignal) {
            return null;
        }

        const phaseWordToNumber = {
            one: 1,
            two: 2,
            three: 3,
            four: 4,
            five: 5,
            six: 6,
            seven: 7,
            eight: 8
        };

        const explicitPhaseMatch = text.match(/\bphase\s*(one|two|three|four|five|six|seven|eight|[1-8])\b/);
        if (explicitPhaseMatch) {
            const raw = explicitPhaseMatch[1];
            const asNumber = /^\d$/.test(raw) ? Number(raw) : phaseWordToNumber[raw];
            if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= 8) {
                return asNumber - 1;
            }
        }

        const phaseMatchers = [
            { index: 0, pattern: /(business foundations|foundations intake|intake phase|business intake)/ },
            { index: 1, pattern: /(executive blueprint|blueprint confirmation|business summary|summary confirmation)/ },
            { index: 2, pattern: /(deep research|market research|industry research|competitive research)/ },
            { index: 3, pattern: /(strategic recommendations?|recommendation checklist|recommendations phase)/ },
            { index: 4, pattern: /(website architecture|experience architecture|site map|sitemap|ux recommendations?)/ },
            { index: 5, pattern: /(systems[, ]*automations?|systems.*compliance|email setup|crm setup|compliance phase)/ },
            { index: 6, pattern: /(roadmap|daily execution|daily task plan|milestone plan)/ },
            { index: 7, pattern: /(launch readiness|deliverables|go-live|go live|launch checklist|dashboard readiness)/ }
        ];

        const matched = phaseMatchers.find(entry => entry.pattern.test(text));
        return matched ? matched.index : null;
    }

    isPhase4RedoIntent(message = '') {
        return this.getRedoPhaseIntent(message) === 3;
    }

    syncCurrentPhaseIndex(phaseIndex) {
        if (!this.state.consultation_flow) this.state.consultation_flow = {};
        if (!this.consultation_data.consultation_flow) {
            const phaseNames = this.getConsultationPhaseNames();
            this.consultation_data.consultation_flow = {
                phases: phaseNames.map((name, idx) => `Phase ${idx + 1}: ${name}`),
                current_index: 0,
                status: 'in_progress',
                timestamps: {}
            };
        }

        this.state.consultation_flow.current_index = phaseIndex;
        this.state.consultation_flow.current_phase_index = phaseIndex;
        this.consultation_data.consultation_flow.current_index = phaseIndex;
        this.consultation_data.consultation_flow.current_phase_index = phaseIndex;
    }

    removeRecommendationPanels() {
        document.querySelectorAll('.vmo-recommendations-panel').forEach(node => {
            try { node.remove(); } catch (_) {}
        });
    }

    resetPhaseArtifactsFromIndex(phaseIndex) {
        this.consultation_data.phase_flags = this.consultation_data.phase_flags || {};
        this.state.phase_flags = this.state.phase_flags || {};
        this.state.approvals = this.state.approvals || {};
        this.consultation_data.approvals = this.consultation_data.approvals || {};

        const allFlagKeys = new Set([
            ...Object.keys(this.consultation_data.phase_flags),
            ...Object.keys(this.state.phase_flags || {})
        ]);

        allFlagKeys.forEach(flagKey => {
            const match = /^phase(\d+)_/i.exec(flagKey);
            if (!match) return;
            const phaseNumber = parseInt(match[1], 10); // 1-based
            if (Number.isInteger(phaseNumber) && phaseNumber >= (phaseIndex + 1)) {
                this.consultation_data.phase_flags[flagKey] = false;
                this.state.phase_flags[flagKey] = false;
            }
        });

        if (phaseIndex <= 2) {
            this.phase3ResearchRequested = false;
            this._deepResearchInProgress = false;
            this._awaitingResearchConfirmation = false;
            this.consultation_data.research_results = null;
            this.state.research_profile = {};

            document.querySelectorAll('.vmo-research-confirmation').forEach(node => {
                try { node.remove(); } catch (_) {}
            });
        }

        if (phaseIndex <= 3) {
            this.phase4ChecklistPresented = false;
            this.consultation_data.recommendation_checklist = null;
            this.consultation_data.recommendations_profile = null;
            this.state.recommendations_profile = {};

            this.state.approvals.recommendations = { accepted: [], rejected: [] };
            this.consultation_data.approvals.recommendations = { accepted: [], rejected: [] };
            this.removeRecommendationPanels();
        }

        if (phaseIndex <= 4) {
            this.state.website_plan = {};
            this.consultation_data.website_plan = {};
        }

        if (phaseIndex <= 5) {
            this.state.systems_plan = {};
            this.consultation_data.systems_plan = {};
            if (this.state.email_roles_meta && this.state.email_roles_meta.email_accounts) {
                this.state.email_roles_meta.email_accounts = {};
            }

            document.querySelectorAll('.vmo-phase6-email-wizard').forEach(node => {
                try { node.remove(); } catch (_) {}
            });
        }

        if (phaseIndex <= 6) {
            this._businessGenerationInProgress = false;
            this._draftWebsite = null;
            this._generatedWebsiteUrl = null;

            const generationProgress = document.getElementById('vmo-generation-progress');
            if (generationProgress) {
                try { generationProgress.remove(); } catch (_) {}
            }

            const refinementNotice = document.getElementById('vmo-refinement-notification');
            if (refinementNotice) {
                try { refinementNotice.remove(); } catch (_) {}
            }

            try {
                window.vmoDraftPostId = null;
                window.vmoDraftBusinessName = null;
                window.vmoPreviewIsLive = false;
            } catch (_) {}
        }

        this.state.roadmap_json = {};
        this.state.daily_tasks_json = {};
        this.consultation_data.roadmap_json = {};
        this.consultation_data.daily_tasks_json = {};
        this.state.approvals.artifacts_generated = false;
        this.consultation_data.approvals.artifacts_generated = false;
    }

    buildRedoPersistencePayload(phaseIndex) {
        return {
            consultation_flow: {
                current_index: phaseIndex,
                current_phase_index: phaseIndex
            },
            phase_flags: this.consultation_data.phase_flags || {},
            research_results: this.consultation_data.research_results || null,
            recommendation_checklist: this.consultation_data.recommendation_checklist || null,
            recommendations_profile: this.consultation_data.recommendations_profile || null,
            approvals: this.consultation_data.approvals || {},
            website_plan: this.consultation_data.website_plan || {},
            systems_plan: this.consultation_data.systems_plan || {},
            roadmap_json: this.consultation_data.roadmap_json || {},
            daily_tasks_json: this.consultation_data.daily_tasks_json || {}
        };
    }

    async triggerInternalPhasePrompt(prompt, fallbackMessage = '') {
        if (!prompt) return;
        if (this.isProcessing) {
            console.log('⏳ Skipping internal phase prompt because another message is processing.');
            return;
        }

        this.isProcessing = true;
        this._processingStartTime = Date.now();
        this.setVoiceStatus('thinking');
        this._showThinkingIndicator();

        try {
            const aiPayload = await this.getAIResponse(prompt);
            this._removeThinkingIndicator();

            let aiResponse = '';
            if (typeof aiPayload?.response === 'string' && aiPayload.response.trim().length) {
                aiResponse = aiPayload.response;
            } else if (typeof aiPayload?.assistant_message === 'string') {
                aiResponse = aiPayload.assistant_message;
            }

            if (aiPayload?.structured_delta) {
                this.applyStructuredDelta(aiPayload.structured_delta);
            }

            const hasCompleteMarker = typeof aiResponse === 'string' && aiResponse.includes('[[PHASE_COMPLETE]]');
            if (hasCompleteMarker) {
                aiResponse = aiResponse.replace(/\[\[PHASE_COMPLETE\]\]/g, '').trim();
            }

            const finalMessage = (aiResponse || fallbackMessage || '').trim();
            if (!aiPayload?._streamedToUI && finalMessage) {
                this.addMessageToUI('ai', finalMessage);
            }

            if (finalMessage) {
                this.conversationHistory.push({
                    role: 'assistant',
                    content: finalMessage,
                    timestamp: new Date().toISOString()
                });
                this.saveMessageToDatabase('ai', finalMessage);
            }

            if (hasCompleteMarker || !!aiPayload?.phase_advanced) {
                await this.advancePhase();
            }
        } catch (error) {
            this._removeThinkingIndicator();
            console.warn('Phase kickoff prompt failed:', error);
            if (fallbackMessage) {
                this.addMessageToUI('ai', fallbackMessage);
                this.saveMessageToDatabase('ai', fallbackMessage);
            }
        } finally {
            this.isProcessing = false;
            this._processingStartTime = null;
            this.setVoiceStatus('ready');
        }
    }

    async executePhaseRedo(phaseIndex, options = {}) {
        const phaseNames = this.getConsultationPhaseNames();
        const phaseName = phaseNames[phaseIndex] || `Phase ${phaseIndex + 1}`;
        const skipConfirm = options?.skipConfirm === true;

        if (!Number.isInteger(phaseIndex) || phaseIndex < 0 || phaseIndex >= phaseNames.length) {
            console.warn('Invalid phase index for redo:', phaseIndex);
            return;
        }

        console.log(`🔙 User requested to go back to Phase ${phaseIndex + 1}: ${phaseName}`);

        if (!skipConfirm) {
            const confirmed = confirm(`Go back to Phase ${phaseIndex + 1}: ${phaseName}?\n\nThis will restart the consultation from this phase. Data collected after this phase will be regenerated.`);
            if (!confirmed) {
                console.log('❌ User cancelled phase navigation');
                return;
            }
        }

        // Interrupt any in-flight conversational request before resetting phase state.
        if (this._consultStreamController) {
            try { this._consultStreamController.abort(); } catch (_) {}
            this._consultStreamController = null;
        }
        if (this._processingWatchdog) {
            try { clearTimeout(this._processingWatchdog); } catch (_) {}
            this._processingWatchdog = null;
        }
        this.isProcessing = false;
        this._processingStartTime = null;
        this.setVoiceStatus('ready');

        this.syncCurrentPhaseIndex(phaseIndex);
        this.resetPhaseArtifactsFromIndex(phaseIndex);

        this.state.phase_redo = {
            phase_index: phaseIndex,
            phase_name: phaseName,
            source: options?.source || 'manual',
            timestamp: new Date().toISOString()
        };

        this.refreshPhaseUI();
        this.refreshAllowedActions();
        this.enforcePhaseAdherence();

        const progressPct = Math.round((phaseIndex / 8) * 100);
        this.updateMainProgressBar(progressPct, `Phase ${phaseIndex + 1}: ${phaseName}`);
        this.addMessageToUI('system', `🔙 Returning to Phase ${phaseIndex + 1}: ${phaseName}. Re-running this phase from a clean checkpoint.`);

        if (window.vmo_consultant_context_v1) {
            await this.saveConsultationData(this.buildRedoPersistencePayload(phaseIndex));
        }

        await this.runPhaseRedoFlow(phaseIndex);
    }

    async runPhaseRedoFlow(phaseIndex) {
        switch (phaseIndex) {
            case 0:
                await this.redoBusinessFoundations();
                break;
            case 1:
                await this.redoExecutiveBlueprint();
                break;
            case 2:
                await this.redoDeepResearch();
                break;
            case 3:
                await this.redoStrategicRecommendations();
                break;
            case 4:
                await this.redoWebsiteArchitecture();
                break;
            case 5:
                await this.redoSystemsAndCompliance();
                break;
            case 6:
                await this.redoRoadmapAndExecution();
                break;
            case 7:
                await this.redoLaunchReadiness();
                break;
            default:
                break;
        }
    }

    // ✅ Go back to a specific phase and redo it
    async goToPhase(phaseIndex) {
        await this.executePhaseRedo(phaseIndex, { source: 'phase-sidebar' });
    }

    async redoBusinessFoundations() {
        this.updateMainProgressBar(5, 'Phase 1: Business Foundations · Ready');
        const fallback = 'We’re back in Phase 1. Let’s refresh your business foundations. What is the core business idea you want this website to support right now?';
        await this.triggerInternalPhasePrompt(
            'We are restarting Phase 1 (Business Foundations Intake). Use all known context, briefly acknowledge the reset, then ask ONE high-value intake question to refresh the business foundations.',
            fallback
        );
    }

    async redoExecutiveBlueprint() {
        this.updateMainProgressBar(15, 'Phase 2: Executive Blueprint · Regenerating summary');

        const hasDocumentContext = !!(this.consultation_data?.doc_locked || this.lastParsedDocument || this.blueprintRaw || this.blueprintSummary);
        if (hasDocumentContext) {
            this.lastDocumentSummarySignature = null;
            this.lastDocumentSummaryDocId = null;

            this.consultation_data.phase_flags = this.consultation_data.phase_flags || {};
            this.consultation_data.phase_flags.phase2_summary_presented = false;
            this.consultation_data.phase_flags.phase2_confirmed = false;

            this.state.phase_flags = this.state.phase_flags || {};
            this.state.phase_flags.phase2_summary_presented = false;
            this.state.phase_flags.phase2_confirmed = false;

            // Clear stale Phase 2 follow-up/question state before rebuilding.
            this.pendingDocumentQuestions = [];
            this.awaitingDocumentAnswer = null;
            this.documentFollowupActive = false;
            this._awaitingPhase2EmailRoles = false;

            const existingParsed = (this.lastParsedDocument && this.isPlainObject(this.lastParsedDocument))
                ? this.lastParsedDocument
                : {};
            const docExtracted = this.isPlainObject(existingParsed.extractedInfo)
                ? existingParsed.extractedInfo
                : {};
            const canonicalExtracted = this.isPlainObject(this.consultation_data?.extracted_information)
                ? this.consultation_data.extracted_information
                : {};

            // ✅ Phase 2 redo fix: always prioritize latest canonical extracted info,
            // while retaining doc-only fields that are not in canonical state yet.
            const refreshedExtracted = this.deepMerge(docExtracted, canonicalExtracted);
            const refreshedMissing = this.filterResolvedMissingItems(
                existingParsed.missingItems || this.lastDocMissingItems || []
            );

            const parsedDoc = {
                ...existingParsed,
                extractedInfo: refreshedExtracted,
                missingItems: refreshedMissing,
                isComplete: refreshedMissing.length === 0
            };

            await this.autoConfirmDocument(parsedDoc);
            return;
        }

        const fallback = 'Phase 2 restarted. I’m preparing your executive blueprint summary for confirmation now.';
        await this.triggerInternalPhasePrompt(
            'We are in Phase 2 (Executive Blueprint Confirmation). Rebuild a clean business summary from collected intake context and ask for confirmation in one concise prompt.',
            fallback
        );
    }

    async redoDeepResearch() {
        this.phase3ResearchRequested = false;
        this._deepResearchInProgress = false;
        this._awaitingResearchConfirmation = false;
        this.updateMainProgressBar(25, 'Phase 3: Deep Research · Awaiting confirmation');
        this.addMessageToUI('ai', '🔄 Phase 3 reset complete. I’m ready to run a fresh deep research scan with your latest context.');
        this.showResearchConfirmation();
    }

    async redoStrategicRecommendations() {
        const researchResults = this.consultation_data?.research_results || {};
        const hasResearch = researchResults && typeof researchResults === 'object' && Object.keys(researchResults).length > 0;

        if (!hasResearch) {
            this.addMessageToUI('ai', 'I need market research results before rebuilding recommendations. Let\'s run Phase 3 research first, then I\'ll regenerate the interactive checklist.');
            this.showResearchConfirmation();
            return;
        }

        this.phase4ChecklistPresented = false;
        this.syncCurrentPhaseIndex(3);
        this.updatePhaseProgressBar();
        this.updateMainProgressBar(50, 'Rebuilding recommendations checklist…');
        this.addMessageToUI('ai', '🔄 Rebuilding your Strategic Recommendations now with the interactive checklist interface.');

        await this.presentPhase4Checklist(researchResults);
    }

    async redoWebsiteArchitecture() {
        await this.onPhaseAdvanced(4);
    }

    async redoSystemsAndCompliance() {
        await this.onPhaseAdvanced(5);
    }

    async redoRoadmapAndExecution() {
        await this.onPhaseAdvanced(6);
    }

    async redoLaunchReadiness() {
        await this.onPhaseAdvanced(7);
        await this.populateDeliverables();
        const fallback = 'Phase 8 restarted. I have refreshed your deliverables and launch readiness view.';
        await this.triggerInternalPhasePrompt(
            'We are in Phase 8 (Launch Readiness & Deliverables). Present a concise launch-readiness checklist and summarize what is ready now, based on current generated assets and deliverables.',
            fallback
        );
    }

    updatePhaseProgressBar() {
        const progressEl = document.getElementById('vmo-wizard-progress');
        const textEl = document.getElementById('vmo-wizard-progress-text');
        if (!progressEl) {
            console.warn('⚠️ Progress bar element not found');
            return;
        }
        
        // Get phase info from both state locations
        const flow = this.state.consultation_flow || {};
        const cdFlow = this.consultation_data.consultation_flow || {};
        const total = 8; // Fixed 8 phases
        
        // ✅ FIX: Check both state locations for current phase
        const currentIdx = typeof flow.current_phase_index === 'number' 
            ? flow.current_phase_index 
            : (typeof cdFlow.current_index === 'number' ? cdFlow.current_index : 0);
        
        // ═══════════════════════════════════════════════════════════════════════════════
        // ✅ IMPROVED PROGRESS CALCULATION
        // Phase 1 starts at 0%, each completed phase adds ~12.5%
        // This prevents showing 13% for a brand new consultation
        // ═══════════════════════════════════════════════════════════════════════════════
        let pct;
        if (currentIdx === 0) {
            // Phase 1 - Show progress based on how much info has been collected
            const inPhaseProgress = this.estimatePhaseProgress(0);
            pct = Math.round(inPhaseProgress * 12); // 0-12% for Phase 1
        } else {
            // Phases 2-8: Each completed phase = 12.5%, plus in-phase progress
            const completedPhasesPct = currentIdx * 12.5;
            const inPhaseProgress = this.estimatePhaseProgress(currentIdx);
            pct = Math.round(completedPhasesPct + (inPhaseProgress * 12.5));
        }
        
        // Ensure minimum visibility and cap at 100
        pct = Math.max(0, Math.min(100, pct));
        
        // Update the fill bar
        progressEl.style.width = pct + '%';
        progressEl.style.transition = 'width 0.5s ease';
        
        // Phase names for display
        const phaseNames = [
            'Business Foundations',
            'Executive Blueprint',
            'Deep Research',
            'Recommendations',
            'Website Architecture',
            'Systems & Compliance',
            'Roadmap & Tasks',
            'Launch Readiness'
        ];
        
        if (textEl) {
            const phaseName = phaseNames[currentIdx] || `Phase ${currentIdx + 1}`;
            textEl.textContent = `Phase ${currentIdx + 1}: ${phaseName} · ${pct}%`;
        }
        
        console.log(`📊 Phase progress updated: Phase ${currentIdx + 1}, ${pct}%`);
    }

    estimatePhaseProgress(currentIndex = 0) {
        if (currentIndex === 0) {
            const questions = this.getDocumentIntakeQuestions();
            if (!questions.length) return 0;
            const source = this.getDisplayExtractedPayload();
            let answered = 0;
            questions.forEach(item => {
                const { hasAnswer } = this.extractAnswerForField(source, item.field);
                if (hasAnswer) answered++;
            });
            return Math.min(0.95, answered / questions.length);
        }
        if (currentIndex === 1) {
            return this.consultation_data?.phase_flags?.phase2_confirmed ? 0.95 : 0.25;
        }
        return 0;
    }

    deepMerge(target, source) {
        if (Array.isArray(source)) {
            return source.slice();
        }
        const output = { ...(target || {}) };
        Object.keys(source || {}).forEach(key => {
            const value = source[key];
            if (value && typeof value === 'object' && !Array.isArray(value)) {
                output[key] = this.deepMerge(output[key] || {}, value);
            } else {
                output[key] = Array.isArray(value) ? value.slice() : value;
            }
        });
        return output;
    }
    
    // Check if user has existing business/website
    async hasExistingBusiness() {
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_check_existing_business',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce
                })
            });

            const data = await response.json();
            if (data.success && data.data) {
                return {
                    exists: data.data.has_business || false,
                    businessName: data.data.business_name || null,
                    projectCount: data.data.project_count || 0,
                    lastProject: data.data.last_project || null
                };
            }
        } catch (e) {
            console.warn('Could not check existing business:', e);
            // Handle invalid JSON or unexpected responses gracefully
            if (e instanceof SyntaxError) {
                console.error('Invalid JSON response:', e.message);
            }
        }
        return { exists: false };
    }

    normalizePhoneProvisioningStatus(rawStatus = {}) {
        const hasPhone = !!(rawStatus && rawStatus.has_phone);
        const allowedRaw = Number.parseInt(rawStatus?.allowed_phone_count, 10);
        const allowedPhoneCount = Number.isFinite(allowedRaw) && allowedRaw > 0 ? allowedRaw : 1;
        const canAddAdditional = !!rawStatus?.can_add_additional || allowedPhoneCount > 1;
        const phoneNumber = (rawStatus?.phone_number || '').toString().trim();
        const phoneFormatted = (rawStatus?.phone_formatted || phoneNumber).toString().trim();
        const policyMessage = (rawStatus?.policy_message || '').toString().trim()
            || (canAddAdditional
                ? `Your account is approved for up to ${allowedPhoneCount} business phone numbers.`
                : 'VMO policy allows 1 business phone number per user by default.');
        const exceptionMessage = (rawStatus?.exception_message || '').toString().trim()
            || 'Need more than one number? Contact VMO directly for an approved exception.';

        return {
            has_phone: hasPhone,
            phone_number: phoneNumber,
            phone_formatted: phoneFormatted,
            allowed_phone_count: allowedPhoneCount,
            can_add_additional: canAddAdditional,
            policy_message: policyMessage,
            exception_message: exceptionMessage,
            checked_at: new Date().toISOString()
        };
    }

    getPhoneProvisioningStatus() {
        const fromState = this.state?.phone_provisioning_status;
        if (fromState && typeof fromState === 'object') {
            return fromState;
        }
        const fromConsultation = this.consultation_data?.phone_provisioning_status;
        if (fromConsultation && typeof fromConsultation === 'object') {
            return fromConsultation;
        }
        return null;
    }

    applyPhoneStatusToConsultationContext(status = null) {
        const normalized = status && typeof status === 'object'
            ? status
            : this.getPhoneProvisioningStatus();
        if (!normalized || typeof normalized !== 'object') {
            return;
        }

        this.state = this.state || this.buildInitialState();
        this.state.phone_provisioning_status = normalized;
        this.consultation_data.phone_provisioning_status = normalized;

        if (normalized.has_phone) {
            const info = this.consultation_data.extracted_information || {};
            if (!info.operations || typeof info.operations !== 'object') {
                info.operations = {};
            }
            if (!info.operations.phone) {
                info.operations.phone = normalized.phone_formatted || normalized.phone_number || 'Business phone already provisioned in dashboard';
            }
            info.needsPhoneSystem = false;
            this.consultation_data.extracted_information = info;
            this.collectedInformation = info;
        }

        const existingProfile = this.state?.recommendations_profile;
        if (existingProfile && typeof existingProfile === 'object' && Object.keys(existingProfile).length > 0) {
            const adjustedProfile = this.applyPhoneRecommendationPolicy(existingProfile);
            this.state.recommendations_profile = adjustedProfile;
            this.consultation_data.recommendations_profile = adjustedProfile;
        }
    }

    async refreshPhoneProvisioningStatus(options = {}) {
        const { force = false, persist = false } = options;
        const existing = this.getPhoneProvisioningStatus();
        const checkedAt = existing?.checked_at ? Date.parse(existing.checked_at) : NaN;
        const isFresh = Number.isFinite(checkedAt) && (Date.now() - checkedAt) < 120000;
        if (!force && existing && isFresh) {
            return existing;
        }

        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url || '/wp-admin/admin-ajax.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                credentials: 'same-origin',
                body: new URLSearchParams({
                    action: 'vmo_get_phone_provision_status',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce
                })
            });

            let data = null;
            try {
                data = await response.json();
            } catch (_) {
                return existing || null;
            }

            if (data?.success && data.data) {
                const normalized = this.normalizePhoneProvisioningStatus(data.data);
                this.applyPhoneStatusToConsultationContext(normalized);
                if (persist && window.vmo_consultant_context_v1) {
                    this.saveConsultationData({ phone_provisioning_status: normalized });
                }
                return normalized;
            }
        } catch (err) {
            console.warn('Could not refresh phone provisioning status:', err);
        }

        return existing || null;
    }
    
    async init() {
        console.log('✅ Intelligent Consultation initialized');
        this.preloadGreetingAudio();
        this.setupVoiceRecognition();
        this.setupVoiceSynthesis();
        this.bindEvents();
        this.setupQuoteFeature();
        this.refreshProvisioningUI();
        await this.bootstrapSessionState();
        await this.refreshPhoneProvisioningStatus({ force: true });
    }
    
    setupVoiceRecognition() {
        if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
            const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
            this.recognition = new SpeechRecognition();
            this.recognition.continuous = true;
            this.recognition.interimResults = true; // Enable interim results for barge-in detection
            try {
                const defaultLang = (document && document.documentElement && document.documentElement.lang)
                    ? document.documentElement.lang
                    : (navigator.language || 'en-US');
                this.recognition.lang = defaultLang || 'en-US';
            } catch (_) {
                this.recognition.lang = 'en-US';
            }

            this.recognition.onresult = (event) => {
                // Prevent self-loop: never process recognition results while the AI is speaking.
                // Barge-in is handled via microphone VAD monitoring during TTS.
                if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) {
                    return;
                }
                // If voice mode is off (e.g., user pressed End), ignore any late results.
                if (!this.isVoiceEnabled) {
                    return;
                }
                
                // ✅ SELF-LOOP PREVENTION: Cooldown check after TTS ends
                // TTS audio may still be resonating/echoing
                if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < this._ttsEndCooldownMs)) {
                    console.log('🔇 Ignoring speech input - TTS cooldown period');
                    return;
                }

                // ✅ SELF-LOOP PREVENTION: Check if transcript matches what the AI just said
                // This catches cases where echoCancellation fails and the mic picks up TTS output
                if (this._lastSpokenText) {
                    const results = event.results;
                    let candidateText = '';
                    for (let i = event.resultIndex; i < results.length; i++) {
                        candidateText += (results[i][0]?.transcript || '').trim() + ' ';
                    }
                    candidateText = candidateText.trim().toLowerCase();
                    const lastSpoken = (this._lastSpokenText || '').toLowerCase();
                    if (candidateText.length > 10 && lastSpoken.length > 10) {
                        // Check if the recognized text is a substring of what the AI just said
                        if (lastSpoken.includes(candidateText) || candidateText.includes(lastSpoken.substring(0, Math.min(80, lastSpoken.length)))) {
                            console.log('🔇 SELF-LOOP BLOCKED: Transcript matches AI spoken text — ignoring');
                            return;
                        }
                    }
                }
                
                // 🎯 BARGE-IN SUPPORT: While AI is speaking, use recognition ONLY to detect interruption.
                // Do NOT enqueue transcripts during TTS (prevents AI self-loop if recognition hears TTS audio).
                if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) {
                    const results = event.results;
                    let candidate = '';
                    for (let i = event.resultIndex; i < results.length; i++) {
                        const transcript = (results[i][0] && results[i][0].transcript)
                            ? results[i][0].transcript.trim()
                            : '';
                        if (transcript) {
                            candidate = transcript;
                            // Prefer interim speech for quick barge-in detection
                            if (!results[i].isFinal) break;
                        }
                    }

                    if (candidate && this.shouldTriggerBargeIn(candidate)) {
                        console.log('🔊 BARGE-IN DETECTED: User interruption during AI speech');
                        this._bargeInTriggeredAt = Date.now();
                        this.interruptSpeakingAndResume('barge-in');
                    }
                    return;
                }

                const results = event.results;
                let finalTranscript = '';
                let interimTranscript = '';

                // ✅ LIVE TRANSCRIPT: Collect both final and interim results
                for (let i = event.resultIndex; i < results.length; i++) {
                    const transcript = (results[i][0] && results[i][0].transcript) ? results[i][0].transcript : '';
                    if (results[i].isFinal) {
                        finalTranscript += transcript;
                        this._transcriptBuffer += transcript;
                        this._interimTranscript = '';
                    } else {
                        interimTranscript += transcript;
                        this._interimTranscript = interimTranscript;
                    }
                }
                
                // ✅ LIVE-TO-INPUT: Show transcription directly in the wizard input box
                this.updateLiveTranscript(this._transcriptBuffer + this._interimTranscript, 'user');
                // Populate the input field — BUT respect manual edits.
                // If the user has manually edited the text, only append NEW speech
                // instead of overwriting their corrections.
                const wizInput = this.chatInput || document.querySelector('#vmo-wizard-response');
                if (wizInput) {
                    if (this._userManuallyEdited && finalTranscript) {
                        // User edited the field — append only the new final transcript
                        const curVal = wizInput.value;
                        wizInput.value = (curVal + ' ' + finalTranscript).trim();
                        // Sync accumulated transcript with the actual input value
                        this._accumulatedTranscript = wizInput.value;
                        // Reset the flag so subsequent speech flows normally
                        // (it will be set again if user types/edits)
                        this._userManuallyEdited = false;
                    } else if (!this._userManuallyEdited) {
                        // No manual edits — safe to overwrite with full transcript
                        wizInput.value = (this._transcriptBuffer + this._interimTranscript).trim();
                    }
                    // If user manually edited and no new final transcript, leave value alone
                    wizInput.scrollTop = wizInput.scrollHeight;
                }

                // ✅ INTERIM SPEECH DETECTION: Reset debounce when user is still talking
                // Even without finalTranscript, interim results prove the user hasn't stopped
                if (interimTranscript && interimTranscript.trim()) {
                    if (this._speechDebounceTimer) {
                        clearTimeout(this._speechDebounceTimer);
                        this._speechDebounceTimer = null;
                    }
                }

                if (finalTranscript.trim()) {
                    // ✅ BARGE-IN: If AI is streaming, abort the stream so user can interrupt
                    if (this._consultStreamController) {
                        console.log('🗣️ Consultation barge-in: user spoke while AI was streaming — aborting stream');
                        try { this._consultStreamController.abort(); } catch(_) {}
                    }

                    // ✅ FIX: Debounce speech — after silence, finalize text in input (no auto-send)
                    // User reviews and clicks Send manually
                    
                    // Track when user started speaking (for 3-minute sessions)
                    if (!this._speechStartTime) {
                        this._speechStartTime = Date.now();
                        console.log('🎤 User started speaking - allowing up to 3 minutes');
                    }
                    
                    this._accumulatedTranscript = (this._accumulatedTranscript + ' ' + finalTranscript).trim();
                    const transcriptPreview = this._accumulatedTranscript.length > 100 
                        ? this._accumulatedTranscript.substring(0, 100) + '...' 
                        : this._accumulatedTranscript;
                    const elapsedSeconds = Math.round((Date.now() - this._speechStartTime) / 1000);
                    console.log(`🎤 [${elapsedSeconds}s] Accumulated: ${transcriptPreview}`);
                    
                    // Clear any existing debounce timer
                    if (this._speechDebounceTimer) {
                        clearTimeout(this._speechDebounceTimer);
                    }
                    
                    // Check if we've hit the 3-minute max (safety limit, but generous)
                    const speechDuration = Date.now() - this._speechStartTime;
                    if (speechDuration >= this._maxSpeechDurationMs) {
                        console.log('⏰ 3-minute speech limit reached - auto-sending to AI');
                        // Use input field value (includes manual edits) or fallback to accumulated
                        const inputEl = this.chatInput || document.querySelector('#vmo-wizard-response');
                        const messageToSend = (inputEl ? inputEl.value.trim() : '') || this._accumulatedTranscript.trim();
                        this._accumulatedTranscript = '';
                        this._speechDebounceTimer = null;
                        this._speechStartTime = null;
                        this._userManuallyEdited = false;
                        // Clear live transcript when message is finalized
                        this._transcriptBuffer = '';
                        this._interimTranscript = '';
                        this.clearLiveTranscript();
                        
                        if (messageToSend) {
                            // Show message in input briefly then auto-send
                            if (wizInput) wizInput.value = '';
                            this.handleUserMessage(messageToSend, true);
                        }
                        return;
                    }
                    
                    // Set new debounce timer — after 3.5s of silence, auto-send to AI
                    this._speechDebounceTimer = setTimeout(() => {
                        // ✅ FIX: Use the INPUT FIELD value as source of truth.
                        // If the user manually edited the transcription, their edits
                        // are in wizInput.value — NOT in _accumulatedTranscript.
                        const inputEl = this.chatInput || document.querySelector('#vmo-wizard-response');
                        const messageToSend = (inputEl ? inputEl.value.trim() : '') || this._accumulatedTranscript.trim();
                        
                        // Capture duration BEFORE clearing start time to log correctly
                        const duration = this._speechStartTime ? Math.round((Date.now() - this._speechStartTime) / 1000) : 0;
                        
                        this._accumulatedTranscript = '';
                        this._speechDebounceTimer = null;
                        this._speechStartTime = null;
                        this._userManuallyEdited = false;
                        // Clear live transcript
                        this._transcriptBuffer = '';
                        this._interimTranscript = '';
                        this.clearLiveTranscript();
                        
                        if (messageToSend) {
                            console.log(`🎯 Auto-sending after ${duration}s of speech: ${messageToSend.substring(0, 100)}...`);
                            // Clear input and auto-send to AI (voice flow: listen → think → respond → listen)
                            if (wizInput) wizInput.value = '';
                            this.handleUserMessage(messageToSend, true);
                        }
                    }, this._speechDebounceMs);
                }
            };

            this.recognition.onerror = (event) => {
                console.error('Speech recognition error:', event.error);
                if (event.error === 'no-speech') {
                    console.warn('⚠️ No speech detected. Please try speaking again.');
                    if (window.showToast) {
                        window.showToast('No speech detected. Please try speaking again.', 'warning');
                    }
                } else if (event.error === 'audio-capture') {
                    console.error('❌ Microphone blocked!');
                    if (window.showToast) {
                        window.showToast('⚠️ Microphone blocked! Please allow microphone access in your browser.', 'error');
                    }
                    alert('MICROPHONE BLOCKED!\n\nYour browser is blocking microphone access.\n\nPlease:\n1. Click the 🔒 lock icon in the address bar\n2. Change microphone permission to "Allow"\n3. Reload the page\n\nOn Mac: Also check System Settings > Privacy & Security > Microphone');
                    // Disable voice to prevent infinite retry loop
                    this.isVoiceEnabled = false;
                    return;
                } else if (event.error === 'not-allowed' || event.error === 'permission-denied') {
                    console.error('❌ Microphone permission denied!');
                    if (window.showToast) {
                        window.showToast('⚠️ Microphone permission denied! Please allow access.', 'error');
                    }
                    alert('MICROPHONE PERMISSION DENIED!\n\nPlease allow microphone access:\n1. Click the 🔒 lock icon in the address bar\n2. Change microphone permission to "Allow"\n3. Reload the page\n\nOn Mac: Also check System Settings > Privacy & Security > Microphone');
                    // Disable voice to prevent infinite retry loop
                    this.isVoiceEnabled = false;
                    return;
                } else {
                    console.error('❌ Speech recognition encountered an error:', event.error);
                }

                // Restart recognition if voice remains enabled (only for recoverable errors)
                // Avoid restarting while an AI turn is processing.
                if (this.isVoiceEnabled && !this.isSpeaking && !(window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking) && !this.isProcessing) {
                    setTimeout(() => this.safeStartRecognition('recoverable_error'), 600);
                }
            };

            this.recognition.onstart = () => {
                this._recognitionActive = true;
                this._recognitionStarting = false;
                this.setVoiceStatus('listening');
                // Reuse Jarvis modal for uniformity
                this.showModalPhase('listening');
                try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true }); } catch(_) {}
            };

            this.recognition.onend = () => {
                this._recognitionActive = false;
                this._recognitionStarting = false;
                try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic('consultation'); } catch(_) {}
                // IMPORTANT: Do not hide the listening modal while we're processing.
                // We intentionally stop recognition during processing; onend will fire and
                // previously caused the UI to flicker/disappear.
                if (!this.isVoiceEnabled) {
                    if (!this.isSpeaking && !(window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) {
                        this.setVoiceStatus('ready');
                        this.hideModal();
                    }
                    return;
                }
                if (this.isProcessing) {
                    this.setVoiceStatus('thinking');
                    this.showModalPhase('thinking');
                    return;
                }
                if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) {
                    // speak() will restart mic when appropriate
                    return;
                }

                // ✅ FIX: Reset debounce timer during recognition restart gap.
                // Chrome periodically ends and restarts recognition sessions.
                // Without this, the debounce timer could fire during the ~500ms
                // gap, cutting off the user mid-sentence.
                if (this._speechDebounceTimer && this._accumulatedTranscript) {
                    clearTimeout(this._speechDebounceTimer);
                    this._speechDebounceTimer = setTimeout(() => {
                        const messageToSend = (this._accumulatedTranscript || '').trim();
                        const duration = this._speechStartTime ? Math.round((Date.now() - this._speechStartTime) / 1000) : 0;
                        this._accumulatedTranscript = '';
                        this._speechDebounceTimer = null;
                        this._speechStartTime = null;
                        this._transcriptBuffer = '';
                        this._interimTranscript = '';
                        this.clearLiveTranscript();
                        if (messageToSend) {
                            console.log(`🎯 Auto-sending after ${duration}s of speech (onend reset): ${messageToSend.substring(0, 100)}...`);
                            const wizInput = this.chatInput || document.querySelector('#vmo-wizard-response');
                            if (wizInput) wizInput.value = '';
                            this.handleUserMessage(messageToSend, true);
                        }
                    }, this._speechDebounceMs);
                }

                setTimeout(() => {
                    if (this.isVoiceEnabled && !this.isSpeaking && !(window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking) && !this.isProcessing) {
                        this.safeStartRecognition('onend_restart');
                    }
                }, 500);
            };
        }

        // -------- Voice helpers (prevents self-loop + double-start) --------
        this.safeStartRecognition = (reason = '') => {
            if (!this.recognition) return;
            if (!this.isVoiceEnabled) return;
            const normalizedReason = String(reason || '').toLowerCase();
            const bypassTtsCooldown = normalizedReason.includes('barge') || normalizedReason.includes('interrupt');
            // ✅ FIX: Don't start recognition if mic is muted by user
            if (this._micMutedByUser || window.vmoMuteConsultation) {
                console.log('🔇 Recognition blocked: microphone is muted');
                return;
            }
            if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) return;
            if (this.isProcessing) return;
            // ✅ SELF-LOOP PREVENTION: Don't start mic during TTS cooldown period
            if (!bypassTtsCooldown && this._ttsEndedAt && (Date.now() - this._ttsEndedAt < (this._ttsEndCooldownMs || 1800))) {
                console.log('🔇 Recognition blocked: TTS cooldown active (' + reason + ')');
                // Retry after cooldown expires
                const remaining = (this._ttsEndCooldownMs || 1800) - (Date.now() - this._ttsEndedAt) + 100;
                setTimeout(() => this.safeStartRecognition(reason + '_retry'), remaining);
                return;
            }
            if (this._recognitionActive || this._recognitionStarting) return;

            this._recognitionStarting = true;
            try {
                this.recognition.start();
            } catch (e) {
                // If it was already started, mark as active and move on.
                const msg = (e && e.message) ? e.message.toLowerCase() : '';
                if (msg.includes('already started')) {
                    this._recognitionActive = true;
                }
                this._recognitionStarting = false;
            }
        };

        this.safeStopRecognition = (reason = '') => {
            if (!this.recognition) return;
            if (!this._recognitionActive && !this._recognitionStarting) return;
            try { this.recognition.stop(); } catch (_) {}
            this._recognitionStarting = false;
            // _recognitionActive will be flipped in onend
        };

        // -------- VAD-based barge-in (no listening while AI speaks) --------
        this._vad = {
            stream: null,
            audioCtx: null,
            analyser: null,
            rafId: null,
            aboveMs: 0,
            lastT: 0,
            threshold: 0.05,   // RMS-ish normalized threshold
            holdMs: 140,        // how long above threshold before we treat it as speech
            enabled: true,
        };

        this._ensureVadStream = async () => {
            // ✅ BARGE-IN FIX: Check if cached stream is still active
            if (this._vad.stream) {
                const tracks = this._vad.stream.getAudioTracks();
                if (tracks.length > 0 && tracks[0].readyState === 'live') {
                    return this._vad.stream;
                }
                // Stream died — clear it so we get a fresh one
                this._vad.stream = null;
            }
            const constraints = {
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true
                }
            };
            this._vad.stream = await navigator.mediaDevices.getUserMedia(constraints);
            return this._vad.stream;
        };

        this.startVADBargeIn = async () => {
            if (!this._vad.enabled) return;
            if (!this.isVoiceEnabled) return;
            // ✅ FIX: Don't start VAD if mic is muted by user
            if (this._micMutedByUser || window.vmoMuteConsultation) {
                console.log('🔇 VAD blocked: microphone is muted');
                return;
            }
            try {
                const stream = await this._ensureVadStream();
                if (!this._vad.audioCtx) {
                    this._vad.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
                }
                if (this._vad.audioCtx.state === 'suspended') {
                    try { await this._vad.audioCtx.resume(); } catch (_) {}
                }
                const source = this._vad.audioCtx.createMediaStreamSource(stream);
                this._vad.analyser = this._vad.audioCtx.createAnalyser();
                this._vad.analyser.fftSize = 1024;
                source.connect(this._vad.analyser);

                const data = new Uint8Array(this._vad.analyser.fftSize);
                this._vad.aboveMs = 0;
                this._vad.lastT = performance.now();

                // ✅ BARGE-IN FIX: Reset adaptive baseline for fresh calibration
                this._vadBaselineRms = 0;
                this._vadBaselineReady = false;
                this._vadCalibrationFrames = 0;
                let _vadDebugCounter = 0; // periodic debug logging

                console.log('🎤 VAD barge-in started — listening for user interruption (threshold:', this._vadThreshold, 'hold:', this._vadHoldMs, 'ms)');

                const tick = () => {
                    if (!this.isSpeaking) return; // stop sampling if we are not speaking
                    this._vad.analyser.getByteTimeDomainData(data);
                    // Compute a rough RMS level
                    let sum = 0;
                    for (let i = 0; i < data.length; i++) {
                        const v = (data[i] - 128) / 128;
                        sum += v * v;
                    }
                    const rms = Math.sqrt(sum / data.length);
                    const now = performance.now();
                    const dt = now - this._vad.lastT;
                    this._vad.lastT = now;

                    // ✅ ADAPTIVE BASELINE: Track the ambient noise floor during TTS
                    // First ~40 frames (~660ms) calibrate the baseline — captures TTS at volume
                    const calibrationTargetFrames = Math.max(6, this._vadCalibrationTargetFrames || 18);
                    if (this._vadCalibrationFrames < calibrationTargetFrames) {
                        this._vadBaselineRms = this._vadCalibrationFrames === 0
                            ? rms
                            : (this._vadBaselineRms * this._vadCalibrationFrames + rms) / (this._vadCalibrationFrames + 1);
                        this._vadCalibrationFrames++;
                        if (this._vadCalibrationFrames >= calibrationTargetFrames) {
                            this._vadBaselineReady = true;
                            console.log('🎤 VAD baseline calibrated:', this._vadBaselineRms.toFixed(4));
                        }
                        this._vad.rafId = requestAnimationFrame(tick);
                        return;
                    } else {
                        // Slow exponential moving average to track drifting ambient level
                        this._vadBaselineRms = this._vadBaselineRms * 0.997 + rms * 0.003;
                    }

                    // ✅ DUAL-THRESHOLD DETECTION:
                    // 1. Absolute threshold — catches clear user speech
                    // 2. Adaptive threshold — catches user speech above ambient/residual TTS
                    // Keep the threshold above ambient bleed, but let clear user speech cut in quickly.
                    const adaptiveThreshold = Math.min(0.12, Math.max(this._vadThreshold, this._vadBaselineRms * 1.8));
                    const isVoiceDetected = rms > adaptiveThreshold;

                    if (isVoiceDetected) {
                        this._vad.aboveMs += dt;
                    } else {
                        this._vad.aboveMs = Math.max(0, this._vad.aboveMs - dt * 0.5);
                    }

                    // Debug logging every ~2 seconds (120 frames at 60fps)
                    _vadDebugCounter++;
                    if (_vadDebugCounter % 120 === 0) {
                        console.log('🎤 VAD:', {
                            rms: rms.toFixed(4),
                            baseline: this._vadBaselineRms.toFixed(4),
                            adaptiveThreshold: adaptiveThreshold.toFixed(4),
                            aboveMs: this._vad.aboveMs.toFixed(0),
                            holdMs: this._vadHoldMs
                        });
                    }

                    if (this._vad.aboveMs >= this._vadHoldMs) {
                        console.warn('🔊 BARGE-IN DETECTED (VAD): stopping AI speech — rms:', rms.toFixed(4), 'baseline:', this._vadBaselineRms.toFixed(4));
                        this._vad.aboveMs = 0;
                        // Stop speech immediately and start recognition to capture the user
                        this.stopSpeaking();
                        setTimeout(() => this.safeStartRecognition('barge_in_vad'), 60);
                        return;
                    }

                    this._vad.rafId = requestAnimationFrame(tick);
                };

                if (this._vad.rafId) cancelAnimationFrame(this._vad.rafId);
                this._vad.rafId = requestAnimationFrame(tick);
            } catch (e) {
                // If we can't get mic stream for VAD, we fall back to push-to-talk only.
                console.warn('⚠️ VAD barge-in unavailable:', e);
            }
        };

        this.stopVADBargeIn = () => {
            if (this._vad.rafId) {
                cancelAnimationFrame(this._vad.rafId);
                this._vad.rafId = null;
            }
            this._vad.aboveMs = 0;
            // We keep the stream open for speed (re-using permissions).
        };
    }
    
    setupVoiceSynthesis() {
        if ('speechSynthesis' in window) {
            this.synthesis = window.speechSynthesis;
        }
    }
    
    bindEvents() {
        // Business Wizard modal input elements
        const chatInput = document.querySelector('#vmo-wizard-response');
        const sendButton = document.querySelector('#vmo-wizard-send');
        const voiceButton = document.querySelector('#vmo-wizard-voice');
        const muteButton = document.querySelector('#vmo-wizard-mute');
        const transcribeButton = document.querySelector('#vmo-wizard-transcribe');
        const videoButton = document.querySelector('#vmo-wizard-video');

    // Persist references for use in other methods (e.g., transcribeOnce)
        this.chatInput = chatInput;
        this.voiceButton = voiceButton;
        this.muteButton = muteButton;
        this.transcribeButton = transcribeButton;
    this.statusEl = document.getElementById('vmo-wizard-voice-status');
    this.statusDot = document.getElementById('vmo-wizard-voice-status-dot');
    this.statusText = document.getElementById('vmo-wizard-voice-status-text');
        
        console.log('🔧 Binding events to wizard elements:', {
            chatInput: chatInput ? 'Found' : 'NOT FOUND',
            sendButton: sendButton ? 'Found' : 'NOT FOUND',
            voiceButton: voiceButton ? 'Found' : 'NOT FOUND'
        });
        
        // Remove existing event listeners by cloning and replacing
        if (chatInput && !chatInput.dataset.vmoEventsbound) {
            chatInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                    // ✅ FIX: Stop TTS on Enter key
                    try { this._progressiveTTSAbort(); } catch(_) {}
                    e.preventDefault();
                    this.sendUserMessage();
                }
            });

            // ✅ Detect manual edits to the input field.
            // When the user types/pastes/edits the transcribed text, set a flag
            // so the speech recognition handler stops overwriting their changes.
            chatInput.addEventListener('input', (e) => {
                // Only flag as manual edit if speech recognition is active
                // (i.e. user is editing transcribed text while mic is on)
                if (this._recognitionActive || this._transcriptBuffer || this._accumulatedTranscript) {
                    this._userManuallyEdited = true;
                }
            });
            chatInput.dataset.vmoEventsbound = 'true';
            console.log('✅ Chat input Enter key bound');
        }
        
        if (sendButton && !sendButton.dataset.vmoEventsbound) {
            sendButton.addEventListener('click', () => {
                console.log('📤 Send button clicked');
                // ✅ FIX: Stop TTS on Send click
                try { this._progressiveTTSAbort(); } catch(_) {}
                this.sendUserMessage();
            });
            sendButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Send button click bound');
        }
        
        if (voiceButton && !voiceButton.dataset.vmoEventsbound) {
            voiceButton.addEventListener('click', () => {
                console.log('🎤 Voice button clicked');
                this.toggleVoice();
            });
            voiceButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Voice button click bound');
        }

        if (muteButton && !muteButton.dataset.vmoEventsbound) {
            muteButton.addEventListener('click', () => {
                this._micMutedByUser = !this._micMutedByUser;
                if (this._micMutedByUser) {
                    muteButton.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1;color:#ff4444">🔇</span>';
                    muteButton.title = "Unmute Microphone";
                    try { this.stopVADBargeIn(); } catch(_) {}
                    try { if (this.recognition) this.recognition.stop(); } catch(_) {}
                    console.log('🔇 Mic Muted');
                } else {
                    muteButton.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1">🎙️</span>';
                    muteButton.title = "Mute Microphone";
                    console.log('🔊 Mic Unmuted');
                    if (this.isVoiceEnabled && this.isSpeaking) {
                        try { this.startVADBargeIn(); } catch(_) {}
                    } else if (this.isVoiceEnabled && !this.isSpeaking) {
                        try { this.safeStartRecognition('unmute'); } catch(_) {}
                    }
                }
            });
            muteButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Mute button click bound');
        }

        // Transcribe button (voice-to-text, insert into input)
        if (transcribeButton && !transcribeButton.dataset.vmoEventsbound) {
            transcribeButton.addEventListener('click', () => {
                console.log('💬 Transcribe button clicked');
                // If already transcribing, stop it (privacy control)
                if (this.activeTranscription) {
                    console.log('🛑 Stopping active transcription - user cancelled');
                    try {
                        this.activeTranscription.stop();
                        this.activeTranscription.abort();
                    } catch(e) {}
                    this.activeTranscription = null;
                    if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic) {
                        window.VMOAudioCoordinator.releaseMic('consultation');
                    }
                    if (this.transcribeButton) {
                        this.transcribeButton.classList.remove('active');
                        this.transcribeButton.style.backgroundColor = '';
                        this.transcribeButton.style.color = '';
                    }
                    if (window.showToast) {
                        window.showToast('Transcription cancelled', 'info');
                    }
                } else {
                    // Start new transcription (populate input only, user sends manually)
                    this.transcribeOnce({ autoSend: false });
                }
            });
            transcribeButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Transcribe button click bound');
        }

        // Video upload button (filter to video types)
        if (videoButton && !videoButton.dataset.vmoEventsbound) {
            try { videoButton.type = 'button'; } catch (_) {}
            videoButton.addEventListener('click', (e) => {
                try { e.preventDefault(); e.stopPropagation(); } catch (_) {}
                console.log('📷 Camera button clicked');
                this.openCameraCapture();
            });
            videoButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Video button click bound');
        }

        // Upload button - stages files for attachment (ChatGPT-style)
        const uploadButton = document.querySelector('#vmo-wizard-upload');
        if (uploadButton && !uploadButton.dataset.vmoEventsbound) {
            try { uploadButton.type = 'button'; } catch (_) {}
            uploadButton.addEventListener('click', (e) => {
                try { e.preventDefault(); e.stopPropagation(); } catch (_) {}
                console.log('📎 Upload button clicked - opening file picker for staging');
                this.openFilePickerForStaging();
            });
            uploadButton.dataset.vmoEventsbound = 'true';
            console.log('✅ Upload button click bound (staging mode)');
        }
        
        // Store references for later cleanup
        this.chatInput = chatInput;
        this.sendButton = sendButton;
        this.voiceButton = voiceButton;
        this.uploadButton = uploadButton;
        this.transcribeButton = transcribeButton;
        this.videoButton = videoButton;

        // Listen for global mic revoke
        try {
            window.addEventListener('vmo:mic-revoke', (e) => {
                const from = e?.detail?.from;
                if (from === 'consultation' && this.recognition) {
                    try { this.recognition.stop(); } catch(_) {}
                }
            });
        } catch(_) {}

        // Hook shared Jarvis modal End button to stop consultation voice if Jarvis isn't active
        const endBtn = document.getElementById('vmo-listening-close');
        if (endBtn && !endBtn.dataset.vmoConsult) {
            endBtn.addEventListener('click', (e) => {
                const modal = document.getElementById('vmo-listening-modal');
                const owner = modal?.dataset?.owner || 'voice-panel';
                if (owner === 'consultation') {
                    // Prevent Jarvis handler from firing when consultation owns the modal
                    e.stopImmediatePropagation();
                    // Always hard-stop consultation voice to avoid late recognition results
                    this.hardStopVoice('listening_modal_end');
                }
            }, true);
            endBtn.dataset.vmoConsult = '1';
        }
        // Hook shared Jarvis modal Mute button for consultation-owned sessions
        // ✅ FIX: Mute now also stops the microphone to prevent background noise from triggering barge-in
        const muteBtn = document.getElementById('vmo-listening-mute');
        if (muteBtn && !muteBtn.dataset.vmoConsult) {
            muteBtn.addEventListener('click', (e) => {
                const modal = document.getElementById('vmo-listening-modal');
                const owner = modal?.dataset?.owner || 'voice-panel';
                if (owner === 'consultation') {
                    e.stopImmediatePropagation();
                    window.vmoMuteConsultation = !window.vmoMuteConsultation;
                    const muted = !!window.vmoMuteConsultation;
                    muteBtn.textContent = muted ? 'Unmute Mic' : 'Mute Mic';
                    
                    // Optional: Visual indication style
                    muteBtn.style.color = muted ? '#ff4444' : '#00ffff';
                    
                    // ✅ CRITICAL FIX: Only pause/resume microphone (do NOT mute TTS output)
                    if (muted) {
                        // Stop VAD and speech recognition when muted
                        console.log('🔇 Mute: Pausing microphone and VAD (User Input Only)');
                        this.stopVADBargeIn();
                        try { if (this.recognition) this.recognition.stop(); } catch(_) {}
                        this._micMutedByUser = true;
                    } else {
                        // Resume VAD and speech recognition when unmuted
                        console.log('🔊 Unmute: Resuming microphone and VAD');
                        this._micMutedByUser = false;
                        if (this.isVoiceEnabled && this.isSpeaking) {
                            this.startVADBargeIn();
                        } else if (this.isVoiceEnabled && !this.isSpeaking) {
                            this.safeStartRecognition('unmute');
                        }
                    }
                    
                    try { window.dispatchEvent(new CustomEvent('vmo:mute-changed', { detail: { owner: 'consultation', muted } })); } catch(_) {}
                }
            }, true);
            muteBtn.dataset.vmoConsult = '1';
        }

        // Upload button inside the shared listening modal (consultation-owned only)
        const modalUploadBtn = document.getElementById('vmo-listening-upload');
        if (modalUploadBtn && !modalUploadBtn.dataset.vmoConsult) {
            modalUploadBtn.addEventListener('click', (e) => {
                const modal = document.getElementById('vmo-listening-modal');
                const owner = modal?.dataset?.owner || 'voice-panel';
                if (owner === 'consultation') {
                    e.stopImmediatePropagation();
                    try { this.openMediaUpload(); } catch (err) { console.warn('Upload failed to open:', err); }
                }
            }, true);
            modalUploadBtn.dataset.vmoConsult = '1';
        }
        // Keep mute changes applied to current consultation audio
        try {
            window.addEventListener('vmo:mute-changed', (e) => {
                const owner = e?.detail?.owner;
                if (owner !== 'consultation') return; // ignore non-consultation/global events
                const muted = !!(e?.detail?.muted);
                if (this.currentAudio) {
                    try { this.currentAudio.muted = muted; this.currentAudio.volume = muted ? 0 : 1; } catch(_) {}
                }
            });
        } catch(_) {}
    }

    /**
     * ✅ QUOTE FEATURE: Set up quote functionality for selecting text from messages
     * Allows users to select text from any AI or user message and quote it in their response
     */
    setupQuoteFeature() {
        // Create quote popup button (hidden by default)
        this._quotePopup = document.createElement('div');
        this._quotePopup.className = 'vmo-quote-popup';
        this._quotePopup.innerHTML = `
            <button class="vmo-quote-btn" type="button">
                <svg viewBox="0 0 24 24"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/></svg>
                Quote
            </button>
        `;
        
        // ✅ FIX: Append to modal container so it appears ABOVE the modal overlay
        // The modal has a high z-index stacking context, so body-level elements appear behind it
        const modal = document.getElementById('vmo-business-wizard-modal');
        if (modal) {
            modal.appendChild(this._quotePopup);
            console.log('💬 Quote popup appended to modal container');
        } else {
            // Fallback to body if modal not found yet
            document.body.appendChild(this._quotePopup);
            console.log('💬 Quote popup appended to body (modal not found)');
        }

        // Handle quote button click
        const quoteBtn = this._quotePopup.querySelector('.vmo-quote-btn');
        quoteBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.quoteSelectedText();
        });

        // Listen for text selection in chat container
        document.addEventListener('mouseup', (e) => {
            // Small delay to let selection complete
            setTimeout(() => this.handleTextSelection(e), 10);
        });

        // Hide popup when clicking elsewhere
        document.addEventListener('mousedown', (e) => {
            if (!this._quotePopup.contains(e.target)) {
                this.hideQuotePopup();
            }
        });

        // Hide popup on scroll
        const chatContainer = document.querySelector('#vmo-wizard-conversation');
        if (chatContainer) {
            chatContainer.addEventListener('scroll', () => {
                this.hideQuotePopup();
            });
        }

        console.log('💬 Quote feature initialized');
    }

    /**
     * Handle text selection in chat messages
     */
    handleTextSelection(e) {
        const selection = window.getSelection();
        const selectedText = selection.toString().trim();

        // Minimum 3 characters to show quote option
        if (!selectedText || selectedText.length < 3) {
            this.hideQuotePopup();
            return;
        }

        // Check if selection is within a chat message
        let range;
        try {
            range = selection.getRangeAt(0);
        } catch (err) {
            this.hideQuotePopup();
            return;
        }
        
        const container = range.commonAncestorContainer;
        
        // More robust message element finding
        let messageEl = null;
        if (container.nodeType === 3) {
            // Text node - get parent
            messageEl = container.parentElement?.closest('.vmo-ai-message, .vmo-user-message, .vmo-message-content');
        } else if (container.closest) {
            messageEl = container.closest('.vmo-ai-message, .vmo-user-message, .vmo-message-content');
        }
        
        // Also check parent chain manually if closest didn't work
        if (!messageEl) {
            let node = container.nodeType === 3 ? container.parentElement : container;
            while (node && node !== document.body) {
                if (node.classList && (
                    node.classList.contains('vmo-ai-message') || 
                    node.classList.contains('vmo-user-message') ||
                    node.classList.contains('vmo-message-content')
                )) {
                    messageEl = node;
                    break;
                }
                node = node.parentElement;
            }
        }

        if (!messageEl) {
            console.log('💬 Quote: Selection not within a chat message');
            this.hideQuotePopup();
            return;
        }

        // Position popup above selection using fixed positioning
        const rect = range.getBoundingClientRect();
        const popupTop = rect.top - 45; // Above the selection
        const popupLeft = rect.left + (rect.width / 2);
        
        // Ensure popup is visible on screen
        const safeTop = Math.max(10, popupTop);
        const safeLeft = Math.min(Math.max(60, popupLeft), window.innerWidth - 60);

        this._quotePopup.style.position = 'fixed';
        this._quotePopup.style.top = `${safeTop}px`;
        this._quotePopup.style.left = `${safeLeft}px`;
        this._quotePopup.classList.add('visible');
        
        console.log('💬 Quote popup shown at', safeTop, safeLeft, 'for text:', selectedText.substring(0, 30));

        // Store the selected text for when quote button is clicked
        this._pendingQuoteText = selectedText;
        this._pendingQuoteSender = messageEl.classList.contains('vmo-user-message') ? 'You' : 'AI';
    }

    /**
     * Hide the quote popup
     */
    hideQuotePopup() {
        if (this._quotePopup) {
            this._quotePopup.classList.remove('visible');
        }
    }

    /**
     * Quote the selected text and add to input area
     */
    quoteSelectedText() {
        if (!this._pendingQuoteText) return;

        // Set the quoted text
        this._quotedText = {
            text: this._pendingQuoteText,
            sender: this._pendingQuoteSender || 'Previous message'
        };

        // Clear selection
        window.getSelection().removeAllRanges();
        this.hideQuotePopup();

        // Show quote preview in input area
        this.showQuotePreview();

        // Focus the input
        const input = this.chatInput || document.querySelector('#vmo-wizard-response');
        if (input) {
            input.focus();
        }

        console.log('💬 Quoted text:', this._quotedText.text.substring(0, 50) + '...');
    }

    /**
     * Show quote preview above the input field
     */
    showQuotePreview() {
        if (!this._quotedText) return;

        const inputContainer = document.querySelector('.vmo-wizard-input-container');
        if (!inputContainer) return;

        // Remove existing preview if any
        this.removeQuotePreview();

        // Create quote preview element
        const preview = document.createElement('div');
        preview.id = 'vmo-quote-preview';
        preview.className = 'vmo-quote-preview';
        
        // Truncate for display (keep full text for sending)
        const displayText = this._quotedText.text.length > 150 
            ? this._quotedText.text.substring(0, 150) + '...'
            : this._quotedText.text;

        preview.innerHTML = `
            <div class="vmo-quote-preview-header">
                <span class="vmo-quote-preview-label">Quoting ${this._quotedText.sender}</span>
                <button class="vmo-quote-preview-remove" type="button" title="Remove quote">&times;</button>
            </div>
            <div class="vmo-quote-preview-text">"${this.escapeHtml(displayText)}"</div>
        `;

        // Handle remove button
        const removeBtn = preview.querySelector('.vmo-quote-preview-remove');
        removeBtn.addEventListener('click', () => {
            this.removeQuotePreview();
            this._quotedText = null;
        });

        // Insert before textarea
        const textarea = inputContainer.querySelector('textarea, #vmo-wizard-response');
        if (textarea) {
            inputContainer.insertBefore(preview, textarea);
        } else {
            inputContainer.prepend(preview);
        }
    }

    /**
     * Remove quote preview from input area
     */
    removeQuotePreview() {
        const existing = document.getElementById('vmo-quote-preview');
        if (existing) {
            existing.remove();
        }
    }

    /**
     * Helper to escape HTML in quoted text
     */
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    /**
     * Get quoted text prefix for message (if any)
     * Returns formatted quote block to prepend to message
     */
    getQuotedTextPrefix() {
        if (!this._quotedText) return '';
        
        const quoteBlock = `[Quoting ${this._quotedText.sender}: "${this._quotedText.text}"]\n\n`;
        return quoteBlock;
    }

    /**
     * Format quoted text as HTML for display in sent message
     */
    formatQuotedTextHtml() {
        if (!this._quotedText) return '';
        
        const displayText = this._quotedText.text.length > 200 
            ? this._quotedText.text.substring(0, 200) + '...'
            : this._quotedText.text;

        return `
            <div class="vmo-quoted-text">
                <div class="vmo-quoted-text-label">Replying to ${this.escapeHtml(this._quotedText.sender)}</div>
                <div class="vmo-quoted-text-content">"${this.escapeHtml(displayText)}"</div>
            </div>
        `;
    }
    
    sendUserMessage() {
        // Use wizard input (stored in openBusinessWizard)
    const input = this.chatInput || document.querySelector('#vmo-wizard-response');
        if (!input) {
            console.error('❌ Chat input not found!');
            return;
        }

        if (this.isGreetingPlaying) {
            // ✅ FIX: Immediately abort TTS when user types
            try { this._progressiveTTSAbort(); } catch(_) {}

            this.addMessageToUI('system', 'One moment—let me finish this personal greeting, then I’ll take your response.');
            return;
        }
        
        const message = input.value.trim();
        const hasAttachments = this.stagedAttachments && this.stagedAttachments.length > 0;
        
        // ✅ FIX: Always forcefully interrupt TTS on physical user action (Send Button / Enter Key)
        try { this._progressiveTTSAbort(); } catch(_){}
        
        // Allow sending if there's a message OR attachments (or both)
        if (!message && !hasAttachments) {
            console.warn('⚠️ Empty message and no attachments');
            return;
        }
        
        console.log('📤 Sending user message:', message, 'with', (this.stagedAttachments || []).length, 'attachments');
        input.value = '';

        // ✅ Clear speech state — user manually submitted, so reset everything
        this._userManuallyEdited = false;
        this._accumulatedTranscript = '';
        this._transcriptBuffer = '';
        this._interimTranscript = '';
        if (this._speechDebounceTimer) {
            clearTimeout(this._speechDebounceTimer);
            this._speechDebounceTimer = null;
        }
        this._speechStartTime = null;
        try { this.clearLiveTranscript(); } catch (_) {}
        
        // Process staged attachments with the message context
        this.sendMessageWithAttachments(message);
    }

    async sendMessageWithAttachments(messageContext) {
        const attachments = this.stagedAttachments || [];
        this.stagedAttachments = [];
        this.updateAttachmentPreviewUI();
        // ✅ FIX: Instantly interrupt any active TTS so voice playback does not bleed into the file upload processing
        try { this._progressiveTTSAbort(); } catch(_) {}
        
        if (attachments.length === 0) {
            // No attachments, just send the text message
            if (messageContext) {
                this.handleUserMessage(messageContext, false);
            }
            return;
        }
        
        // Show uploading status
        this.addMessageToUI('system', `Uploading ${attachments.length} file${attachments.length > 1 ? 's' : ''}…`);
        
        // Upload each attachment and collect info
        const uploadedFiles = [];
        for (const file of attachments) {
            try {
                const result = await this.uploadFileAndGetInfo(file);
                if (result) {
                    uploadedFiles.push(result);
                }
            } catch (err) {
                console.error('Failed to upload attachment:', file.name, err);
            }
        }
        
        if (uploadedFiles.length === 0) {
            this.addMessageToUI('ai', 'Upload failed. Please try again.');
            if (messageContext) {
                this.handleUserMessage(messageContext, false);
            }
            return;
        }
        
        // Build combined message: context + attachment list
        const attachmentNames = uploadedFiles.map(f => f.title || f.filename).join(', ');
        const emoji = uploadedFiles.length === 1 ? this.getFileEmoji(uploadedFiles[0].type) : '📎';
        
        let userVisibleMessage = '';
        if (messageContext) {
            userVisibleMessage = messageContext;
            if (uploadedFiles.length > 0) {
                userVisibleMessage += `\n\n${emoji} Attached: ${attachmentNames}`;
            }
        } else if (uploadedFiles.length > 0) {
            userVisibleMessage = `${emoji} Uploaded: ${attachmentNames}`;
        }
        
        if (userVisibleMessage) {
            this.addMessageToUI('user', userVisibleMessage);
            await this.saveMessageToDatabase('user', userVisibleMessage, uploadedFiles);
        }
        
        // Analyze/ingest each uploaded file
        for (const fileInfo of uploadedFiles) {
            const lower = (fileInfo.filename || '').toLowerCase();
            const isTextLike = (fileInfo.type || '').startsWith('text/') || ['.txt','.md','.markdown','.csv','.json'].some(ext => lower.endsWith(ext));
            
            if (isTextLike && fileInfo._rawText) {
                await this.ingestBlueprintText(fileInfo._rawText, fileInfo.filename, fileInfo.url);
            } else if (fileInfo.url) {
                await this.analyzeUploadedMedia(fileInfo);
            }
        }
        
        // If user provided context WITHOUT files, send as regular message
        // If files are attached, the analyzeUploadedMedia already triggers AI response
        // so we DON'T call handleUserMessage again to avoid duplicate responses
        if (messageContext && uploadedFiles.length === 0) {
            this.handleUserMessage(messageContext, false);
        } else if (messageContext && uploadedFiles.length > 0) {
            // User provided context WITH files - store the context for the document analysis
            // but DON'T send as separate message (avoid duplicate)
            this._pendingFileContext = messageContext;
            console.log('📎 Storing file context for document analysis:', messageContext.substring(0, 100));
        }
        // Files without context - analyzeUploadedMedia already handles the response
    }

    getFileEmoji(mimeType) {
        const type = String(mimeType || '').toLowerCase();
        if (type.startsWith('image')) return '🖼️';
        if (type.startsWith('video')) return '🎥';
        if (type.startsWith('audio')) return '🎵';
        if (type.includes('pdf')) return '📕';
        if (type.includes('word') || type.includes('document')) return '📄';
        if (type.includes('sheet') || type.includes('excel') || type.includes('csv')) return '📊';
        return '📎';
    }
    
    enforcePhaseAdherence() {
        const flow = this.consultation_data.consultation_flow;
        const currentPhase = flow.phases[flow.current_index];
        console.log(`🔒 Current Phase: ${currentPhase} (Index: ${flow.current_index})`);
        // Phase rules are now defined in getPhaseAllowedActions() and getPhaseSpecificGoals()
        // which are called from buildIntelligentSystemPrompt() to construct AI instructions
    }

    isActionAllowed(action) {
        if (!this.allowedActions) {
            console.warn('⚠️ Allowed actions not initialized. Defaulting to allow all actions.');
            // Default to allowing all actions if not initialized
            return true;
        }
        return this.allowedActions.has(action);
    }

    async handleUserMessage(message, fromVoice = false) {
        // ✅ FIX: Reject empty messages before any processing
        if (!message || typeof message !== 'string' || message.trim() === '') {
            return;
        }
        
        // If the message originated from voice, ignore unless voice is explicitly enabled.
        // This prevents a late SpeechRecognition onresult from triggering responses after End.
        if (fromVoice && !this.isVoiceEnabled) {
            return;
        }
        if (this.isGreetingPlaying) {
            // ✅ FIX: Immediately abort TTS when user types
            try { this._progressiveTTSAbort(); } catch(_) {}

            this.addMessageToUI('system', 'Hang tight for just a few seconds—I’m just wrapping up your personal greeting before I start listening.');
            return;
        }
        
        // ═══════════════════════════════════════════════════════════════════════════════
        // ✅ FIX: Immediately abort TTS when user typed a new message
        try { this._progressiveTTSAbort(); } catch(_) {}

        // 🔙 PHASE REDO INTENT DETECTION
        // Detect explicit "redo/go back to phase" commands and route to native phase handlers.
        const redoPhaseIndex = this.getRedoPhaseIntent(message);
        if (redoPhaseIndex !== null) {
            this.addMessageToUI('user', message);
            await this.saveMessageToDatabase('user', message);
            this.conversationHistory.push({
                role: 'user',
                content: message,
                timestamp: new Date().toISOString()
            });
            await this.executePhaseRedo(redoPhaseIndex, {
                skipConfirm: true,
                source: fromVoice ? 'voice-intent' : 'chat-intent'
            });
            return;
        }

        // 🔄 RESET INTENT DETECTION
        // Detect when user wants to start over / reset consultation
        // ═══════════════════════════════════════════════════════════════════════════════
        if (this.detectResetIntent(message)) {
            // Add user message to UI first
            this.addMessageToUI('user', message);
            
            // Show AI acknowledgment
            const resetAcknowledgment = "I understand you'd like to start fresh! I'll show you a confirmation dialog to make sure you want to reset everything. This will clear our entire conversation and let you begin a brand new consultation.";
            this.addMessageToUI('ai', resetAcknowledgment);
            
            // Speak the acknowledgment if voice is enabled
            if (this.isVoiceEnabled && typeof this.speakText === 'function') {
                try {
                    await this.speakText(resetAcknowledgment);
                } catch(e) {
                    console.log('TTS for reset acknowledgment skipped:', e);
                }
            }
            
            // Show the reset confirmation modal
            if (typeof window.vmoShowResetConfirmation === 'function') {
                window.vmoShowResetConfirmation();
            } else {
                // Fallback: show native confirm dialog
                if (confirm('Are you sure you want to reset the consultation and start fresh? This will clear all conversation history.')) {
                    if (typeof window.vmoResetConsultation === 'function') {
                        window.vmoResetConsultation();
                    } else {
                        window.location.reload();
                    }
                }
            }
            return; // Don't process this as a normal message
        }

        // ═══════════════════════════════════════════════════════════════════════════════
        // 🔍 PHASE 3 RESUME INTERCEPT: If user is at Phase 3 but research hasn't started
        // and they say something confirming (yes, ok, proceed, go ahead, continue, etc.),
        // show the research confirmation UI immediately instead of letting the AI ramble
        // ═══════════════════════════════════════════════════════════════════════════════
        if (!this._awaitingResearchConfirmation && !this.phase3ResearchRequested && !this._deepResearchInProgress) {
            const currentPhaseIdx = this.consultation_data?.consultation_flow?.current_index ?? 0;
            const phase3Done = this.consultation_data?.phase_flags?.phase3_completed || 
                              this.state?.phase_flags?.phase3_completed;
            if (currentPhaseIdx === 2 && !phase3Done) {
                const lowerMsg = message.toLowerCase().trim();
                const proceedPhrases = [
                    'yes', 'yeah', 'yep', 'yup', 'ok', 'okay', 'sure', 'go ahead',
                    'proceed', 'continue', 'let\'s proceed', 'let\'s go', 'let\'s do it',
                    'start', 'begin', 'do it', 'go for it', 'ready', 'sounds good',
                    'please', 'absolutely', 'definitely', 'for sure', 'confirmed',
                    'let\'s continue', 'please proceed', 'yes go ahead', 'start the research',
                    'yes start', 'please start', 'please lets proceed'
                ];
                const isProceedMessage = proceedPhrases.some(p => lowerMsg === p || lowerMsg === p + '.' || lowerMsg === p + '!') 
                    || proceedPhrases.some(p => p.length > 5 && lowerMsg.includes(p));
                if (isProceedMessage) {
                    console.log('🔍 Phase 3 resume: User said proceed but research confirmation not shown yet — showing it now');
                    this.addMessageToUI('user', message);
                    // Stop any speech that might be playing
                    try { this.stopSpeaking(); } catch(_) {}
                    // Show the research confirmation UI
                    this.showResearchConfirmation();
                    return;
                }
            }
        }

        // ═══════════════════════════════════════════════════════════════════════════════
        // 🎤 VOICE CONFIRMATION FOR DEEP RESEARCH
        // Detect when user verbally confirms they want to start deep research
        // ═══════════════════════════════════════════════════════════════════════════════
        const researchConfirmation = this.isResearchVoiceConfirmation(message);
        if (researchConfirmation) {
            // ✅ FIX: Stop any currently playing speech immediately to prevent overlap
            try { this.stopSpeaking(); } catch(_) {}
            try { this._progressiveTTSAbort(); } catch(_) {}
            try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_) {}

            // Add user message to UI first
            this.addMessageToUI('user', message);
            
            if (researchConfirmation === 'start') {
                console.log('🎤 Voice confirmation to start deep research detected!');
                await this.confirmAndStartDeepResearch();
                return; // Don't process as normal message
            } else if (researchConfirmation === 'wait') {
                console.log('🎤 Voice request to add more info before research');
                this._awaitingResearchConfirmation = false;
                this.addMessageToUI('ai', "📝 No problem! Please share any additional information you'd like me to consider before I start the research. You can also upload more documents if needed.");
                await this.saveMessageToDatabase('ai', 'User requested to add more information before research.');
                
                // Remove the confirmation buttons
                const confirmSection = document.querySelector('.vmo-research-confirmation');
                if (confirmSection) {
                    const actionsDiv = confirmSection.querySelector('.vmo-confirmation-actions');
                    if (actionsDiv) actionsDiv.remove();
                }
                
                if (this.isVoiceEnabled) {
                    this.speak("No problem! What additional information would you like to share?");
                }
                return; // Don't process as normal message
            }
        }

        if (this.isProcessing) {
            console.log('⚠️ Still processing previous message');
            // Don't drop voice messages; queue the latest and process it right after.
            if (fromVoice) {
                this._pendingVoiceMessage = (message || '').trim();
            }
            
            // 🔧 SAFETY FIX: If stuck processing for more than 60 seconds, force reset
            if (this._processingStartTime && (Date.now() - this._processingStartTime > 60000)) {
                console.warn('⚠️ Processing stuck for 60s - forcing reset');
                // ✅ FIX: Abort any in-flight API request to prevent race conditions
                if (this._consultStreamController) {
                    try { this._consultStreamController.abort(); } catch(_) {}
                    this._consultStreamController = null;
                }
                this.isProcessing = false;
                this.setVoiceStatus('ready');
                if (this.isVoiceEnabled) {
                    this.showModalPhase('listening');
                }
                // Fall through to process this message
            } else {
                return;
            }
        }
        
        this.isProcessing = true;
        this._processingStartTime = Date.now(); // Track when processing started
        this.setVoiceStatus('thinking');
        if (fromVoice || this.isVoiceEnabled) {
            this.showModalPhase('thinking');
        }
        
        // 🔧 SAFETY WATCHDOG: Disabled during long-running operations
        // The watchdog was causing interruptions during deep research and generation
        // Instead, let the operations complete naturally - they have their own progress tracking
        const currentPhase = this.consultation_data?.consultation_flow?.current_index ?? 0;
        const isDeepResearchActive = this.phase3ResearchRequested || this._deepResearchInProgress;
        const isGenerationActive = this._businessGenerationInProgress;
        const isDeepResearchPhase = (currentPhase === 2); // Phase 3 = deep research
        const isGenerationPhase = (currentPhase >= 5); // Phase 6, 7, 8 = generation phases
        const isLongRunningPhase = isDeepResearchPhase || isGenerationPhase || isDeepResearchActive || isGenerationActive;
        
        // ✅ FIX: DISABLE watchdog interruptions during long-running phases
        // These phases have their own progress tracking and should not be interrupted
        // The watchdog only handles truly stuck states - it should NEVER show user messages
        if (this._processingWatchdog) clearTimeout(this._processingWatchdog);
        this._processingWatchdog = null;
        
        // ✅ FIX: Use a generation counter so the finally block only clears
        // our own watchdog — not a watchdog set by a reentrant call.
        const watchdogGeneration = (this._watchdogGeneration = (this._watchdogGeneration || 0) + 1);
        
        // Only set a watchdog for short operations (Phases 1-4)
        // Long-running phases should complete without timeout interruption
        if (!isLongRunningPhase) {
            let watchdogTimeout = 180000; // 3 minutes for regular operations
            this._processingWatchdog = setTimeout(() => {
                if (this.isProcessing && !this.phase3ResearchRequested && !this._deepResearchInProgress && !this._businessGenerationInProgress) {
                    console.warn('⚠️ Processing watchdog: Resetting stuck state (silent reset, no user message)');
                    this.isProcessing = false;
                    this._processingStartTime = null;
                    this.setVoiceStatus('ready');
                    // ✅ SILENT RESET - Don't show timeout messages, just reset state
                    // The user can simply try again if needed
                    if (this.isVoiceEnabled) {
                        this.showModalPhase('listening');
                        try { this.recognition.start(); } catch(_) {}
                    }
                }
            }, watchdogTimeout);
        } else {
            console.log('🔒 Watchdog disabled for long-running operation (Phase', currentPhase + 1, ')');
        }

        // While processing, stop recognition to avoid capturing and dropping speech.
        if ((fromVoice || this.isVoiceEnabled) && this.recognition) {
            try { this.recognition.stop(); } catch (_) {}
            try { this.recognition.abort(); } catch (_) {}
        }

        // Check if the action is allowed in the current phase (only if phase enforcement is active)
        if (this.allowedActions && !this.isActionAllowed("askQuestions")) {
            this.addMessageToUI('ai', '⚠️ This action is not allowed in the current phase. Please complete the required steps first.');
            this.isProcessing = false;
            return;
        }

        // Payment/proceed confirmation is handled server-side (multilingual intent classifier).
        // The client should not infer intent using English keywords or auto-advance phases.
        
        // ═══════════════════════════════════════════════════════════════════════════════════════════
        // ✅ FIX: Detect when user pastes content and ASK if they want to use just the pasted portion
        // This prevents the AI from reading outdated parts of a full document
        // 
        // IMPORTANT: Skip paste detection entirely if:
        // 1. Documents were already loaded from the database (user's session was restored)
        // 2. The input came from voice (speech recognition, not clipboard paste)
        // 3. The user already made a choice about pasted content
        // ═══════════════════════════════════════════════════════════════════════════════════════════
        const shouldSkipPasteDetection = (
            this._documentsLoadedFromDB ||           // Documents already loaded from database
            fromVoice ||                              // Input came from voice, not paste
            this._pastedContentChoice ||              // User already made a choice
            this._skipPasteDetection ||               // Explicit skip flag
            (this.blueprintRaw && this.blueprintRaw.length > 500) // Already have a document loaded
        );
        
        // ✅ FIX: Only trigger paste detection for very large pastes (2000+ chars)
        // that have MULTIPLE business plan indicators (not just one keyword in a conversational message)
        if (!shouldSkipPasteDetection && message.length > 2000) {
            const businessPlanIndicators = [
                'product overview', 'business plan', 'target market', 'product name',
                'description:', 'goals & positioning', 'sitemap', 'pricing', 'affiliate',
                'short-term', 'long-term', 'mid-term', 'mission', 'vision', 'objectives'
            ];
            const lowerMessage = message.toLowerCase();
            // ✅ FIX: Require at least 3 indicators to be present (not just 1) to confirm it's a real document paste
            const matchCount = businessPlanIndicators.filter(indicator => lowerMessage.includes(indicator)).length;
            const hasIndicators = matchCount >= 3;
            
            if (hasIndicators) {
                // If we already have a document, ask user what to do
                if (this.blueprintRaw && this.blueprintRaw.length > 0) {
                    console.log('📄 Pasted content detected - user already has document, asking for preference');
                    
                    // Check if this is the same content as existing document
                    const isSameContent = this.blueprintRaw.includes(message.trim()) || message.includes(this.blueprintRaw.trim());
                    
                    if (!isSameContent && !this._pastedContentChoice) {
                        // Show choice dialog
                        this._pendingPastedContent = message;
                        this.addMessageToUI('user', message.substring(0, 200) + '...');
                        this.addMessageToUI('ai', `📄 I noticed you've pasted some content. Would you like me to:

**1.** Use ONLY this pasted portion (and replace any previous document)
**2.** Let me paste the entire document instead (I'll wait for you to paste the full document)
**3.** Just consider this as a regular message in our conversation

Please reply with 1, 2, or 3.`);
                        this._awaitingPastedContentChoice = true;
                        this.isProcessing = false;
                        return;
                    }
                } else {
                    // No existing document - ask user what they want
                    console.log('📄 Pasted content detected - no existing document, asking for preference');
                    this._pendingPastedContent = message;
                    this.addMessageToUI('user', message.substring(0, 200) + '...');
                    this.addMessageToUI('ai', `📄 I noticed you've pasted some content that looks like business information. Would you like me to:

**1.** Use ONLY this pasted portion as your business document
**2.** Let me paste the entire document instead (I'll wait for you to paste the full document)
**3.** Just consider this as a regular message in our conversation

Please reply with 1, 2, or 3.`);
                    this._awaitingPastedContentChoice = true;
                    this.isProcessing = false;
                    return;
                }
            }
        }
        
        // Handle pasted content choice response
        if (this._awaitingPastedContentChoice && /^[123]$/.test(message.trim())) {
            const choice = message.trim();
            this._awaitingPastedContentChoice = false;
            const pastedContent = this._pendingPastedContent;
            this._pendingPastedContent = null;
            
            if (choice === '1') {
                // Replace document with pasted content only
                this.blueprintRaw = pastedContent;
                this.blueprintSummary = null; // Clear old summary
                this.addMessageToUI('user', 'Option 1 - Use only the pasted portion');
                this.addMessageToUI('ai', '✅ Got it! I\'ll now work exclusively with the content you just pasted. Let me analyze this...');
                await this.analyzeDocument(pastedContent);
            } else if (choice === '2') {
                // User wants to paste the full document - wait for it
                this._awaitingFullDocument = true;
                this.addMessageToUI('user', 'Option 2 - Let me paste the entire document');
                this.addMessageToUI('ai', '✅ Perfect! Please go ahead and paste the entire document now. I\'ll wait for it and then analyze the complete content.');
                this.isProcessing = false;
                return;
            } else {
                // Treat as regular message
                this.addMessageToUI('user', 'Option 3 - Just a regular message');
                // Continue with normal processing - fall through
                message = pastedContent;
            }
            
            if (choice !== '3') {
                this.isProcessing = false;
                return;
            }
        }
        
        // Handle full document paste after user selected option 2
        if (this._awaitingFullDocument && message.length > 300) {
            this._awaitingFullDocument = false;
            console.log('📄 Full document received after option 2 selection');
            this.blueprintRaw = message;
            this.blueprintSummary = null;
            this.addMessageToUI('user', '📄 Full document pasted');
            this.addMessageToUI('ai', '📄 I\'ve received your complete document! Let me analyze it carefully...');
            await this.analyzeDocument(message);
            this.isProcessing = false;
            return;
        }
        
        // If Phase 2 is waiting on email role confirmation, capture that first.
        if (this._awaitingPhase2EmailRoles) {
            this._awaitingPhase2EmailRoles = false;
            const roles = this.applyPhase2EmailRolesFromText(message);
            if (roles) {
                const lines = Object.keys(roles).map(k => `- ${k}: ${roles[k]}`).join('\n');
                const confirmMsg = `✅ Email roles saved for Phase 2:\n${lines}\n\nNow please confirm your Phase 2 summary again (in any language). If something is wrong, tell me what to change.`;
                this.addMessageToUI('ai', confirmMsg);
                await this.saveMessageToDatabase('ai', confirmMsg);
                this.isProcessing = false;
                return;
            }
            const retryMsg = '⚠️ I didn\'t catch those email addresses. Reply with **1** to use default addresses, or provide at least one email address (e.g., info@yourdomain.com).';
            this.addMessageToUI('ai', retryMsg);
            await this.saveMessageToDatabase('ai', retryMsg);
            this._awaitingPhase2EmailRoles = true;
            this.isProcessing = false;
            return;
        }

        this.detectScenarioFromMessage(message);

        // Proceed/confirm intent is handled server-side (multilingual).
        
        // ✅ QUOTE FEATURE: Include quoted text if present
        let displayMessage = message;
        let messageForAI = message;
        
        if (this._quotedText) {
            // Prepend quote context for AI understanding
            const quotePrefix = this.getQuotedTextPrefix();
            messageForAI = quotePrefix + message;
            
            // Get formatted quote HTML for display
            const quoteHtml = this.formatQuotedTextHtml();
            displayMessage = quoteHtml + this._formatMessageHtml(message);
            
            // Clear the quoted text after using it
            this._quotedText = null;
            this.removeQuotePreview();
        }

        // Payment choice is handled server-side (multilingual + numeric shortcut).
        
        // Add user message to UI (with quote if present)
        if (displayMessage !== message) {
            // Custom HTML with quote - use innerHTML directly
            const chatContainer = document.querySelector('#vmo-wizard-conversation') || document.querySelector('#vmo-chat-messages, .vmo-chat-messages, [data-vmo-chat-messages]');
            if (chatContainer) {
                const messageDiv = document.createElement('div');
                messageDiv.className = 'vmo-user-message';
                
                const header = document.createElement('div');
                header.className = 'vmo-message-header';
                const role = document.createElement('span');
                role.className = 'vmo-message-role';
                role.textContent = this.getMessageLabel('user');
                const timestampMeta = this.formatMessageTimestamp();
                const timeEl = document.createElement('time');
                timeEl.className = 'vmo-message-timestamp';
                timeEl.setAttribute('datetime', timestampMeta.iso);
                timeEl.textContent = timestampMeta.display;
                header.appendChild(role);
                header.appendChild(timeEl);
                messageDiv.appendChild(header);
                
                const content = document.createElement('div');
                content.className = 'vmo-message-content';
                content.innerHTML = displayMessage;
                messageDiv.appendChild(content);
                chatContainer.appendChild(messageDiv);
                chatContainer.scrollTop = chatContainer.scrollHeight;
            }
        } else {
            this.addMessageToUI('user', message);
        }
        
        // Add to conversation history (with quote context for AI)
        this.conversationHistory.push({
            role: 'user',
            content: messageForAI,
            timestamp: new Date().toISOString()
        });
        
        // Save to database (original message, but AI sees full context)
        await this.saveMessageToDatabase('user', messageForAI);

        // ✅ CRITICAL FIX: REMOVED the scripted Q&A bypass
        // ALL user messages now go through the intelligent AI
        // The old code here was:
        //   if (this.awaitingDocumentAnswer || this.pendingDocumentQuestions.length) {
        //       await this.recordDocumentAnswer(message);
        //       return;
        //   }
        // This caused robotic behavior - bypassing AI intelligence entirely.
        // Now the AI handles everything intelligently with full context.
        
        // Clear any pending scripted questions - let AI take over
        if (this.pendingDocumentQuestions.length) {
            console.log('🧠 Clearing scripted questions - AI will handle intelligently');
            this.pendingDocumentQuestions = [];
            this.awaitingDocumentAnswer = null;
            this.documentFollowupActive = false;
        }
        
        // ✅ Show "thinking" indicator so user knows AI is working
        this._showThinkingIndicator();

        try {
            // ✅ Set voice flag BEFORE AI call so streaming can start progressive TTS
            this._voiceResponseRequested = this.isVoiceEnabled || fromVoice;

            // Call REAL AI backend with intelligence (use messageForAI to include quote context)
            const aiPayload = await this.getAIResponse(messageForAI);

            // ✅ Remove thinking indicator — response has arrived
            this._removeThinkingIndicator();
            let aiResponse = '';
            if (typeof aiPayload?.response === 'string' && aiPayload.response.trim().length) {
                aiResponse = aiPayload.response;
            } else if (typeof aiPayload?.assistant_message === 'string') {
                aiResponse = aiPayload.assistant_message;
            }
            const phaseAdvancedByServer = !!aiPayload?.phase_advanced;

            if (aiPayload?.structured_delta) {
                this.applyStructuredDelta(aiPayload.structured_delta);
            }
            
            // Check for phase completion marker OR server flag
            const hasCompleteMarker = typeof aiResponse === 'string' && aiResponse.includes('[[PHASE_COMPLETE]]');
            
            if (hasCompleteMarker || phaseAdvancedByServer) {
                console.log('🎯 Phase completion detected (Marker or Server). Advancing phase...');
                if (hasCompleteMarker) {
                    aiResponse = aiResponse.replace(/\[\[PHASE_COMPLETE\]\]/g, '').trim();
                }
                await this.advancePhase();
            }
            
            // ✅ NEW: Check for Phase 8 completion triggers (auto-deliverables)
            if (typeof aiResponse === 'string') {
                if (aiResponse.includes('[[GENERATE_WEBSITE]]')) {
                    console.log('🌐 Website generation trigger detected');
                    await this.triggerWebsiteGeneration();
                    aiResponse = aiResponse.replace(/\[\[GENERATE_WEBSITE\]\]/g, '').trim();
                }
                
                if (aiResponse.includes('[[POPULATE_DELIVERABLES]]')) {
                    console.log('📊 Deliverables population trigger detected');
                    await this.populateDeliverables();
                    aiResponse = aiResponse.replace(/\[\[POPULATE_DELIVERABLES\]\]/g, '').trim();
                }
                
                if (aiResponse.includes('[[VOICE_WELCOME]]')) {
                    console.log('🎤 Voice welcome trigger detected');
                    await this.triggerVoiceWelcome();
                    aiResponse = aiResponse.replace(/\[\[VOICE_WELCOME\]\]/g, '').trim();
                }

                if (aiResponse.includes('[[CHECK_DNS]]')) {
                    console.log('🔍 DNS Check trigger detected');
                    aiResponse = aiResponse.replace(/\[\[CHECK_DNS\]\]/g, '').trim();
                    // Execute DNS check after a short delay to let the AI message appear first
                    setTimeout(() => this.performDNSCheckAndInstruct(), 1000);
                }

                if (aiResponse.includes('[[ANALYZE_SITE]]')) {
                    console.log('🔍 Site Analysis trigger detected');
                    aiResponse = aiResponse.replace(/\[\[ANALYZE_SITE\]\]/g, '').trim();
                    // Extract URL from the response or previous user message
                    const urlMatch = aiResponse.match(/https?:\/\/[^\s]+/) || this.conversationHistory[this.conversationHistory.length - 1].content.match(/https?:\/\/[^\s]+/);
                    const url = urlMatch ? urlMatch[0] : null;
                    
                    if (url) {
                        setTimeout(() => this.performSiteAnalysis(url), 1000);
                    } else {
                        // If no URL found, ask for it
                        this.addMessageToUI('ai', "I'd love to analyze your existing site. Could you please share the URL?");
                    }
                }
            }
            
            // ✅ FIX: Synchronized text+audio - create message container first
            // Voice mode: Progressive reveal synced with TTS
            // Text mode: Show full message immediately
            // Skip addMessageToUI if streaming already rendered the message
            let contentElement = null;
            if (!aiPayload._streamedToUI) {
                const useProgressiveReveal = this.isVoiceEnabled || fromVoice;
                contentElement = this.addMessageToUI('ai', aiResponse, { progressive: useProgressiveReveal });
            }
            
            // Add to conversation history
            this.conversationHistory.push({
                role: 'assistant',
                content: aiResponse,
                timestamp: new Date().toISOString()
            });
            
            // Save to database (async, don't block)
            this.saveMessageToDatabase('ai', aiResponse);
            
            // ✅ CRITICAL FIX: Save consultation data after EVERY message to ensure persistence
            // This ensures blueprintRaw, blueprintSummary, and extracted_information are always saved
            if (window.vmo_consultant_context_v1) {
                this.saveConsultationData({
                    extracted_information: this._toCanonicalExtracted(),
                    documents: this.consultation_data.documents || [],
                    media_files: this._toCanonicalMediaFiles(),
                    consultation_flow: this.consultation_data.consultation_flow,
                    blueprintRaw: this.blueprintRaw,
                    blueprintSummary: this.blueprintSummary
                });
            }
            
            // Extract information from conversation — AWAIT so KNOWN ANSWERS is current
            // before the next user message triggers a new AI call
            try {
                await this.extractInformationFromConversation(message, aiResponse);
            } catch (extractErr) {
                console.warn('⚠️ Background extraction failed (non-blocking):', extractErr.message);
            }
            
            // Speak if voice is enabled
            if (aiPayload._progressiveTTSActive) {
                // ✅ Progressive TTS already started during streaming — voice is playing
                console.log('🔊 Progressive TTS active — voice already streaming');
            } else if (this.isVoiceEnabled || fromVoice) {
                // ✅ DOCUMENT CHECK: If this is a long document, don't read the whole
                // thing aloud — just announce it exists in the chat.
                if (this._isDocumentForTTS(aiResponse)) {
                    console.log('📄 Long document detected — announcing instead of reading');
                    this.speakWithTextSync("I've prepared a document for you. Please see it in the chat.", contentElement);
                } else {
                    // Non-streaming fallback: speak full response after it arrives
                    this.speakWithTextSync(aiResponse, contentElement);
                }
            } else {
                this.setVoiceStatus('ready');
            }
            
        } catch (error) {
            this._removeThinkingIndicator();
            console.error('❌ AI Response Error:', error);
            console.error('❌ Error details:', error.message, error.stack);
            // Show a more helpful error message with basic troubleshooting
            const errorMsg = error.name === 'AbortError' 
                ? 'The request was cancelled. Please try again.'
                : `I apologize, but I encountered an error (${error.message || 'unknown'}). Could you please try again?`;
            this.addMessageToUI('ai', errorMsg);
            // Ensure voice modal shows error state
            if (this.isVoiceEnabled) {
                this.showModalPhase('listening');
            }
        } finally {
            this.isProcessing = false;
            this._processingStartTime = null;
            // ✅ FIX: Only clear watchdog if it belongs to this invocation.
            // If a reentrant call set a new watchdog, don't touch it.
            if (this._processingWatchdog && this._watchdogGeneration === watchdogGeneration) {
                clearTimeout(this._processingWatchdog);
                this._processingWatchdog = null;
            }
            // If a voice utterance arrived mid-turn, process it now.
            try { await this.drainPendingVoiceMessage(); } catch (_) {}
        }
    }
    
    async getAIResponse(userMessage) {
        // Build intelligent system prompt that guides without forcing rigid structure
        const systemPrompt = this.buildIntelligentSystemPrompt();
        
        // 🔍 DEBUG: Log current phase being sent to AI
        const currentPhase = this.consultation_data.consultation_flow.phases[this.consultation_data.consultation_flow.current_index];
        console.log(`🤖 AI Call - Current Phase: ${currentPhase} (Index: ${this.consultation_data.consultation_flow.current_index})`);
        
        // Build conversation context
        const messages = [
            { role: 'system', content: systemPrompt }
        ];
        
        // ✅ CRITICAL FIX: Keep FULL conversation history, not just last 10 messages
        // Business consultations need complete context from start to finish
        // Document uploads, business goals, customer insights - ALL must persist
        const allHistory = this.conversationHistory || [];
        
        // 🔍 SMART CONTEXT WINDOW MANAGEMENT:
        // GPT-5.2 has a massive context window — we can keep extensive history
        // Only truncate if we exceed 500 messages (very long consultations)
        // When truncating, keep first 80 (document uploads, initial vision, business profile)
        // + last 80 (recent phases + current conversation)
        // This ensures the AI NEVER loses the user's original vision and decisions
        const historyToInclude = allHistory.length > 500 
            ? [
                // Keep first 80 messages (document upload, initial vision, business profile, early decisions)
                ...allHistory.slice(0, 80),
                // Add separator to indicate skipped messages
                { role: 'system', content: '[... ' + (allHistory.length - 160) + ' earlier messages omitted — core vision, targets, and all extracted information are preserved in the system context and vision anchor ...]' },
                // Keep last 80 messages (recent context)
                ...allHistory.slice(-80)
              ]
            : allHistory; // For most consultations, keep EVERYTHING
        
        // 🔍 DEBUG: Check if document exists in memory
        const hasDocument = !!(this.blueprintRaw && this.blueprintRaw.length > 0);
        console.log(`📊 Context: ${historyToInclude.length} messages (${allHistory.length} total) | Document: ${hasDocument ? 'YES (' + this.blueprintRaw.length + ' chars)' : 'NO'}`);
        
        messages.push(...historyToInclude);
        
        // Add current message
        messages.push({ role: 'user', content: userMessage });
        
        // Call backend AI
        const AX = this.getAjax();

        // ✅ STREAMING + INTERRUPTION: Use AbortController for barge-in
        if (this._consultStreamController) { try { this._consultStreamController.abort(); } catch(_){} }
        this._consultStreamController = new AbortController();

        const response = await fetch(AX.url, {
            method: 'POST',
            signal: this._consultStreamController.signal,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                action: 'vmo_process_consultation_message',
                security: AX.nonce,
                nonce: AX.nonce,
                _ajax_nonce: AX.nonce,
                message: userMessage,
                system_prompt: systemPrompt,
                context: JSON.stringify({
                    history: this.conversationHistory,
                    collected: this.collectedInformation,
                    topicsCovered: Array.from(this.topicsCovered),
                    blueprintSummary: this.blueprintSummary,
                    blueprintRaw: this.blueprintRaw,
                    hasDocument: hasDocument,
                    consultation_data: this.state
                }),
                session_id: this.sessionId,
                stream: '1'
            })
        });

        const contentType = response.headers.get('Content-Type') || '';

        if (contentType.includes('text/event-stream')) {
            // ══ STREAMING PATH ══
            // ✅ Remove thinking indicator — first streaming bytes arrived
            this._removeThinkingIndicator();
            // Create progressive AI message bubble and stream tokens into it
            const contentElement = this.addMessageToUI('ai', '', { progressive: true });
            let fullText = '';
            let metadata = {};
            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let sseBuffer = '';
            const chatContainer = contentElement ? contentElement.closest('#vmo-wizard-conversation, [data-vmo-chat-messages]') || contentElement.parentElement?.parentElement : null;

            // ✅ PROGRESSIVE TTS: Start speaking as text streams in
            const voiceActive = this._voiceResponseRequested;
            if (voiceActive && typeof window.vmoSpeak === 'function') {
                this._progressiveTTSInit();
            }

            // ✅ TYPEWRITER EFFECT: Stream visuals smoothly
            let visualText = '';
            let targetText = '';
            let streamFinished = false;
            let visualUpdateTimer = null;
            let docDetectedVisual = false; // Skip typewriter once document detected

            // ✅ FIX: Smart auto-scroll — only scroll to bottom if user is near the bottom.
            // This lets users scroll up to read earlier messages during streaming without
            // being forcibly snapped back to the bottom 33 times per second.
            const _shouldAutoScroll = () => {
                if (!chatContainer) return false;
                const threshold = 120; // px from bottom to still auto-scroll
                return (chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight) < threshold;
            };

            const updateVisuals = () => {
                // If document was detected, skip typewriter — show full content instantly
                if (docDetectedVisual) {
                    if (contentElement) {
                        contentElement.innerHTML = this._formatMessageHtml(targetText);
                    }
                    if (_shouldAutoScroll()) chatContainer.scrollTop = chatContainer.scrollHeight;
                    if (streamFinished && visualUpdateTimer) clearInterval(visualUpdateTimer);
                    return;
                }

                const diff = targetText.length - visualText.length;
                if (diff > 0) {
                    let charsToAdd = 1;
                    if (diff > 200) charsToAdd = Math.ceil(diff / 15);
                    else if (diff > 50) charsToAdd = 2;
                    else if (voiceActive) charsToAdd = 1; // slow down for TTS sync

                    visualText = targetText.substring(0, visualText.length + charsToAdd);
                    if (contentElement) {
                        // Use plain formatting during typewriter (no doc detection on partials)
                        contentElement.innerHTML = this._formatPlainMessageHtml(visualText) + '<span class="vmo-typing-cursor"></span>';
                    }
                    if (_shouldAutoScroll()) chatContainer.scrollTop = chatContainer.scrollHeight;
                } else if (streamFinished) {
                    if (contentElement) {
                        // Final render: apply full formatting including document viewer
                        contentElement.innerHTML = this._formatMessageHtml(targetText);
                    }
                    if (_shouldAutoScroll()) chatContainer.scrollTop = chatContainer.scrollHeight;
                    if (visualUpdateTimer) clearInterval(visualUpdateTimer);
                }
            };
            
            visualUpdateTimer = setInterval(updateVisuals, 30);

            try {
                while (true) {
                    const { done, value } = await reader.read();
                    if (done) {
                        streamFinished = true;
                        break;
                    }
                    sseBuffer += decoder.decode(value, { stream: true });
                    const lines = sseBuffer.split('\n');
                    sseBuffer = lines.pop(); // keep incomplete line
                    for (const line of lines) {
                        const trimmed = line.trim();
                        if (!trimmed || !trimmed.startsWith('data: ')) continue;
                        try {
                            const evt = JSON.parse(trimmed.substring(6));
                            if (evt.t) {
                                fullText += evt.t;
                                targetText = fullText;
                                
                                // ✅ Detect if inside a markdown code block or doc block
                                const blockSpaces = (fullText.match(/```/g) || []).length;
                                const isInsideCodeBlock = (blockSpaces % 2 !== 0);

                                if (isInsideCodeBlock) {
                                    // Bypass typewriter and instantly show the document block
                                    visualText = targetText;
                                }

                                // ✅ DOCUMENT DETECTION: Once we have enough text, check if this
                                // is a long document. If so, stop feeding TTS and queue an
                                // announcement instead of reading 100+ lines aloud.
                                // Also immediately present the document visually (skip typewriter).
                                if (!docDetectedVisual && fullText.length > 800) {
                                    if (this._isDocumentForTTS(fullText)) {
                                        // Instantly render document in the viewer (skip typewriter)
                                        docDetectedVisual = true;

                                        if (voiceActive && this._ptts && !this._ptts._docAnnounced) {
                                            // Stop feeding tokens to TTS — the doc is too long to read
                                            this._ptts._docDetected = true;
                                            // If TTS hasn't spoken anything yet, queue the announcement
                                            if (this._ptts.totalChunks === 0 || !this._ptts.started) {
                                                this._ptts.buffer = ''; // Clear buffered doc text
                                                this._queueTTSChunk("I've prepared a document for you. Please see it in the chat.");
                                            } else {
                                                // TTS already started reading the intro — flush what's there
                                                // and add announcement after
                                                const leftover = this._stripForTTS(this._ptts.buffer || '');
                                                this._ptts.buffer = '';
                                                if (leftover && leftover.length >= 8) {
                                                    this._queueTTSChunk(leftover);
                                                }
                                                this._queueTTSChunk("The full document is in the chat for you to review.");
                                            }
                                            this._ptts._docAnnounced = true;
                                        }
                                    }
                                }

                                // ✅ Feed each token to progressive TTS
                                if (voiceActive && this._ptts) {
                                    // Mute TTS for: code blocks, already-announced documents
                                    const skipTTS = isInsideCodeBlock || evt.t.includes('```') || this._ptts._docDetected;
                                    if (!skipTTS) {
                                        this._progressiveTTSAppend(evt.t);
                                    }
                                }
                            } else if (evt.done) {
                                metadata = evt;
                            } else if (evt.error) {
                                throw new Error(evt.error);
                            }
                        } catch (pe) {
                            // ✅ FIX: Log JSON parse errors instead of silently swallowing them.
                            // If the error message mentions JSON, it's a malformed SSE data line —
                            // log it so we can diagnose token loss. Non-JSON errors are rethrown.
                            if (pe.message && !pe.message.includes('JSON')) throw pe;
                            console.warn('⚠️ SSE parse error (token may be lost):', trimmed.substring(0, 80));
                        }
                    }
                }
            } catch (streamErr) {
                if (streamErr.name === 'AbortError') {
                    console.log('🔇 Consultation stream interrupted by user');
                    // Abort progressive TTS on barge-in
                    if (voiceActive) this._progressiveTTSAbort();
                } else {
                    // ✅ FIX: Don't re-throw — gracefully handle connection drops
                    // The server may have finished streaming tokens but crashed during
                    // post-processing (phase advance, research generation, etc.)
                    // If we have accumulated text, return it as a partial response
                    // instead of crashing the entire page.
                    console.error('⚠️ Stream connection error (recovering with partial response):', streamErr.message);
                    if (voiceActive) this._progressiveTTSAbort();
                    if (!fullText.trim()) {
                        throw streamErr; // Only re-throw if we got nothing at all
                    }
                    // ✅ FIX: Notify user that response may be incomplete
                    if (contentElement) {
                        const truncNote = document.createElement('div');
                        truncNote.style.cssText = 'color:#f59e0b;font-size:12px;margin-top:8px;font-style:italic;';
                        truncNote.textContent = '⚠️ Response may be incomplete due to a connection issue. You can ask me to continue.';
                        contentElement.appendChild(truncNote);
                    }
                }
            } finally {
                this._consultStreamController = null;
            }

            // ✅ Flush remaining text to TTS
            if (voiceActive && this._ptts) {
                this._progressiveTTSFlush();
            }

            // Remove the typing cursor if present
            if (contentElement) {
                const cursor = contentElement.querySelector('.vmo-typing-cursor');
                if (cursor) cursor.remove();
            }

            // ✅ FIX: Strip any [[PHASE_COMPLETE]] markers that leaked through the stream
            // (they should have been filtered server-side, but handle edge cases)
            const hasPhaseComplete = fullText.includes('[[PHASE_COMPLETE]]');
            let cleanText = fullText.replace(/\[\[PHASE_COMPLETE\]\]/g, '').trim();

            // Update the displayed content to remove the marker
            if (hasPhaseComplete && contentElement) {
                contentElement.innerHTML = this._formatMessageHtml(cleanText);
            }

            // Return a payload matching the non-streaming shape
            return {
                response: cleanText,
                session_id: metadata.session_id || this.sessionId,
                isComplete: !!metadata.isComplete,
                phase_advanced: !!metadata.phase_advanced || hasPhaseComplete,
                _streamedToUI: true,                // Flag: caller should skip addMessageToUI
                _progressiveTTSActive: !!(voiceActive && this._ptts)  // Flag: TTS already in progress
            };
        }

        // ══ FALLBACK: non-streaming JSON response ══
        this._consultStreamController = null;
        const responseText = await response.text();
        let data;
        try {
            data = JSON.parse(responseText);
        } catch (parseError) {
            // Check if response is HTML (likely a PHP error page)
            if (responseText.trim().startsWith('<') || responseText.includes('<html') || responseText.includes('<!DOCTYPE')) {
                console.error('❌ Server returned HTML instead of JSON. Possible PHP error:', responseText.substring(0, 500));
                throw new Error('Server error occurred. The backend may have encountered an issue. Please try again.');
            }
            console.error('❌ Failed to parse JSON response:', parseError, 'Raw response:', responseText.substring(0, 200));
            throw new Error('Invalid server response format. Please try again.');
        }
        
        if (!data.success) {
            // ✅ CHECK FOR INSUFFICIENT TOKENS - Show toast and pause for top-up
            if (this.isInsufficientTokensResponse(data)) {
                console.log('⚠️ Insufficient tokens detected during consultation');
                // Save current message so we can resume
                this._pausedConsultationMessage = userMessage;
                this._pausedConsultationData = {
                    message: userMessage,
                    phase: this.consultation_data.consultation_flow.current_index,
                    operationType: 'consultation'
                };
                await this.saveConsultationData({ paused_consultation: this._pausedConsultationData });
                
                // Show the credit toast with resume capability
                this.showInsufficientCreditsWithResume('consultation');
                
                // Wait for user to top up
                await this.waitForCreditsAndResume();
                
                // After credits restored, retry this message
                console.log('✅ Credits restored, retrying consultation message...');
                return this.callConsultationAI(userMessage);
            }
            throw new Error(data.data?.message || 'AI processing failed');
        }

        if (data.data?.consultation_data) {
            this.mergeConsultationState(data.data.consultation_data);
            this.refreshPhaseUI();
        }
        
        return data.data;
    }
    
    /**
     * Build a definitive list of questions/topics the user has ALREADY answered
     * by scanning the actual conversation history. This is more reliable than
     * extracted_information because it doesn't depend on the async extraction API.
     */
    buildAlreadyAnsweredSummary() {
        const answered = [];
        const ex = this.consultation_data.extracted_information || {};
        const valueOf = (...keys) => {
            for (const key of keys) {
                const value = ex[key];
                if (value === undefined || value === null) continue;
                if (typeof value === 'string' && !value.trim()) continue;
                return value;
            }
            return null;
        };

        // Check extracted_information fields (these come from AI extraction)
        const businessName = valueOf('businessName', 'business_name');
        const businessIdea = valueOf('businessIdea', 'business_idea');
        const targetAudience = valueOf('targetAudience', 'target_audience');
        const businessGoals = valueOf('businessGoals', 'business_goals');
        const websitePreferences = valueOf('websitePreferences', 'website_preferences');
        const brandingInfo = valueOf('brandingInfo', 'branding_info');
        const successDefinition = valueOf('successDefinition', 'success_definition');
        const scaleIntent = valueOf('scaleIntent', 'scale_intent');
        const automationPreference = valueOf('automationPreference', 'automation_preference');

        if (businessName && businessName !== 'Not specified') answered.push(`Business Name: ${businessName}`);
        if (ex.industry && ex.industry !== 'Not specified') answered.push(`Industry: ${ex.industry}`);
        if (businessIdea && businessIdea !== 'Not specified') answered.push(`Business Concept: ${String(businessIdea).substring(0, 200)}`);
        if (targetAudience && targetAudience !== 'Not specified') answered.push(`Target Audience: ${String(targetAudience).substring(0, 200)}`);
        if (ex.offerings && ((Array.isArray(ex.offerings) && ex.offerings.length > 0) || (!Array.isArray(ex.offerings) && ex.offerings !== 'Not specified'))) {
            answered.push(`Offerings: ${Array.isArray(ex.offerings) ? ex.offerings.join(', ') : ex.offerings}`);
        }
        if (businessGoals && businessGoals !== 'Not specified') answered.push(`Business Goals: ${String(businessGoals).substring(0, 200)}`);
        if (ex.businessVision && ex.businessVision !== 'Not specified') answered.push(`Vision: ${String(ex.businessVision).substring(0, 200)}`);
        if (ex.visionTimeframe && ex.visionTimeframe !== 'Not specified') answered.push(`Vision Timeframe: ${ex.visionTimeframe}`);
        if (ex.location && ex.location !== 'Not specified') answered.push(`Location: ${ex.location}`);
        if (ex.offeringType) answered.push(`Offering Type: ${ex.offeringType}`);
        if (websitePreferences && websitePreferences !== 'Not specified') answered.push(`Website Preferences: ${String(websitePreferences).substring(0, 200)}`);
        if (ex.pricing && ex.pricing !== 'Not specified') answered.push(`Pricing: ${String(ex.pricing).substring(0, 200)}`);
        if (brandingInfo && brandingInfo !== 'Not specified') answered.push(`Branding: ${brandingInfo}`);
        if (ex.needsEcommerce === true || ex.needsEcommerce === false) answered.push(`E-commerce Needed: ${ex.needsEcommerce ? 'Yes' : 'No'}`);
        if (ex.payment_collection && ex.payment_collection.confirmed) answered.push(`Payment Collection: ${ex.payment_collection.mode || 'confirmed'}`);
        if (ex.businessStage) answered.push(`Business Stage: ${ex.businessStage}`);
        if (ex.wants_branded_email === true || ex.wants_branded_email === false) answered.push(`Branded Email: ${ex.wants_branded_email ? 'Yes' : 'No'}`);
        if (ex.business_email) answered.push(`Business Email: ${ex.business_email}`);
        if (ex.email_frequency) answered.push(`Email Frequency: ${ex.email_frequency}`);
        if (ex.customPages && Array.isArray(ex.customPages) && ex.customPages.length > 0) answered.push(`Custom Pages: ${ex.customPages.join(', ')}`);
        if (ex.pageDescriptions && Object.keys(ex.pageDescriptions).length > 0) answered.push(`Page Descriptions: ${Object.keys(ex.pageDescriptions).join(', ')}`);
        if (successDefinition) answered.push(`Success Definition: ${String(successDefinition).substring(0, 150)}`);
        if (scaleIntent) answered.push(`Scale Intent: ${String(scaleIntent).substring(0, 150)}`);
        if (automationPreference) answered.push(`Automation Preference: ${String(automationPreference).substring(0, 150)}`);

        // Scan conversation history for answers that extraction might have missed
        const userMessages = this.conversationHistory.filter(m => m.role === 'user').map(m => m.content || '');
        const allUserText = userMessages.join(' ').toLowerCase();

        // Check if location was mentioned in conversation even if not in extracted_information
        if (!ex.location || ex.location === 'Not specified') {
            const locationPatterns = [/atlanta/i, /new york/i, /los angeles/i, /chicago/i, /london/i, /\b[A-Z][a-z]+,?\s*(USA|UK|US|CA|AU)\b/];
            for (const msg of userMessages) {
                if (locationPatterns.some(p => p.test(msg))) {
                    answered.push(`Location: mentioned in conversation — "${msg.substring(0, 100)}"`);
                    break;
                }
            }
        }

        // Check media uploads
        const media = this.consultation_data.media_files || [];
        if (media.length > 0) {
            const logos = media.filter(m => (m.roles || []).includes('logo') || (m.role || '').includes('logo'));
            if (logos.length > 0) answered.push(`Logo: UPLOADED (${logos.length} file(s))`);
            const images = media.filter(m => (m.type || '').startsWith('image'));
            if (images.length > 0) answered.push(`Images: ${images.length} uploaded`);
        }

        // Check documents
        const docs = this.consultation_data.documents || [];
        if (docs.length > 0) {
            answered.push(`Documents: ${docs.length} uploaded (${docs.map(d => d.title || 'untitled').join(', ')})`);
        }

        if (answered.length === 0) return '';

        return `\n🚫 ALREADY ANSWERED — DO NOT RE-ASK THESE (${answered.length} items):\n${answered.map(a => `  ✅ ${a}`).join('\n')}\n\nIf ANY of these topics come up, REFERENCE the known answer above. NEVER ask the user for information listed here.\n`;
    }

    buildIntelligentSystemPrompt() {
        const flow = this.consultation_data.consultation_flow;
        const currentPhaseIndex = flow.current_index;
        const currentPhase = flow.phases[currentPhaseIndex] || 'Phase 1: Business Foundations Intake';
        const ex = this.consultation_data.extracted_information;
        const scenarioGuidance = this.getScenarioGuidance();
        const phaseGoals = this.getPhaseSpecificGoals(currentPhaseIndex);
        const allowedActions = this.getPhaseAllowedActions(currentPhaseIndex);
        const nextSteps = this.getNextStepsGuidance();
        const alreadyAnswered = this.buildAlreadyAnsweredSummary();

        const docDirective = this.blueprintSummary || this.blueprintRaw
            ? `🚨 DOCUMENT SUPREMACY: A business plan / blueprint exists. Reference it first, quote from it, and only ask about missing data. Never re-ask basics already in the document.

            KNOWN DOCUMENT DETAILS:
            ${this.blueprintSummary || 'No summary available.'}

            If the user confirms they want to continue (in any language), move to the next phase without re-analyzing.`
            : 'No uploaded document yet—collect information through a friendly, one-question-at-a-time conversation.';

        const phaseFormatting = (() => {
            if (currentPhaseIndex === 1) {
                if (this.blueprintSummary || this.blueprintRaw) {
                    return `FORMAT REQUIREMENT (Phase 2 - Document Mode):
You are in PHASE 2: Business Summary Confirmation.

🚫 DO NOT RE-ANALYZE DOCUMENTS ALREADY PROCESSED.
✅ Use ATTRACTIVE, PROFESSIONAL FORMATTING:
   • Bold headers using **Header Text**
   • Proper spacing between sections
   • Bullet points for lists
   • Visual separators (━━━━━━━━) between major sections

Ask for confirmation: "Is this an accurate representation of your business? If it is correct, the user will confirm in their own words (any language). If not, they will describe what to change. Once confirmed, continue to Phase 3: Deep Research & Market Analysis."`;
                }
                return 'FORMAT REQUIREMENT: Present findings as "Question → Answer" bullet points, then ask for confirmation ("Is everything correct?").';
            }
            if (currentPhaseIndex === 2) {
                return 'FORMAT REQUIREMENT: Summaries must include sections for Market Insights, Competitors, Opportunities, and Legal/Compliance. Keep recommendations hidden until Phase 4.';
            }
            if (currentPhaseIndex === 3) {
                return 'FORMAT REQUIREMENT: Present the recommendations checklist with sections (Website, Systems, Growth). Each item must include "Why I Recommend This" and remain pre-checked unless the user says otherwise.';
            }
            return '';
        })();

        // ✅ FIX: Session context to prevent AI from saying "thank you for uploading" for restored documents
        const sessionContextDirective = (() => {
            if (this._isReturningUser) {
                const docCount = this._restoredDocumentCount || 0;
                return `🔄 SESSION CONTEXT (CRITICAL - READ CAREFULLY):
This is a RETURNING USER with an EXISTING consultation session.

⚠️ DOCUMENT HANDLING RULES:
- The documents and business information you see were uploaded in a PREVIOUS session, NOT just now.
- DO NOT say "thank you for uploading" or act like documents were just uploaded.
- DO NOT repeatedly thank them for documents or summarize the same document multiple times.
- INSTEAD: Reference the document content naturally as established context.
- If appropriate, acknowledge: "I still have your business plan from our previous conversation..."

📊 RESTORED DATA:
- ${docCount} document(s) restored from previous session
- Extracted information from prior conversations is available
- Treat this as a CONTINUATION, not a fresh start

🎯 BEHAVIOR:
- Acknowledge returning user naturally ("Welcome back!" or "Let's continue where we left off...")
- Reference previously collected information without re-asking
- Move forward based on where they were in the consultation flow
- If they've already provided detailed info, don't start from scratch`;
            }
            return `🆕 SESSION CONTEXT: This appears to be a NEW consultation session. No prior documents or data restored.`;
        })();

        // ✅ Website scanning capability context
        const websiteScanningCapability = `🌐 WEBSITE SCANNING CAPABILITY (CRITICAL - YOU CAN SCAN WEBSITES):
You HAVE the ability to scan and analyze websites. When a user shares a URL, the system automatically fetches and analyzes that website's content, structure, and design.

✅ WHAT YOU CAN DO:
- Scan reference/inspiration websites the user shares (e.g., "check out thetinypod.com")
- Analyze existing user websites for benchmarking and improvement suggestions
- Extract business information, features, styling, and content from URLs
- Compare websites for competitive analysis
- Understand design patterns and UI/UX from reference sites

🔧 HOW IT WORKS:
- When the user includes a URL in their message, the backend automatically scans it
- The scanned content is added to your context as "SCANNED WEBSITE REFERENCES"
- You can then discuss the website's content, design, features, and how to apply learnings to the user's project

❌ NEVER SAY:
- "I can't scan websites"
- "I can't browse the internet"  
- "I can't access external URLs"
- "Please paste the content instead"
- "I don't have the ability to view websites"

✅ INSTEAD SAY:
- "Let me analyze that website..."
- "I've scanned [URL] and here's what I found..."
- "Based on the website content at [URL]..."
- If a URL hasn't been scanned yet in context, say: "I'll scan that website for you - just give me a moment to analyze it."

📋 SCANNED WEBSITES IN CONTEXT:
${this.formatScannedWebsitesContext()}`;

        let prompt = `You are VMO, an expert AI business consultant guiding an 8-phase onboarding.

${sessionContextDirective}

${websiteScanningCapability}

CURRENT SCENARIO:
${scenarioGuidance}

PHASE LOCK RULES:
- You are currently in ${currentPhase} (Phase ${currentPhaseIndex + 1} of 8).
- Stay inside the current phase. No recommendations before Phase 4. No website architecture before Phase 5.
- Never mention phase mechanics to the user—just behave accordingly.
- When the user confirms or says "proceed", advance naturally and continue the flow.

🚨 PROCEED INTENT DETECTION (CRITICAL - HIGHEST PRIORITY RULE):
When the user expresses ANY form of agreement, confirmation, or "move on" intent, you MUST advance immediately.

Trigger phrases (in any language or variation):
- "move on", "proceed", "continue", "go on", "next"
- "yes", "correct", "confirmed", "I confirm", "that's right"
- "looks good", "all good", "great", "perfect", "fine"
- "go ahead", "carry on", "keep going", "let's go"
- "everything is correct", "everything is fine", "everything is set"
- Any single-word affirmative: "yes", "yep", "ok", "okay", "sure"

🔴 HARD RULE: ONE CONFIRMATION = PHASE/TOPIC COMPLETE
Once the user confirms ONCE, that topic/phase is DONE. Do NOT:
❌ Re-summarize what was just confirmed
❌ Ask "Is there anything else to add?"
❌ Ask "Can you confirm this is correct?"
❌ Show another recap or summary
❌ Ask the same question in different words

✅ WHAT TO DO INSTEAD:
- Say "Perfect!" or "Great!" (max 5 words)
- Immediately ask about a NEW topic OR transition to the next phase
- If all Phase 1 topics are covered, say "Let's move to Phase 2" and present the business summary ONCE

🚨 LOOP DETECTION:
If you find yourself summarizing the same information twice, you are LOOPING. STOP and transition immediately.

INTAKE VALIDATION RULES:
- In Phase 1, be a real thinking partner: encourage, reflect back what you heard, and help the user refine what they mean.
- If the user response is vague or off-topic, respond naturally (not robotic), then ask ONE clarifying question to get the missing detail.
- Do not “advance the script” on filler replies; keep the conversation helpful and focused.

NO-RECOMMENDATIONS RULE:
- Before Phase 4, do not recommend tools, vendors, or strategies.
- You MAY help the user clarify goals/positioning ("what do you mean by…", "which option fits you better…") but do not prescribe solutions.

ALLOWED ACTIONS NOW:
${allowedActions}

PHASE GOALS:
${phaseGoals}

${phaseFormatting}

${docDirective}

CONVERSATION STATE:
- Messages exchanged: ${this.conversationHistory.length}
- Topics remaining: ${this.consultationTopics.filter(t => !this.topicsCovered.has(t)).join(', ') || 'All core topics covered'}
- Mode: ${this.mode === 'document' ? 'Document-first' : 'Conversation'}

KNOWN ANSWERS:
${JSON.stringify(ex, null, 2)}

${alreadyAnswered}

${this.formatDocumentContext()}
${this.formatMediaContext()}

NEXT FOCUS:
${nextSteps}

TONE & STYLE:
- Mirror the user’s tone and language (multilingual if needed).
- Sound like a thoughtful consultant: empathetic, confident, no jargon overload.
- Answer user questions before continuing with your agenda.

🎯 CONVERSATION INTELLIGENCE (CRITICAL):
- You are having a REAL CONVERSATION, not reading a script.
- FIRST: Acknowledge and respond to what the user just said. Show you understood them.
- SECOND: Share a brief insight, encouragement, or relevant observation.
- THIRD (only if needed): Ask ONE natural follow-up question.
- If the user provides vague or off-topic responses, engage with what they said—don't ignore it.
- Never robotically move to the "next question" without genuinely engaging with their answer.
- Vary your question patterns—don't always start with "What..." or "Can you tell me..."
- If you have enough information on a topic, don't ask more—move forward naturally.
- Use natural transitions like "That makes sense...", "I love that...", "Great choice...", etc.

🚨 ANTI-ROBOT RULES (EXTREMELY IMPORTANT):
- If the user says something UNEXPECTED, DO NOT ignore it and ask the next question. RESPOND to what they said.
- If the user expresses frustration, STOP EVERYTHING and acknowledge: "I hear your frustration - let me adjust."
- If the user uploads a document, ACKNOWLEDGE it specifically and reference what's in it.
- NEVER ask for information that's already in an uploaded document. Use the document content.
- If a user's response doesn't fit your expected flow, ADAPT. You are intelligent, not scripted.
- If the user asks YOU a question, ANSWER IT before continuing your agenda.
- Show emotional intelligence: match excitement with excitement, address concerns with empathy.

🖼️ MEDIA & CONTENT RULES (EXTREMELY IMPORTANT):
- NEVER tell the user you will "omit" or "skip" media, images, videos, or any content because they didn't provide it.
- If the user has NOT provided images, videos, logos, or other media, you MUST:
  1. ASK the user if they have such media ready to upload
  2. Let them know that you CAN GENERATE professional images, videos, and animations for them automatically
  3. Tell them they can always replace generated media later with their own
  4. If they want to add media later, tell them: "You can chat with Jarvis in your website editor to add or replace any media at any time"
- When generating the website, ALL visual sections MUST have media — generated by AI if not provided by the user
- The generated website must ALWAYS look complete, polished, and premium — never empty or sparse
- NEVER mention "placeholders", "placeholder images", or "stock photos" — use the term "AI-generated visuals" instead
- NEVER show raw HTML code, URLs, or technical markup in conversation messages to the user

💬 MESSAGE FORMATTING RULES:
- NEVER include raw HTML tags (<ul>, <li>, <a>, <strong>, etc.) in your messages to the user
- Use plain text with Markdown formatting only (bold with **, lists with - or numbered, etc.)
- NEVER show URLs, buy links, checkout links, or any code to the user unless they specifically ask
- When referencing products, use their names only — never show technical identifiers or raw URLs
- Keep all messages novice-friendly, warm, and free of any technical jargon

🎤 VOICE ACCURACY RULES:
- Never claim direct audio hearing. You only receive typed text or speech-to-text transcript.
- If a user asks "can you hear me", reply that you can read their text/transcript and help verify mic/transcription.
- Never say "Yes, I can hear you" unless explicitly referring to a visible transcript that was received.

🧠 MEMORY RULES (CRITICAL - DO NOT FORGET):
- You have PERFECT MEMORY of everything in the extracted_information JSON shown above.
- If a detail exists in extracted_information, DO NOT ask about it again.
- If the user provided a logo, you KNOW they provided a logo - don't ask again.
- If the user specified their target market, you KNOW it - don't ask again.
- If the user confirmed emails, they are CONFIRMED - don't ask again.
- Before asking ANY question, check if the answer already exists in the KNOWN ANSWERS section above.
- If you ask for something already provided, you are FAILING as a consultant. Check your context first.

🔄 ANTI-LOOP ENFORCEMENT:
- Count how many times you've shown a summary in this conversation.
- If you've shown a summary and the user confirmed, DO NOT show another summary.
- If the user says "move on" / "proceed" / "continue" more than once, you are STUCK. Break out IMMEDIATELY.
- When stuck: transition to the NEXT phase without any summary or confirmation request.

MEDIA & ASSET INTELLIGENCE:
- Auto-reference uploaded logos, hero media, galleries, or documents when relevant.
- Suggest placements only when confident ≥0.8; otherwise ask one clarifying question.

ETHICAL GUARDRAILS:
- Decline illegal or harmful requests.
- Keep the consultation focused on building the business responsibly.

Remember: respect the phase, stay document-first, and deliver novice-friendly clarity.`;

        if (this.blueprintSummary) {
            prompt += `

BLUEPRINT SUMMARY (use as source of truth):
${this.blueprintSummary}
`;
        }
        // NOTE: blueprintRaw is NOT included here to avoid duplication.
        // The backend injects the full blueprint text into its own prompt construction
        // (see class-vmo-enhanced-business-wizard.php → generate_consultation_response).
        // Including it here would double token consumption on every message.

        return prompt;
    }
    
    formatDocumentContext() {
        const docs = this.consultation_data.documents || [];
        if (docs.length === 0) return '';
        
        let ctx = '\nDOCUMENTS UPLOADED (Reference these for accurate business information):\n';
        let totalChars = 0;
        // ✅ GPT-5.2 UPGRADE: Raised from 12K to 60K — GPT-5.2 can handle full documents
        const maxChars = 60000; // Budget for document context
        
        docs.forEach(doc => {
            if (totalChars >= maxChars) return;
            
            ctx += `\n━━━ ${doc.title || doc.id} (${doc.mime || 'document'}) ━━━\n`;
            
            // Include full chunks, not just samples
            if (doc.chunks && doc.chunks.length > 0) {
                doc.chunks.forEach(chunk => {
                    if (totalChars >= maxChars) return;
                    const text = typeof chunk === 'string' ? chunk : (chunk.text || '');
                    const remaining = maxChars - totalChars;
                    const toAdd = text.substring(0, remaining);
                    ctx += toAdd + '\n';
                    totalChars += toAdd.length;
                });
            } else if (doc.content) {
                // Fallback: use raw content if no chunks
                const remaining = maxChars - totalChars;
                ctx += doc.content.substring(0, remaining) + '\n';
                totalChars += Math.min(doc.content.length, remaining);
            }
        });
        
        if (totalChars >= maxChars) {
            ctx += '\n[Document content truncated for context limit]\n';
        }
        
        return ctx;
    }
    
    formatMediaContext() {
        const media = this.consultation_data.media_files || [];
        if (media.length === 0) return '';
        
        let ctx = '\nMEDIA FILES AVAILABLE (Use these URLs directly in website generation):\n';
        media.forEach(m => {
            const roles = Array.isArray(m.roles) ? m.roles.join(', ') : (m.role || 'unclassified');
            const url = m.url || m.src || '';
            const title = m.title || m.id || 'Media';
            const mime = m.mime || m.type || 'unknown';
            ctx += `- ${title} (${roles}): ${url}\n`;
            ctx += `  Type: ${mime}\n`;
        });
        ctx += '\nIMPORTANT: Use these actual uploaded media URLs when generating the website. Do not use placeholder images if user media is available.\n';
        return ctx;
    }
    
    /**
     * Format scanned websites context for the AI prompt
     * Includes websites that have been scanned/analyzed for reference
     */
    formatScannedWebsitesContext() {
        const scannedWebsites = this.consultation_data.scanned_websites || {};
        
        if (Object.keys(scannedWebsites).length === 0) {
            return 'No websites have been scanned yet in this session.';
        }
        
        let ctx = 'The following websites have been scanned and analyzed:\n\n';
        
        for (const [url, data] of Object.entries(scannedWebsites)) {
            ctx += `━━━ ${url} ━━━\n`;
            ctx += `Purpose: ${data.purpose || 'reference'}\n`;
            ctx += `Title: ${data.title || 'Unknown'}\n`;
            ctx += `Scanned at: ${data.scanned_at || 'Unknown'}\n`;
            
            if (data.analysis) {
                if (data.analysis.business_type) {
                    ctx += `Business Type: ${data.analysis.business_type}\n`;
                }
                if (data.analysis.value_proposition) {
                    ctx += `Value Proposition: ${data.analysis.value_proposition}\n`;
                }
                if (data.analysis.key_features && Array.isArray(data.analysis.key_features)) {
                    ctx += `Key Features: ${data.analysis.key_features.slice(0, 5).join(', ')}\n`;
                }
                if (data.analysis.design_style) {
                    ctx += `Design Style: ${data.analysis.design_style}\n`;
                }
                if (data.analysis.summary) {
                    ctx += `Summary: ${data.analysis.summary}\n`;
                }
            }
            
            if (data.headings && Array.isArray(data.headings) && data.headings.length > 0) {
                ctx += `Main Headings: ${data.headings.slice(0, 5).join(', ')}\n`;
            }
            
            if (data.navigation && Array.isArray(data.navigation) && data.navigation.length > 0) {
                ctx += `Navigation Items: ${data.navigation.slice(0, 8).join(', ')}\n`;
            }
            
            ctx += '\n';
        }
        
        return ctx;
    }
    
    formatCollectedInformation() {
        const info = this.collectedInformation;
        let summary = '';
        
        if (info.businessIdea) summary += `- Business Idea: ${info.businessIdea}\n`;
        if (info.businessName) summary += `- Business Name: ${info.businessName}\n`;
        if (info.industry) summary += `- Industry: ${info.industry}\n`;
        if (info.targetAudience) summary += `- Target Audience: ${info.targetAudience}\n`;
        if (info.offeringType) summary += `- Offering Type: ${info.offeringType}\n`;
        if (info.offerings.length > 0) summary += `- Offerings: ${info.offerings.join(', ')}\n`;
        if (info.businessGoals) summary += `- Goals: ${info.businessGoals}\n`;
        if (info.location) summary += `- Location: ${info.location}\n`;
        
        return summary || '- Nothing collected yet (just starting the conversation)';
    }
    
    // ✅ FIX: Define allowed actions for each phase to prevent jumping ahead
    getPhaseAllowedActions(phaseIndex) {
        const hasDocument = this.blueprintSummary || this.blueprintRaw;
        const isEcommerce = this.consultation_data.extracted_information?.ecommerce_needed || false;
        
        const phaseRules = {
            0: hasDocument 
                ? "Phase 1 (Business Foundations Intake): Extract ALL answers from the uploaded plan. Quote from the document when possible. Only ask about missing or unclear items. NO recommendations yet—just intake." 
                : "Phase 1 (Business Foundations Intake): Learn the business through natural conversation. Ask one warm question at a time, stay novice-friendly. NO recommendations—just collect information. IMPORTANT: Ask if they have an existing website. If yes, ask if they want to keep/upgrade it or build new.",
            1: hasDocument
                ? "Phase 2 (Executive Blueprint Confirmation): Present a Q→A summary sourced from the document and conversation. Confirm accuracy, highlight anything missing, wait for explicit approval to proceed. NO recommendations."
                : "Phase 2 (Executive Blueprint Confirmation): Present a professional Q→A summary of everything learned. Ask for confirmation, collect corrections. Still NO recommendations.",
            2: hasDocument
                ? "Phase 3 (Deep Industry & Market Research): Run deep research using the document as the source of truth. Share market insights, competitor findings, legal considerations. Keep recommendations for Phase 4."
                : "Phase 3 (Deep Industry & Market Research): Conduct and summarize deep research (competitors, market trends, legal requirements). If user has an existing website, BENCHMARK it against 10-15 top competitors. Perform SWOT analysis. Draw conclusions on whether to improve existing or build new.",
            3: "Phase 4 (Strategic Recommendations & Decisions): FIRST TIME recommendations appear. Present the personalized checklist with pre-checked essentials. Explain 'Why I Recommend This' for each item. Mark improvements with 💡. Invite user to uncheck anything before proceeding.",
            4: "Phase 5 (Website & Experience Architecture): Design site map, page strategy, copy tone, and UX differentiators. Tie every section back to research & stakeholder goals. Configure payments/Stripe if e-commerce. Set up products/services and email.",
            5: "Phase 6 (Systems, Automations & Compliance): Define CRM, phone, email, analytics stack. List every legal/compliance document required. Configure business email (IMAP/SMTP) and optional bulk email provider.",
            6: "Phase 7 (Roadmap & Daily Execution Model): Generate complete website, legal documents, email system, CRM, phone system, marketing automation. Create business roadmap with milestones and daily activities.",
            7: "Phase 8 (Launch Readiness & Deliverables): Present Business Dashboard with KPIs. Walk through all sections. Demonstrate daily activities and roadmap. Provide training. Execute Go-Live deployment."
        };
        
        return phaseRules[phaseIndex] || "Follow the 8-phase consultation flow strictly.";
    }
    
    getNextStepsGuidance() {
        const topicsNeeded = this.consultationTopics.filter(t => !this.topicsCovered.has(t));
        
        if (topicsNeeded.length === 0) {
            return "All key topics covered! Summarize what you've learned and ask if they're ready to proceed with website generation.";
        }

        if (topicsNeeded.includes('website_status')) {
            return "Ask if they are building a new website from scratch or if they have an existing website they want to improve/redesign.";
        }
        
        if (topicsNeeded.includes('business_concept')) {
            return "Focus on understanding their core business idea - what they want to create/build/sell.";
        }
        
        if (topicsNeeded.includes('target_market')) {
            return "Learn about their target customers - who they serve and what problems they solve.";
        }
        
        if (topicsNeeded.includes('offerings')) {
            return "Understand what they're selling - products, services, or both. Ask naturally about details.";
        }
        
        if (topicsNeeded.includes('business_goals')) {
            return "Discuss their business goals and what success looks like to them.";
        }
        
        if (topicsNeeded.includes('website_vision')) {
            return "Explore their vision for their website - features, style, functionality they want.";
        }
        
        return "Continue the natural conversation, covering remaining topics organically.";
    }

    async performSiteAnalysis(url) {
        this.addMessageToUI('ai', `🔍 Analyzing your existing website at ${url}...`);
        
        let analysis = null;

        // Try the backend website scanner first
        try {
            const ajaxURL = (typeof vmo_ajax !== 'undefined' && vmo_ajax.ajaxurl) ? vmo_ajax.ajaxurl : '/wp-admin/admin-ajax.php';
            const nonce = (typeof vmo_ajax !== 'undefined' && vmo_ajax.nonce) ? vmo_ajax.nonce : '';
            const formData = new FormData();
            formData.append('action', 'vmo_scan_website');
            formData.append('security', nonce);
            formData.append('url', url);
            formData.append('purpose', 'analysis');

            const resp = await fetch(ajaxURL, { method: 'POST', body: formData });
            const json = await resp.json();
            if (json.success && json.data) {
                const d = json.data;
                const scanAnalysis = d.analysis || {};
                const extracted = d.extracted || {};
                const issues = [];
                const assets = [];

                // Build issues from scan results
                if (scanAnalysis.mobile_friendly === false) issues.push('Mobile responsiveness needs improvement');
                if (scanAnalysis.has_ssl === false) issues.push('SSL certificate is missing or invalid');
                if (scanAnalysis.load_time && scanAnalysis.load_time > 3) issues.push(`Page load time is ${scanAnalysis.load_time}s (should be under 3s)`);
                if (!extracted.meta_description) issues.push('Meta description is missing');
                if (!extracted.og_image) issues.push('Open Graph image is missing (bad for social sharing)');
                if (scanAnalysis.issues && Array.isArray(scanAnalysis.issues)) issues.push(...scanAnalysis.issues);
                if (issues.length === 0) issues.push('No critical issues detected — your site has a solid foundation');

                // Build assets
                if (extracted.title) assets.push(`Page title: "${extracted.title}"`);
                if (extracted.headings && extracted.headings.length) assets.push(`Found ${extracted.headings.length} content sections`);
                if (extracted.images && extracted.images.length) assets.push(`Detected ${extracted.images.length} images`);
                if (extracted.navigation && extracted.navigation.length) assets.push(`Found ${extracted.navigation.length} navigation links`);
                if (scanAnalysis.color_scheme) assets.push(`Brand colors: ${scanAnalysis.color_scheme}`);

                const score = scanAnalysis.score || Math.min(95, Math.max(20, 100 - (issues.length * 12)));

                analysis = { score, issues, assets };
            }
        } catch (e) {
            console.warn('VMO: Backend site analysis unavailable, using lightweight analysis:', e.message);
        }

        // Fallback: lightweight client-side analysis
        if (!analysis) {
            await this.delay(1500);
            analysis = {
                score: 0,
                issues: ['Could not reach the website for analysis — the URL may be unreachable or protected'],
                assets: [`Target URL: ${url}`]
            };
        }
        
        const reportHtml = `
            <div class="vmo-site-analysis" style="background:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; padding:15px; margin-top:10px;">
                <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
                    <h4 style="margin:0;">📊 Site Analysis Report</h4>
                    <span style="background:${analysis.score > 70 ? '#dcfce7' : '#fef9c3'}; color:${analysis.score > 70 ? '#166534' : '#854d0e'}; padding:4px 8px; border-radius:4px; font-weight:bold;">Score: ${analysis.score}/100</span>
                </div>
                <p style="font-size:13px; color:#64748b;">I've scanned your site and found some great opportunities for improvement:</p>
                <ul style="font-size:13px; color:#334155; padding-left:20px;">
                    ${analysis.issues.map(i => `<li>${i}</li>`).join('')}
                </ul>
                <div style="margin-top:15px; padding-top:10px; border-top:1px solid #e2e8f0;">
                    <p style="font-size:13px; font-weight:bold;">My Proposal:</p>
                    <p style="font-size:13px; color:#64748b;">I can import your existing content (text & images) and apply a modern, high-converting VMO design. You can preview the new version without affecting your live site.</p>
                    <p style="font-size:13px; color:#64748b; margin-top:8px;"><strong>Note:</strong> Even if you keep your site, I will still perform a full business consultation (SEO, Phone System, Policies, etc.) to upgrade your business operations.</p>
                    <button class="vmo-btn-primary" onclick="window.vmoStartSiteImport('${url}')" style="width:100%; margin-top:10px;">
                        🚀 Import & Modernize (Safe Preview)
                    </button>
                </div>
            </div>
        `;
        
        this.addMessageToUI('ai', reportHtml);
        
        // Add to history
        this.conversationHistory.push({
            role: 'assistant',
            content: `Analyzed site ${url}. Score: ${analysis.score}. Proposed import & modernize strategy.`,
            timestamp: new Date().toISOString()
        });
    }

    async performDNSCheckAndInstruct() {
        this.addMessageToUI('ai', '🔍 Checking your domain configuration...');
        
        try {
            const AX = this.getAjax();
            // 1. Try to auto-detect from the current environment
            let domain = this.getProvisioningHostFallback();
            if (domain === 'example.com') domain = null;

            // 2. If auto-detect failed (staging/local), check extracted info
            if (!domain) {
                domain = this.consultation_data.extracted_information?.domain;
            }
            
            // 3. Fallback: Try to find domain in recent messages if not extracted yet
            if (!domain) {
                const recentUserMsgs = this.conversationHistory
                    .filter(m => m.role === 'user')
                    .slice(-3)
                    .map(m => m.content)
                    .join(' ');
                const domainMatch = recentUserMsgs.match(/[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}/);
                if (domainMatch) domain = domainMatch[0];
            }

            if (!domain) {
                this.addMessageToUI('ai', 'I need to check your domain settings, but I don\'t have your domain name yet. Could you please type it for me? (e.g., mybusiness.com)');
                return;
            }

            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_detect_dns_host',
                    security: AX.nonce,
                    domain: domain
                })
            });
            
            const data = await response.json();
            if (!data.success) throw new Error(data.data?.message || 'DNS check failed');
            
            const result = data.data;
            
            const providerLabel = (result.host_type && result.host_type !== 'unknown') ? this.formatHostName(result.host_type) : 'your provider';
            this.addMessageToUI('ai', `✅ Domain check complete. Your domain appears to be managed by ${providerLabel}.\n\nWe won\'t change your DNS. Next, please tell me which professional mailboxes you already have (e.g., info@${domain}, support@${domain}, billing@${domain}) or create them inside your current email/hosting provider. Then we\'ll connect them here using IMAP/SMTP.`);
            return;
            
        } catch (error) {
            console.error('DNS Check Error:', error);
            this.addMessageToUI('ai', 'I encountered an issue checking your DNS settings. We can proceed manually.');
        }
    }

    formatHostName(type) {
        const map = {
            'godaddy': 'GoDaddy',
            'cloudflare': 'Cloudflare',
            'google_cloud_dns': 'Google Domains',
            'azure_dns': 'Azure',
            'digitalocean': 'DigitalOcean',
            'namecheap': 'Namecheap',
            'bluehost': 'Bluehost',
            'hostgator': 'HostGator'
        };
        return map[type] || 'Domain Provider';
    }

    getRegistrarLoginUrl(type) {
        const map = {
            'godaddy': 'https://dcc.godaddy.com/manage/dns',
            'cloudflare': 'https://dash.cloudflare.com/',
            'google_cloud_dns': 'https://domains.google.com/registrar',
            'namecheap': 'https://ap.www.namecheap.com/',
            'bluehost': 'https://my.bluehost.com/cgi/dm/dns',
            'hostgator': 'https://portal.hostgator.com/domain/manage',
            'digitalocean': 'https://cloud.digitalocean.com/networking/domains',
            'azure_dns': 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.Network%2FdnsZones'
        };
        return map[type] || 'https://www.google.com/search?q=login+to+my+domain+registrar';
    }

    getPhaseSpecificGoals(phaseIndex) {
        const isEcommerce = this.consultation_data.extracted_information?.ecommerce_needed || false;
        
        switch (phaseIndex) {
            case 0:
                return `Phase 1 (Vision Capture & Media Collection) goals:

🚨 CRITICAL: VMO IS NOT LIKE OTHER WEBSITE BUILDERS 🚨
Unlike other platforms that just build what users ask for, VMO:
- DEEPLY UNDERSTANDS the user's business vision, dreams, and goals
- Does NOT assume the user knows their ideal target market
- In Phase 3, researches the ENTIRE market to find REAL opportunities
- In Phase 4, CONTRASTS user's preferences against research findings
- Gives user FULL DISCRETION to choose their original idea OR our recommendations

PHOTOGRAPHER EXAMPLE:
- User wants: Artistic portfolio website (copying art photographers)
- User's actual goal: Make money taking photos
- AI discovers in Phase 3: Schools, organizations, businesses need bulk photography
- Phase 4 recommendation: Website designed to win contracts from bulk clients

🚨🚨🚨 ABSOLUTE RULE — NEVER RE-ASK ANSWERED QUESTIONS 🚨🚨🚨
Before asking ANY question, check THREE sources:
1. The "ALREADY ANSWERED" section in the system prompt above
2. The "KNOWN ANSWERS" JSON in the system prompt above
3. The conversation history messages
If the answer exists in ANY source → DO NOT ASK. Reference it instead.
VIOLATION = CONSULTATION FAILURE. This is the #1 rule.

📋 TOPIC CHECKLIST (Reference only — NOT a sequential script):
These are the topics to cover. SKIP any topic already answered.
Only ask about topics with NO answer in extracted_information or conversation history.

BUSINESS FOUNDATION TOPICS:
□ Business concept (what they do and for whom)
□ Success definition (money, freedom, impact, mix)
□ Scale intent (side income, main income, or big company)
□ Hands-on preference (run everything vs. automation)
□ Timeline/vision timeframe
□ Offering type (products, services, or both)
□ Payment collection (products/donations/none on website)
□ Online/physical/both
□ Customer origin (local, national, global)
□ Business name
□ Budget/constraints
□ Target audience (their best guess — AI researches later)

WEBSITE VISION TOPICS (ask ONLY after business foundation topics covered):
□ Look and feel preference
□ Inspiration websites
□ Must-have features
□ Specific/custom pages needed
□ Affiliate/referral program needed?
□ Special tools/calculators needed?

MEDIA COLLECTION TOPICS (ask ONLY after website vision):
□ Logo (have one? upload it / create one later?)
□ Photos (self, work, products, team, location)
□ Videos (homepage, demos, testimonials)
□ Written content (descriptions, about text, blog)
□ Pricing for products/services

EMAIL PREFERENCES TOPICS (ask ONLY after media):
□ Contact/notification email address
□ Branded email addresses wanted?
□ Email types (welcome, orders, newsletter, promos, abandoned cart)
□ Email frequency
□ Existing email list?

💳 PAYMENT COLLECTION RULES:
- If extracted_information.payment_collection.confirmed === true → ALREADY DONE, skip.
- If selling products → capture product catalog (name, price, description, variants, images)
- If donations → capture fund categories and preset amounts
- Store in extracted_information.payment_collection and ecommerce fields

🖼️ MEDIA UPLOAD RULES:
- When user uploads a logo → acknowledge it, store hasLogo = true, DO NOT ask for logo again
- When user uploads any image → acknowledge it, assign role, continue consultation
- NEVER stall after an upload — always continue to the next unanswered topic

🏁 COMPLETION:
When all topics above are covered (or user says "proceed"/"move on"):
- Say "I have everything I need. Ready to continue to your business summary?"
- ONE confirmation request only. If they confirm → IMMEDIATELY move to Phase 2.
- Do NOT provide a long summary here — Phase 2 handles that.

📄 DOCUMENT UPLOAD SHORTCUT:
If the user uploads a business document that covers most topics:
- Absorb ALL information from it
- Mark covered topics as DONE
- Only ask about the FEW topics not covered in the document
- Do NOT re-ask anything covered in the document
- This can skip most of Phase 1

⚠️ MEDIA TAGGING RULES:
- Every uploaded asset MUST be tagged with its intended use (logo, hero_video, product_image, etc.)
- When generating website in Phase 7, use EXACTLY the right media in the right place
- NEVER mix up media placements

⚠️ DATA CAPTURE RULES:
- Store custom pages in extracted_information.customPages[]
- Store special features in extracted_information.specialFeatures[]
- Store logo status in extracted_information.hasLogo
- Store brand assets in extracted_information.brandAssets[]
- Store email preferences in extracted_information.email_preferences{}

⚠️ RULES:
- Ask ONE question at a time, conversationally
- Use novice-friendly language - let them express dreams, YOU translate to professional terms
- Do NOT skip ahead to website questions before business vision is complete
- Do NOT skip ahead to website questions before email preferences are captured
- Do NOT assume they know their target market - that's what Phase 3 is for
- Website preferences are just initial inputs - Phase 3 research will inform final recommendations
- ZERO recommendations in Phase 1 - you are LISTENING and RECORDING only
- REMEMBER: The user's specific page requests MUST appear in Phase 4 recommendations`;
            case 1:
                return `Phase 2 (Business Summary Confirmation) goals:
1. Present a "Question → Answer" style summary using the user's own words.
2. Highlight any open blanks that still need information.
3. INCLUDE a section for "Requested Pages & Features" listing any custom pages they mentioned.
4. INCLUDE a section for "Email & Communication Setup" showing their email preferences.
5. INCLUDE a section for "Media Assets" showing what they've uploaded and what's still needed.
6. At the END of the summary, ask ONE simple question: "Does this look correct? If so, I'll begin the deep market research."
7. No recommendations yet.

🚨 CRITICAL PHASE 2 RULES:
- Present the summary ONCE only
- If user confirms ("yes", "correct", "proceed", etc.), IMMEDIATELY transition to Phase 3
- Do NOT re-show the summary after confirmation
- Do NOT ask "Is there anything else?" after they confirm
- ONE confirmation = Phase 2 complete = Begin Phase 3`;
            case 2:
                return `Phase 3 (Comprehensive 10-Category Market Research) goals:
⚠️ IMPORTANT: The deep research is handled by a dedicated background system with a visual progress bar. 
DO NOT try to perform the full deep research natively in your chat responses! 

STATE 1 - WAITING FOR CONFIRMATION:
If the user hasn't confirmed yet, simply tell them to click the "Yes, Start Comprehensive Research" button or say "Yes" to begin.

STATE 2 - RESEARCH IN PROGRESS:
If they ask questions while the progress bar is running, answer them briefly but remind them the full research is actively processing in the background and will take a few minutes.

STATE 3 - RESEARCH COMPLETE:
When research is fully complete (results will appear in your context), ONLY then transition to Phase 4.`;
            case 3:
                return `Phase 4 (Contrast & Strategic Recommendations) goals:
🔥 THIS IS WHERE VMO ADDS UNIQUE VALUE

STEP 1: Present Research Summary
- Target market discovery (may differ from user's guess!)
- Competitive landscape with gaps
- Opportunities discovered
- SEO & digital landscape

STEP 2: CONTRAST USER'S IDEAS VS. RESEARCH (CRITICAL)
For each major area, show side-by-side:
| YOUR ORIGINAL IDEA | RESEARCH FINDING | MY RECOMMENDATION |
- Target market (who they imagined vs. who research found)
- Pricing strategy (what they wanted vs. what market supports)
- Website style (their preference vs. what converts)
- Business model (their idea vs. optimized approach)
- Marketing channels (their guess vs. where customers are)

STEP 3: Give User FULL DISCRETION
For each contrast area, ask:
"Would you like to go with your original plan, try my recommendation, or do a hybrid?"

STEP 4: Finalize Customized Execution Checklist
Based on user's choices, present final plan and get approval.`;
            case 4:
                return `Phase 5 (Payments & Stripe Onboarding) goals:
1. Configure VMO Pay payment processing with Stripe Connect integration.
2. Guide user through Stripe Express onboarding for payouts.
3. Set up payment methods (cards, Apple Pay, Google Pay) and currencies.
4. Configure pricing structure and subscription options if needed.
5. Ensure PCI compliance and secure payment handling.`;
            case 5:
                return `Phase 6 (Product/Service Creation + Email Setup) goals:

STEP 1: Prepare the Catalog
Create products/services with pricing so checkout flows can be generated.

STEP 2: Generate Buy Buttons / Checkout Links
Ensure the store catalog is created so each product can be purchased.

STEP 3: Email System Setup
Securely connect IMAP/SMTP for transactional messages and optionally connect Brevo/MailerLite for bulk campaigns.

STEP 4: Confirm & Continue
Proceed to full website generation.`;
            case 6:
                return `Phase 7 (Full Business Generation) goals:
⚠️ WARN USER: This generation may take several minutes. Progress will be saved.

1. Generate complete website with all pages, content, and design.
2. Create all legal documents (Privacy Policy, Terms, Refund Policy).
3. Configure email system with professional mailboxes.
4. Set up CRM, AI phone receptionist, and marketing automation.
5. Generate business roadmap with milestones and daily activities.
6. Create operational documentation and SOPs.
7. Ensure GDPR/CCPA compliance and accessibility (ADA/WCAG).

⚠️ MEDIA PLACEMENT: Use EXACTLY the right media from Phase 1 and Phase 6 in the right places:
- Logo → header and footer
- Hero video → homepage hero section
- Product images → product pages (correct product!)
- Portfolio pieces → gallery/portfolio page

🚨 MANDATORY MEDIA GENERATION RULE:
If the user has NOT provided images or videos, you MUST generate ALL media using AI.
- Mark EVERY image with data-vmo-generate='true' and data-vmo-prompt='[detailed generation prompt]'
- Mark EVERY hero video with data-vmo-sora='true' and data-vmo-sora-prompt='[cinematic description]'
- NEVER leave ANY visual section empty — the website MUST look complete and premium
- NEVER use placeholder.com, example.com, picsum.photos, or any other placeholder URLs
- NEVER skip, omit, or defer media generation — ALL media MUST be present in the generated website
- If specific content was not discussed, CREATE appropriate professional content based on the business type`;
            case 7:
                return `Phase 8 (Dashboard Control & Go-Live) goals:
1. Present the complete Business Dashboard with all KPIs.
2. Walk through each dashboard section (Website, Products, CRM, Analytics).
3. Demonstrate daily activities tracker and roadmap progress.
4. Provide training on managing products, orders, and customer interactions.
5. Confirm all systems are operational and user is confident to go live.
6. Execute Go-Live deployment and monitor initial performance.`;
            default:
                return 'Follow the structured 8-phase consultation flow.';
        }
    }

    getScenarioGuidance() {
        const scenario = this.consultation_data.scenario_type || this.scenarioType || 'scenario_a_new';
        switch (scenario) {
            case 'scenario_d_discovery':
            case 'scenario_d_income_goal':
            case 'scenario_d_turnkey':
                return `SCENARIO D (Discovery Mode - No Business Idea Yet):
- User has NO IDEA what business to start - they need brainstorming help
- Do NOT ask about business details yet - first explore WHO THEY ARE
- Ask about: passions, hobbies, skills, work experience, personality, lifestyle, location
- After understanding them, generate 3-5 personalized business ideas that match their profile

💰 INCOME-GOAL DRIVEN DISCOVERY:
- If user mentions income goals ("I want to make $X"), focus on that
- Ask: How much do you want to earn? By when? What can you invest? Where are you located?
- Perform REALITY CHECK: Is their goal achievable with their constraints?
- If unrealistic, be honest but kind and offer achievable alternatives

🌍 LOCATION-BASED OPPORTUNITIES:
- Based on their location, suggest lucrative businesses even if they have NO experience
- Examples: Export agency in Nigeria, Amazon FBA in USA, Dropshipping anywhere
- Focus on LOW-SKILL businesses where they just need to follow steps

🔑 TURNKEY MODE (when they say "just tell me what to do"):
- VMO PRESCRIBES everything: business name, vision, mission, targets
- VMO CALCULATES realistic income trajectory (Month 1, 6, 12)
- VMO BUILDS: website, emails, payment system, legal docs
- VMO TEACHES: How the business works, daily tasks, common mistakes
- VMO GUIDES: Daily step-by-step task list to follow
- User just approves and follows instructions

Be warm, encouraging, and help them discover their entrepreneurial path`;
            case 'scenario_b_existing':
                return `SCENARIO B (Existing Business):
- Assume the user already has operations, a website, or customers.
- Ask what they want improved before suggesting rebuilds.
- Reference their current assets (brand, systems, marketing) and focus on upgrades.`;
            case 'scenario_c_document':
                return `SCENARIO C (Document Upload):
- Treat the uploaded business plan as the primary truth source.
- Quote from the document when answering questions.
- Only ask about missing details or confirmations.`;
            case 'scenario_a_new':
            default:
                return `SCENARIO A (New Founder):
- Assume they are starting from scratch and may feel overwhelmed.
- Use simple language, celebrate their idea, and guide them step-by-step.`;
        }
    }
    
    async extractInformationFromConversation(userMessage, aiResponse) {
        // Use AI to intelligently extract structured information from natural conversation
        // This happens in the background - user doesn't see it

        const cleanedUserMessage = this.cleanDisplayValue(userMessage || '');
        if (!this.isExtractionEligible(cleanedUserMessage)) {
            return;
        }
        
        const extractionPrompt = `Analyze this conversation exchange and extract any business information mentioned:

USER: "${userMessage}"
AI: "${aiResponse}"

CURRENT INFORMATION:
${JSON.stringify(this.collectedInformation, null, 2)}

Extract and update ONLY the information explicitly mentioned. Return JSON with these fields (set to null if not mentioned):
{
    "businessIdea": "brief description of their business concept",
    "businessName": "business name if mentioned",
    "domain": "domain name if mentioned",
    "industry": "industry/sector",
    "targetAudience": "who they serve",
    "offeringType": "products" | "services" | "both" | null,
    "offerings": ["list of specific products/services mentioned"],
    "businessGoals": "what they want to achieve",
    "successDefinition": "specific metric or outcome that defines success",
    "scaleIntent": "how big they want to grow and team size preference",
    "automationPreference": "how automated vs hands-on they want operations",
    "websitePreferences": "style, features, or functionality mentioned",
    "brandingInfo": "colors, logo, style preferences",
    "location": "business location",
    "needsEcommerce": true/false,
    "payment_collection": {
        "collects_payments": true/false/null,
        "mode": "products" | "donations" | "none" | null,
        "confirmed": true/false/null
    },
    "ecommerce": {
        "collection_mode": "products" | "donations" | null,
        "donation_funds": ["list of donation fund categories/types if mentioned"]
    },
    "products_inventory": [
        {
            "name": "product name",
            "description": "product description",
            "price": "number or string",
            "currency": "USD/EUR/etc if mentioned",
            "main_image": {"url": "..."},
            "gallery_images": [{"url": "..."}],
            "videos": [{"url": "...", "role": "demo|promo"}],
            "variants": ["any variants mentioned (size/color/etc)"]
        }
    ],
    "services_inventory": [
        {
            "name": "service name",
            "description": "service description",
            "price": "number or string",
            "duration": "duration if mentioned",
            "images": [{"url": "..."}],
            "videos": [{"url": "..."}]
        }
    ],
    "needsBooking": true/false,
    "needsPhoneSystem": true/false,
    "needsEmailMarketing": true/false,
    "email_preferences": {
        "emails": ["list of email addresses mentioned - e.g. 'info@domain.com', 'support@domain.com'"],
        "campaignTypes": ["newsletter", "promotions", "transactional", etc.],
        "frequency": "daily/weekly/monthly if mentioned",
        "hasBrandedEmail": true/false if they have their own domain email
    },
    "customPages": ["list of specific pages user wants - e.g. 'affiliate-signup', 'faq', 'referral-calculator', 'partner-dashboard', 'pricing', 'gallery', 'testimonials', 'case-studies', 'blog', 'member-portal'"],
    "specialFeatures": ["list of special features - e.g. 'affiliate-program', 'earnings-calculator', 'quote-generator', 'booking-calendar', 'membership-area'"],
    "hasLogo": true/false if they mentioned having a logo,
    "hasProductImages": true/false if they mentioned having product images,
    "hasVideos": true/false if they mentioned having videos,
    "topicCovered": "business_concept" | "target_market" | "offerings" | "business_goals" | "website_vision" | "custom_pages" | "brand_assets" | "email_preferences" | "operations_preferences" | null
}

IMPORTANT: Look for ANY mention of specific pages or features the user wants. Common patterns:
- "I want a referral page" → customPages: ["referral-signup"]
- "affiliate program with calculator" → customPages: ["affiliate-signup", "affiliate-dashboard"], specialFeatures: ["earnings-calculator"]
- "FAQ section" → customPages: ["faq"]
- "I have a logo" → hasLogo: true
- "I'll upload photos later" → hasProductImages: false (they mentioned it but don't have them now)`;
        
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams({
                    action: 'vmo_extract_consultation_info',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    prompt: extractionPrompt,
                    session_id: this.sessionId
                })
            });
            
            const data = await response.json();
            
            if (data.success && data.data.extracted) {
                const extracted = data.data.extracted;
                
                // ✅ Array fields that should be merged, not replaced (primitive arrays)
                const mergePrimitiveArrayFields = new Set(['offerings', 'customPages', 'specialFeatures', 'brandAssets', 'productImages', 'videoUrls']);
                
                // Update collected information
                Object.keys(extracted).forEach(key => {
                    if (extracted[key] === null || key === 'topicCovered') return;

                    const incoming = extracted[key];
                    const existing = this.collectedInformation ? this.collectedInformation[key] : undefined;

                    // Merge primitive arrays
                    if (mergePrimitiveArrayFields.has(key) && Array.isArray(incoming)) {
                        const currentArr = Array.isArray(existing)
                            ? existing
                            : (existing ? [existing] : []);
                        const merged = [...currentArr, ...incoming].filter(Boolean);
                        this.collectedInformation[key] = Array.from(new Set(merged));
                        return;
                    }

                    // Deep-merge plain objects to avoid stomping nested structures
                    if (incoming && typeof incoming === 'object' && !Array.isArray(incoming)) {
                        const currentObj = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
                        this.collectedInformation[key] = this.deepMerge(currentObj, incoming);
                        return;
                    }

                    // Default: replace
                    this.collectedInformation[key] = incoming;
                });
                
                // Mark topic as covered
                if (extracted.topicCovered && this.isMeaningfulForTopic(cleanedUserMessage, extracted.topicCovered)) {
                    this.topicsCovered.add(extracted.topicCovered);
                }
                
                console.log('📊 Information extracted:', extracted);
                console.log('📋 Current state:', this.collectedInformation);
                console.log('📄 Document state:', {
                    blueprintRaw: this.blueprintRaw ? `${this.blueprintRaw.length} chars` : 'NOT SET',
                    blueprintSummary: this.blueprintSummary ? 'EXISTS' : 'NOT SET',
                    hasDocument: !!(this.blueprintRaw && this.blueprintRaw.length > 0)
                });

                if (window.vmo_consultant_context_v1) {
                    await this.saveConsultationData({
                        extracted_information: this._toCanonicalExtracted(),
                        blueprintRaw: this.blueprintRaw,
                        blueprintSummary: this.blueprintSummary,
                        documents: this.consultation_data.documents || [],
                        media_files: this._toCanonicalMediaFiles()
                    });
                }

                await this.maybeFinalizeDocumentIntake();
            }
        } catch (error) {
            console.error('Information extraction error:', error);
            // Non-critical - conversation continues even if extraction fails
        }
    }

    async analyzeDocument(rawText) {
        const text = (rawText || '').trim();
        if (!text) {
            console.warn('⚠️ analyzeDocument called with empty text');
            return;
        }

        this.isProcessing = true;
        this.setVoiceStatus('thinking');

        try {
            await this.ingestBlueprintText(text, 'Business Plan (pasted)', '');
        } catch (error) {
            console.error('📄 Inline document analysis failed:', error);
            this.addMessageToUI('ai', 'I ran into an issue analyzing that document paste. Please try uploading the file instead.');
        } finally {
            this.isProcessing = false;
            this.setVoiceStatus('ready');
        }
    }

    // ═══════════════════════════════════════════════════════════
    // ANIMATED ANALYZING INDICATOR
    // Shows a pulsing message with animated dots while the AI
    // processes uploaded documents, so the user knows it's working.
    // ═══════════════════════════════════════════════════════════

    _showAnalyzingIndicator(filename) {
        // Remove any existing indicator first
        this._removeAnalyzingIndicator();

        const chatContainer = document.querySelector('#vmo-wizard-conversation')
            || document.querySelector('#vmo-chat-messages, .vmo-chat-messages, [data-vmo-chat-messages]');
        if (!chatContainer) return;

        const shortName = filename && filename.length > 35
            ? filename.substring(0, 32) + '…'
            : (filename || 'your document');

        const messageDiv = document.createElement('div');
        messageDiv.className = 'vmo-ai-message vmo-analyzing-indicator';
        messageDiv.innerHTML = `
            <div class="vmo-message-content" style="display:flex; align-items:center; gap:10px; padding:12px 16px;">
                <span class="vmo-analyzing-spinner" style="
                    display:inline-block; width:18px; height:18px; border:2.5px solid rgba(0,228,255,0.25);
                    border-top-color:#00e4ff; border-radius:50%;
                    animation: vmo-analyze-spin 0.8s linear infinite;
                "></span>
                <span class="vmo-analyzing-text" style="color:#e6f3ff; font-size:14px;">
                    Reviewing <strong>${this._escapeHtml(shortName)}</strong><span class="vmo-analyzing-dots" style="display:inline-block; min-width:18px;"></span>
                </span>
            </div>`;

        chatContainer.appendChild(messageDiv);
        chatContainer.scrollTop = chatContainer.scrollHeight;

        // Animate the dots: . → .. → ... → (repeat)
        let dotCount = 0;
        const dotsEl = messageDiv.querySelector('.vmo-analyzing-dots');
        this._analyzingDotsTimer = setInterval(() => {
            dotCount = (dotCount + 1) % 4;
            if (dotsEl) dotsEl.textContent = '.'.repeat(dotCount || 1);
        }, 500);

        // Inject keyframe once
        if (!document.getElementById('vmo-analyze-spin-style')) {
            const style = document.createElement('style');
            style.id = 'vmo-analyze-spin-style';
            style.textContent = `@keyframes vmo-analyze-spin { to { transform: rotate(360deg); } }`;
            document.head.appendChild(style);
        }
    }

    _removeAnalyzingIndicator() {
        // Clear dots animation timer
        if (this._analyzingDotsTimer) {
            clearInterval(this._analyzingDotsTimer);
            this._analyzingDotsTimer = null;
        }
        // Remove the indicator element from the DOM
        const indicator = document.querySelector('.vmo-analyzing-indicator');
        if (indicator) {
            indicator.remove();
        }
    }

    /**
     * Show an animated "AI is thinking" indicator in the chat while waiting for a response.
     * Appears as a chat bubble with bouncing dots so the user knows the AI is working.
     */
    _showThinkingIndicator() {
        // Remove any existing indicator first (no duplicates)
        this._removeThinkingIndicator();

        const chatContainer = document.querySelector('#vmo-wizard-conversation')
            || document.querySelector('#vmo-chat-messages, .vmo-chat-messages, [data-vmo-chat-messages]');
        if (!chatContainer) return;

        const messageDiv = document.createElement('div');
        messageDiv.className = 'vmo-ai-message vmo-thinking-indicator';
        messageDiv.innerHTML = `
            <div class="vmo-message-header">
                <span class="vmo-message-role">${this.getMessageLabel('ai')}</span>
            </div>
            <div class="vmo-message-content" style="display:flex; align-items:center; gap:10px; padding:10px 16px;">
                <span class="vmo-thinking-spinner" style="
                    display:inline-block; width:16px; height:16px;
                    border:2px solid rgba(0,228,255,0.2);
                    border-top-color:#00e4ff; border-radius:50%;
                    animation: vmo-analyze-spin 0.8s linear infinite;
                    flex-shrink:0;
                "></span>
                <span style="color:#c0d8f0; font-size:14px;">Thinking<span class="vmo-thinking-dots" style="display:inline-block; min-width:18px;"></span></span>
            </div>`;

        chatContainer.appendChild(messageDiv);
        chatContainer.scrollTop = chatContainer.scrollHeight;

        // Animate the dots: . → .. → ... → (repeat)
        let dotCount = 0;
        const dotsEl = messageDiv.querySelector('.vmo-thinking-dots');
        this._thinkingDotsTimer = setInterval(() => {
            dotCount = (dotCount + 1) % 4;
            if (dotsEl) dotsEl.textContent = '.'.repeat(dotCount || 1);
        }, 500);

        // Inject keyframe once (shared with analyzing indicator)
        if (!document.getElementById('vmo-analyze-spin-style')) {
            const style = document.createElement('style');
            style.id = 'vmo-analyze-spin-style';
            style.textContent = `@keyframes vmo-analyze-spin { to { transform: rotate(360deg); } }`;
            document.head.appendChild(style);
        }
    }

    /**
     * Remove the "AI is thinking" indicator from the chat.
     */
    _removeThinkingIndicator() {
        if (this._thinkingDotsTimer) {
            clearInterval(this._thinkingDotsTimer);
            this._thinkingDotsTimer = null;
        }
        const indicator = document.querySelector('.vmo-thinking-indicator');
        if (indicator) {
            indicator.remove();
        }
    }

    _escapeHtml(str) {
        const div = document.createElement('div');
        div.textContent = str || '';
        return div.innerHTML;
    }

    addMessageToUI(sender, message, options = {}) {
        const chatContainer = document.querySelector('#vmo-wizard-conversation') || document.querySelector('#vmo-chat-messages, .vmo-chat-messages, [data-vmo-chat-messages]');
        if (!chatContainer) {
            console.warn('Chat container not found');
            return null;
        }
        
        const messageDiv = document.createElement('div');
        messageDiv.className = sender === 'user' ? 'vmo-user-message' : 'vmo-ai-message';

        const header = document.createElement('div');
        header.className = 'vmo-message-header';

        const role = document.createElement('span');
        role.className = 'vmo-message-role';
        role.textContent = this.getMessageLabel(sender);

        const timestampMeta = this.formatMessageTimestamp();
        const timeEl = document.createElement('time');
        timeEl.className = 'vmo-message-timestamp';
        timeEl.setAttribute('datetime', timestampMeta.iso);
        timeEl.textContent = timestampMeta.display;

        header.appendChild(role);
        header.appendChild(timeEl);
        messageDiv.appendChild(header);

        const content = document.createElement('div');
        content.className = 'vmo-message-content';
        
        // ✅ FIX: Support progressive text reveal for synchronized audio playback
        if (options.progressive && sender === 'ai') {
            // Start with empty content - will be filled progressively
            content.innerHTML = '<span class="vmo-typing-cursor">▊</span>';
            content.setAttribute('data-full-message', String(message || ''));
        } else {
            content.innerHTML = this._formatMessageHtml(String(message || ''));
        }

        messageDiv.appendChild(content);
        chatContainer.appendChild(messageDiv);
        
        // Scroll to bottom
        chatContainer.scrollTop = chatContainer.scrollHeight;
        
        // Return content element for progressive updates
        return content;
    }
    
    /**
     * ✅ NEW: Start synchronized text reveal with audio playback
     * Text reveals progressively as audio plays, matching speaking pace
     */
    startSynchronizedTextReveal(contentElement, fullMessage, audioDuration = null) {
        // Stop any existing animation
        this.stopTextAnimation();
        
        if (!contentElement || !fullMessage) return;
        
        this._activeMessageElement = contentElement;
        this._fullMessageText = String(fullMessage);
        this._textRevealIndex = 0;
        
        // Calculate reveal speed based on audio duration or speaking rate
        const totalChars = this._fullMessageText.length;
        let msPerChar;
        
        if (audioDuration && audioDuration > 0) {
            // Sync to actual audio duration (reveal finishes when audio ends)
            msPerChar = (audioDuration * 1000) / totalChars;
        } else {
            // Estimate based on speaking rate (~2.8 words/sec, ~5 chars/word)
            msPerChar = 1000 / (this._ttsWordsPerSecond * 5);
        }
        
        // Clamp to reasonable bounds (10-100ms per char)
        msPerChar = Math.max(10, Math.min(100, msPerChar));
        
        console.log(`📝 Starting synchronized text reveal: ${totalChars} chars at ${msPerChar.toFixed(1)}ms/char`);
        
        // ✅ LIVE TRANSCRIPT: Show initial AI message in listening modal
        this.updateLiveTranscript(this._fullMessageText.substring(0, 50) + (this._fullMessageText.length > 50 ? '...' : ''), 'ai');
        
        // Progressive reveal interval
        this._activeTextAnimation = setInterval(() => {
            if (this._textRevealIndex >= this._fullMessageText.length) {
                // Animation complete
                this.stopTextAnimation();
                contentElement.innerHTML = this._formatMessageHtml(this._fullMessageText);
                // ✅ LIVE TRANSCRIPT: Clear when AI finishes speaking
                this.clearLiveTranscript();
                return;
            }
            
            // Reveal next chunk (3-5 chars at a time for smoother animation)
            const chunkSize = Math.min(3, this._fullMessageText.length - this._textRevealIndex);
            this._textRevealIndex += chunkSize;
            
            const revealedText = this._fullMessageText.substring(0, this._textRevealIndex);
            const remaining = this._fullMessageText.length - this._textRevealIndex;
            
            // ✅ LIVE TRANSCRIPT: Update with current spoken portion (last ~80 chars)
            const transcriptStart = Math.max(0, this._textRevealIndex - 80);
            const liveText = this._fullMessageText.substring(transcriptStart, this._textRevealIndex);
            this.updateLiveTranscript((transcriptStart > 0 ? '...' : '') + liveText, 'ai');
            
            // Show revealed text with cursor if still animating
            if (remaining > 0) {
                contentElement.innerHTML = this._formatMessageHtml(revealedText) + '<span class="vmo-typing-cursor">▊</span>';
            } else {
                contentElement.innerHTML = this._formatMessageHtml(revealedText);
            }
            
            // Keep scrolled to bottom
            const chatContainer = contentElement.closest('#vmo-wizard-conversation, #vmo-chat-messages, .vmo-chat-messages');
            if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
            
        }, msPerChar * 3); // Multiply by chunk size
    }
    
    /**
     * ✅ NEW: Stop text animation (called on interrupt or completion)
     */
    stopTextAnimation() {
        if (this._activeTextAnimation) {
            clearInterval(this._activeTextAnimation);
            this._activeTextAnimation = null;
        }
        
        // If interrupted mid-animation, show what was revealed so far
        if (this._activeMessageElement && this._fullMessageText && this._textRevealIndex > 0) {
            const partialText = this._fullMessageText.substring(0, this._textRevealIndex);
            if (this._textRevealIndex < this._fullMessageText.length) {
                // Show partial with ellipsis to indicate interruption
                this._activeMessageElement.innerHTML = this._formatMessageHtml(partialText + '...');
            }
        }
        
        // ✅ LIVE TRANSCRIPT: Clear on stop
        this.clearLiveTranscript();
        
        this._activeMessageElement = null;
        this._fullMessageText = '';
        this._textRevealIndex = 0;
    }
    
    /**
     * ✅ NEW: Complete text animation immediately (show full message)
     */
    completeTextAnimation() {
        if (this._activeTextAnimation) {
            clearInterval(this._activeTextAnimation);
            this._activeTextAnimation = null;
        }
        
        // Show complete message
        if (this._activeMessageElement && this._fullMessageText) {
            this._activeMessageElement.innerHTML = this._formatMessageHtml(this._fullMessageText);
        }
        
        // ✅ LIVE TRANSCRIPT: Clear on completion
        this.clearLiveTranscript();
        
        this._activeMessageElement = null;
        this._fullMessageText = '';
        this._textRevealIndex = 0;
    }

    addNodeMessageToUI(sender, node) {
        const chatContainer = document.querySelector('#vmo-wizard-conversation') || document.querySelector('#vmo-chat-messages, .vmo-chat-messages, [data-vmo-chat-messages]');
        if (!chatContainer) {
            console.warn('Chat container not found');
            return null;
        }
        const messageDiv = document.createElement('div');
        messageDiv.className = sender === 'user' ? 'vmo-user-message' : 'vmo-ai-message';

        const header = document.createElement('div');
        header.className = 'vmo-message-header';

        const role = document.createElement('span');
        role.className = 'vmo-message-role';
        role.textContent = this.getMessageLabel(sender);

        const timestampMeta = this.formatMessageTimestamp();
        const timeEl = document.createElement('time');
        timeEl.className = 'vmo-message-timestamp';
        timeEl.setAttribute('datetime', timestampMeta.iso);
        timeEl.textContent = timestampMeta.display;

        header.appendChild(role);
        header.appendChild(timeEl);
        messageDiv.appendChild(header);

        const content = document.createElement('div');
        content.className = 'vmo-message-content';
        if (node) {
            // ✅ FIX: Handle both DOM nodes and HTML strings
            if (typeof node === 'string') {
                content.innerHTML = node;
            } else if (node instanceof Node) {
                content.appendChild(node);
            } else {
                console.warn('addNodeMessageToUI: Invalid node type', typeof node);
            }
        }
        messageDiv.appendChild(content);
        chatContainer.appendChild(messageDiv);
        chatContainer.scrollTop = chatContainer.scrollHeight;
        return content;
    }

    getMessageLabel(sender) {
        if (sender === 'user') {
            return 'You';
        }
        if (sender === 'system') {
            return 'System';
        }
        return 'Jerreme';
    }

    formatMessageTimestamp(date = new Date()) {
        try {
            const iso = date.toISOString();
            const display = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
            return { iso, display };
        } catch (err) {
            const fallback = new Date();
            return {
                iso: fallback.toISOString(),
                display: fallback.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
            };
        }
    }

    _formatMessageHtml(raw) {
        try {
            // ═══════════════════════════════════════════════════════════
            // ✅ DOCUMENT VIEWER: Detect long structured content and
            // wrap it in a professional MS Word-style document container
            // instead of showing it inline as a chat bubble.
            // ═══════════════════════════════════════════════════════════
            const docResult = this._detectAndExtractDocument(raw);
            if (docResult) {
                // Build: conversational intro + document viewer
                let html = '';
                if (docResult.intro) {
                    html += this._formatPlainMessageHtml(docResult.intro);
                }
                html += this._buildDocViewerHtml(docResult.docContent, docResult.title);
                if (docResult.outro) {
                    html += this._formatPlainMessageHtml(docResult.outro);
                }
                return html;
            }

            return this._formatPlainMessageHtml(raw);
        } catch (_) {
            // Fallback to simple escape
            return (String(raw || '')
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;'));
        }
    }

    /**
     * Format a plain (non-document) message as HTML paragraphs.
     */
    _formatPlainMessageHtml(raw) {
        let esc = raw
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
        esc = esc
            .replace(/\*\*([\s\S]+?)\*\*/g, '<strong>$1</strong>')
            .replace(/__([\s\S]+?)__/g, '<em>$1</em>');
        const norm = esc.replace(/\r\n/g, '\n');
        const parts = norm.split(/\n\n+/).map(p => p.replace(/\n/g, '<br>'));
        return '<p>' + parts.join('</p><p>') + '</p>';
    }

    /**
     * ✅ DOCUMENT DETECTION: Identifies when AI output is a long structured
     * document (business plan, report, strategy, analysis, etc.) that should
     * be rendered in a professional document viewer instead of inline chat.
     *
     * Returns { intro, docContent, outro, title } or null if not a document.
     */
    _detectAndExtractDocument(raw) {
        if (!raw || typeof raw !== 'string') return null;
        const text = raw.trim();

        // ── Check 1: Code-block-wrapped document (```...```) ──
        // AI sometimes wraps documents in triple backticks
        const codeBlockMatch = text.match(/^([\s\S]*?)```[a-z]*\n([\s\S]{400,})```([\s\S]*)$/m);
        if (codeBlockMatch) {
            const intro = codeBlockMatch[1].trim();
            const docBody = codeBlockMatch[2].trim();
            const outro = codeBlockMatch[3].trim();
            const title = this._extractDocTitle(docBody);
            return { intro, docContent: docBody, outro, title };
        }

        // ── Check 2: Long structured content with headings ──
        const lineCount = (text.match(/\n/g) || []).length + 1;
        const headingCount = (text.match(/^#{1,4}\s+.+/gm) || []).length;
        const boldHeadingCount = (text.match(/^\*\*[^*]+\*\*\s*$/gm) || []).length;
        const totalHeadings = headingCount + boldHeadingCount;
        const bulletCount = (text.match(/^[\s]*[-•*]\s+/gm) || []).length;
        const numberedCount = (text.match(/^[\s]*\d+[\.\)]\s+/gm) || []).length;

        // Document heuristic: long text (15+ lines) with multiple headings/structure
        const isStructuredDoc = (
            lineCount >= 15 &&
            totalHeadings >= 3 &&
            text.length >= 800
        ) || (
            lineCount >= 25 &&
            (bulletCount + numberedCount) >= 8 &&
            text.length >= 1200
        ) || (
            lineCount >= 40 &&
            text.length >= 2000
        );

        if (!isStructuredDoc) return null;

        // Try to split intro (conversational lead-in) from document body
        // Look for the first heading as the doc start
        const firstHeadingMatch = text.match(/^(#{1,4}\s+.+|\*\*[^*]+\*\*\s*)$/m);
        if (firstHeadingMatch && firstHeadingMatch.index > 0) {
            const intro = text.substring(0, firstHeadingMatch.index).trim();
            const docContent = text.substring(firstHeadingMatch.index).trim();
            // Only split if intro is a short conversational line (not part of the doc)
            if (intro.length > 0 && intro.length < 500 && docContent.length > 600) {
                const title = this._extractDocTitle(docContent);
                return { intro, docContent, outro: '', title };
            }
        }

        // Entire message is the document
        const title = this._extractDocTitle(text);
        return { intro: '', docContent: text, outro: '', title };
    }

    /**
     * Extract a title from the first heading of document content.
     */
    _extractDocTitle(text) {
        // Try markdown heading
        const h = text.match(/^#{1,3}\s+(.+)/m);
        if (h) return h[1].replace(/\*\*/g, '').trim().substring(0, 80);
        // Try bold heading on first line
        const b = text.match(/^\*\*(.+?)\*\*/m);
        if (b) return b[1].trim().substring(0, 80);
        return 'Document';
    }

    /**
     * ✅ BUILD DOCUMENT VIEWER: Creates the professional Word-style document
     * container HTML with a header bar, copy button, and rich body formatting.
     */
    _buildDocViewerHtml(markdownContent, title = 'Document') {
        // Convert markdown to rich HTML for the document body
        let html = markdownContent;

        // Escape HTML entities first
        html = html
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');

        // Headings (# to ####)
        html = html.replace(/^####\s+(.+)$/gm, '<h4>$1</h4>');
        html = html.replace(/^###\s+(.+)$/gm, '<h3>$1</h3>');
        html = html.replace(/^##\s+(.+)$/gm, '<h2>$1</h2>');
        html = html.replace(/^#\s+(.+)$/gm, '<h1>$1</h1>');

        // Numbered bold headings: 1. **Title** → <h2>1. Title</h2>
        html = html.replace(/^\s*(\d+)\.\s*\*\*([^*]+)\*\*\s*$/gm, '<h2>$1. $2</h2>');

        // Bold headings on their own line: **Title** → <h3>
        html = html.replace(/^\*\*([^*]+)\*\*\s*$/gm, '<h3>$1</h3>');

        // Bold and italic inline
        html = html.replace(/\*\*([\s\S]+?)\*\*/g, '<strong>$1</strong>');
        html = html.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');

        // Horizontal rules
        html = html.replace(/^[-_*]{3,}\s*$/gm, '<hr>');
        html = html.replace(/^[═—–]{3,}\s*$/gm, '<hr>');

        // Bullet lists: Convert consecutive lines starting with - or * or •
        html = html.replace(/((?:^[\t ]*[-•*]\s+.+(?:\n|$))+)/gm, (match) => {
            const items = match.trim().split('\n').map(line => {
                const text = line.replace(/^[\t ]*[-•*]\s+/, '');
                return `<li>${text}</li>`;
            }).join('');
            return `<ul>${items}</ul>`;
        });

        // Numbered lists: Convert consecutive lines starting with 1. 2. etc.
        html = html.replace(/((?:^[\t ]*\d+[\.\)]\s+.+(?:\n|$))+)/gm, (match) => {
            const items = match.trim().split('\n').map(line => {
                const text = line.replace(/^[\t ]*\d+[\.\)]\s+/, '');
                return `<li>${text}</li>`;
            }).join('');
            return `<ol>${items}</ol>`;
        });

        // Normalize newlines and create paragraphs
        html = html.replace(/\r\n/g, '\n');
        // Don't wrap headings/lists/hrs in paragraphs
        const blocks = html.split(/\n\n+/);
        html = blocks.map(block => {
            const trimmed = block.trim();
            if (!trimmed) return '';
            if (/^<(h[1-4]|ul|ol|hr|blockquote|table|div)/i.test(trimmed)) return trimmed;
            // Single line breaks → <br> inside paragraphs
            return '<p>' + trimmed.replace(/\n/g, '<br>') + '</p>';
        }).join('\n');

        // Unique ID for copy functionality
        const viewerId = 'vmo-doc-' + Date.now();

        const scopedStyles = `<style>
#${viewerId}.vmo-doc-viewer {
    background: #ffffff !important;
    color: #0f172a !important;
    border: 1px solid #cfd8e3 !important;
}
#${viewerId} .vmo-doc-viewer-header {
    background: linear-gradient(to bottom, #f8f9fa, #eef0f2) !important;
    border-bottom: 1px solid #d0d0d0 !important;
}
#${viewerId} .vmo-doc-viewer-title,
#${viewerId} .vmo-doc-viewer-title span {
    color: #475569 !important;
}
#${viewerId} .vmo-doc-viewer-body,
#${viewerId} .vmo-doc-viewer-body * {
    color: #0f172a !important;
}
#${viewerId} .vmo-doc-viewer-body {
    background: #ffffff !important;
    line-height: 1.75 !important;
    font-size: 15px !important;
}
#${viewerId} .vmo-doc-viewer-body h1,
#${viewerId} .vmo-doc-viewer-body h2,
#${viewerId} .vmo-doc-viewer-body h3,
#${viewerId} .vmo-doc-viewer-body h4 {
    font-weight: 700 !important;
    text-decoration: underline !important;
    text-decoration-thickness: 1.5px !important;
    text-underline-offset: 3px !important;
    margin-top: 18px !important;
    margin-bottom: 10px !important;
}
#${viewerId} .vmo-doc-viewer-body h1 { font-size: 23px !important; }
#${viewerId} .vmo-doc-viewer-body h2 { font-size: 19px !important; }
#${viewerId} .vmo-doc-viewer-body h3 { font-size: 17px !important; }
#${viewerId} .vmo-doc-viewer-body h4 { font-size: 15px !important; }
#${viewerId} .vmo-doc-viewer-body p {
    margin: 0 0 12px 0 !important;
    line-height: 1.7 !important;
}
#${viewerId} .vmo-doc-viewer-body ul,
#${viewerId} .vmo-doc-viewer-body ol {
    margin: 8px 0 14px 0 !important;
    padding-left: 28px !important;
}
#${viewerId} .vmo-doc-viewer-body li {
    margin: 5px 0 !important;
    line-height: 1.65 !important;
}
#${viewerId} .vmo-doc-viewer-body li::marker {
    color: #334155 !important;
    font-weight: 700 !important;
}
#${viewerId} .vmo-doc-viewer-body strong {
    font-weight: 700 !important;
    color: #0b1220 !important;
}
#${viewerId} .vmo-doc-viewer-body hr {
    border: none !important;
    border-top: 1px solid #cbd5e1 !important;
    margin: 16px 0 !important;
}
</style>`;

        return `${scopedStyles}<div class="vmo-doc-viewer" id="${viewerId}" style="background:#ffffff;color:#0f172a;border:1px solid #cfd8e3;">
            <div class="vmo-doc-viewer-header" style="background:linear-gradient(to bottom,#f8f9fa,#eef0f2);border-bottom:1px solid #d0d0d0;">
                <div class="vmo-doc-viewer-title" style="color:#475569;">
                    <span class="vmo-doc-icon">📄</span>
                    <span>${this._escapeHtml(title)}</span>
                </div>
                <div class="vmo-doc-viewer-actions">
                    <button style="background:#fff;color:#475569;border:1px solid #cbd5e1;" onclick="(function(){var b=document.getElementById('${viewerId}');var t=b.querySelector('.vmo-doc-viewer-body');var r=document.createRange();r.selectNodeContents(t);var s=window.getSelection();s.removeAllRanges();s.addRange(r);document.execCommand('copy');s.removeAllRanges();var btn=event.target;btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy'},1500)})()" title="Copy document text">Copy</button>
                </div>
            </div>
            <div class="vmo-doc-viewer-body" style="background:#ffffff;color:#0f172a;">${html}</div>
        </div>`;
    }

    /**
     * ✅ Check if a text response is a long document that should NOT be read
     * aloud by TTS. Returns true for documents (TTS should announce, not read).
     */
    _isDocumentForTTS(text) {
        return this._detectAndExtractDocument(text) !== null;
    }
    
    async saveMessageToDatabase(sender, message, attachments = []) {
        try {
            const AX = this.getAjax();
            await fetch(AX.url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams({
                    action: 'vmo_save_consultation_message',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    session_id: this.sessionId,
                    message_data: JSON.stringify({
                        type: sender === 'user' ? 'user' : 'ai',
                        content: message,
                        media_attachments: Array.isArray(attachments) ? attachments : []
                    })
                })
            });
        } catch (error) {
            console.error('Failed to save message:', error);
        }
    }
    
    stopSpeaking() {
        if (this.isGreetingPlaying) {
            // ✅ FIX: Immediately abort TTS when user types
            try { this._progressiveTTSAbort(); } catch(_) {}

            console.log('🔒 Greeting audio is playing—skip manual stop to keep onboarding uninterrupted.');
            return;
        }
        console.log('🛑 Stopping AI speech IMMEDIATELY (barge-in or manual)...');

        // ✅ CRITICAL FIX: Kill standard streaming TTS loop
        try { this._progressiveTTSAbort(); } catch(_) {}


        // If we interrupted mid-turn, approximate what was heard and mark the last assistant
        // message as interrupted so the next response won't assume the full content was delivered.
        try {
            const wasActivelySpeaking = !!(this.currentAudio || this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking));
            if (wasActivelySpeaking && this._lastSpokenText) {
                const prefix = this._approxSpokenPrefixFromAudio(this._lastSpokenText);
                if (prefix && prefix.length) {
                    this._markLastAssistantInterrupted(prefix);
                }
            }
        } catch (_) {}

        this.isSpeaking = false;

        // Cancel any pending mic restart/watchdog timers
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}
        
        // 🎯 BARGE-IN FIX: Stop currentAudio immediately if it has a vmoStop hook
        if (this.currentAudio) {
            try {
                if (this.currentAudio.vmoStop) {
                    this.currentAudio.vmoStop('barge-in');
                } else {
                    this.currentAudio.pause();
                    this.currentAudio.currentTime = 0;
                    this.currentAudio.removeAttribute('src');
                    this.currentAudio.load();
                }
                this.currentAudio = null;
            } catch (_) {}
        }
        
        // CRITICAL: Stop audio coordinator immediately
        if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.stopSpeaking) {
            window.VMOAudioCoordinator.stopSpeaking();
        }
        
        // 🎯 BARGE-IN FIX: Use stopAllTTS if available for immediate cutoff
        if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.stopAllTTS) {
            try { window.VMOAudioCoordinator.stopAllTTS('barge-in', 'consultation'); } catch (_) {}
        }
        
        // Stop legacy voice system if exists
        if (window.vmoVoiceSystem && window.vmoVoiceSystem.stopSpeaking) {
            window.vmoVoiceSystem.stopSpeaking();
        }
        
        // CRITICAL: Cancel ALL browser speech synthesis immediately
        if (this.synthesis) {
            this.synthesis.cancel(); // Stops mid-sentence
        }
        if (window.speechSynthesis) {
            window.speechSynthesis.cancel(); // Global stop
        }
        
        // CRITICAL: Stop and remove ALL audio elements (TTS playback)
        const audioElements = document.querySelectorAll('audio');
        audioElements.forEach(audio => {
            try {
                if (audio.vmoStop) {
                    audio.vmoStop('barge-in');
                } else {
                    audio.pause();
                    audio.currentTime = 0;
                    audio.src = '';
                    audio.load();
                }
            } catch (_) {}
        });

        // Ensure coordinator unblocks mic immediately
        try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop && window.VMOAudioCoordinator.onTTSStop(); } catch (_) {}
        
        console.log('✅ All speech stopped - barge-in complete, user can now speak');

        // If voice is enabled, ensure we are actually listening again after an interrupt.
        if (this.isVoiceEnabled && this.recognition) {
            try {
                setTimeout(() => {
                    if (!this.isVoiceEnabled || !this.recognition) return;
                    if (this.isProcessing) return;
                    if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) return;

                    try {
                        if (window.VMOAudioCoordinator) {
                            const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                            if (!ok) return;
                        }
                    } catch (_) {}

                    try {
                        this.setVoiceStatus('listening');
                        this.showModalPhase('listening');
                    } catch (_) {}

                    try { this.safeStartRecognition('stop_resume'); } catch (_) {}
                }, 120);
            } catch (_) {}
        }
    }

    normalizeForBargeInCompare(text) {
        return String(text || '')
            .toLowerCase()
            .replace(/[\u2019']/g, "'")
            .replace(/[^a-z0-9\s']/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
    }

    /**
     * Estimate how much of the TTS text has been spoken based on:
     * 1. audio.currentTime / audio.duration (most accurate, if available)
     * 2. Elapsed time * words-per-second (fallback)
     * 
     * This is critical for long AI messages where we need to compare
     * only the ALREADY SPOKEN portion against user's speech input,
     * preventing false matches against future content.
     */
    getEstimatedSpokenText() {
        const fullText = this._lastSpokenText || '';
        if (!fullText) return '';
        
        const words = fullText.split(/\s+/).filter(Boolean);
        if (words.length === 0) return '';
        
        let spokenWordCount = words.length; // Default: assume all spoken
        
        // Method 1: Use audio element timing (most accurate)
        if (this.currentAudio) {
            try {
                const audio = this.currentAudio;
                const duration = audio.duration;
                const currentTime = audio.currentTime;
                
                if (Number.isFinite(duration) && duration > 0 && Number.isFinite(currentTime) && currentTime >= 0) {
                    const progressRatio = Math.max(0, Math.min(1, currentTime / duration));
                    // Add small buffer (10%) to account for partial words being recognized
                    const adjustedRatio = Math.min(1, progressRatio + 0.10);
                    spokenWordCount = Math.ceil(words.length * adjustedRatio);
                }
            } catch (_) {}
        }
        
        // Method 2: Fallback to elapsed time estimation
        if (spokenWordCount === words.length && this._ttsStartTime > 0) {
            try {
                const elapsedSeconds = (Date.now() - this._ttsStartTime) / 1000;
                const estimatedWords = Math.ceil(elapsedSeconds * this._ttsWordsPerSecond);
                // Add buffer of ~2 words for recognition latency
                spokenWordCount = Math.min(words.length, estimatedWords + 2);
            } catch (_) {}
        }
        
        // Return only the portion that's been spoken
        const spokenWords = words.slice(0, Math.max(1, spokenWordCount));
        return spokenWords.join(' ');
    }

    shouldTriggerBargeIn(transcript) {
        const t = this.normalizeForBargeInCompare(transcript);
        if (!t || t.length < 2) return false;

        // If we very recently triggered barge-in, ignore repeat chatter.
        const now = Date.now();
        if (this._bargeInTriggeredAt && (now - this._bargeInTriggeredAt) < 500) {
            return false;
        }

        // 🎯 ENHANCED: Use progressive spoken text estimation for long messages
        // Only compare against what the AI has ALREADY said, not future content
        const estimatedSpoken = this.getEstimatedSpokenText();
        const spoken = this.normalizeForBargeInCompare(estimatedSpoken);

        // If recognition is basically hearing the AI's own TTS, do not barge-in.
        // Use a word overlap heuristic (more stable for partial transcripts).
        const spokenWords = new Set((spoken || '').split(/\s+/).filter(Boolean));
        const candWords = (t || '').split(/\s+/).filter(Boolean);
        const overlapCount = candWords.reduce((n, w) => n + (spokenWords.has(w) ? 1 : 0), 0);
        const overlapRatio = candWords.length ? (overlapCount / candWords.length) : 1;
        
        // Debug logging for long messages (helps troubleshoot)
        if (this._lastSpokenText && this._lastSpokenText.length > 500) {
            console.log('🔊 Barge-in check (long message):', {
                userTranscript: t.substring(0, 50) + '...',
                estimatedSpokenWords: spokenWords.size,
                totalWords: (this._lastSpokenText || '').split(/\s+/).length,
                overlapRatio: overlapRatio.toFixed(2)
            });
        }
        
        if (spoken && overlapRatio >= 0.8) {
            this._bargeInCandidate = '';
            this._bargeInCandidateCount = 0;
            return false;
        }

        // Fast-path: explicit interruption words/phrases (still require low overlap).
        if (/\b(stop|wait|pause|hold on|hold up|hang on|one second|excuse me|actually|no|hey|um|listen|sorry|let me)\b/i.test(t) && overlapRatio < 0.55) {
            return true;
        }

        // Require stability across consecutive interim updates to avoid random noise.
        if (t === this._bargeInCandidate) {
            this._bargeInCandidateCount += 1;
        } else {
            this._bargeInCandidate = t;
            this._bargeInCandidateCount = 1;
        }

        // Trigger immediately for a clear longer phrase, or after 2 stable shorter hits.
        const clearSinglePhrase = this._bargeInCandidateCount >= 1 && t.length >= 8 && overlapRatio < 0.45;
        const stableShortPhrase = this._bargeInCandidateCount >= 2 && t.length >= 4 && overlapRatio < 0.65;
        return clearSinglePhrase || stableShortPhrase;
    }

    async parseAjaxJsonResponse(response, context = 'request') {
        const responseText = await response.text();
        const raw = String(responseText || '');
        const trimmed = raw.trim();

        const attempts = [];
        if (trimmed) {
            attempts.push(trimmed);
        }

        const firstJsonChar = trimmed.search(/[\[{]/);
        const lastObjectChar = trimmed.lastIndexOf('}');
        const lastArrayChar = trimmed.lastIndexOf(']');
        const lastJsonChar = Math.max(lastObjectChar, lastArrayChar);
        if (firstJsonChar !== -1 && lastJsonChar > firstJsonChar) {
            const sliced = trimmed.slice(firstJsonChar, lastJsonChar + 1);
            if (sliced !== trimmed) {
                attempts.push(sliced);
            }
        }

        for (const candidate of attempts) {
            try {
                return JSON.parse(candidate);
            } catch (_) {}
        }

        if (trimmed.startsWith('<') || trimmed.includes('<html') || trimmed.includes('<!DOCTYPE')) {
            console.error(`❌ ${context} returned HTML instead of JSON:`, trimmed.substring(0, 500));
            throw new Error(`Server error during ${context}. Please try again.`);
        }

        console.error(`❌ Failed to parse JSON for ${context}:`, trimmed.substring(0, 300));
        throw new Error(`Invalid server response during ${context}. Please try again.`);
    }

    _approxSpokenPrefixFromAudio(text) {
        // Use the progressive estimation first
        const estimated = this.getEstimatedSpokenText();
        if (estimated && estimated.length > 0) {
            return estimated;
        }
        
        // Fallback to original logic
        const full = String(text || '');
        try {
            const audio = this.currentAudio;
            const d = audio && Number.isFinite(audio.duration) ? audio.duration : null;
            const t = audio && Number.isFinite(audio.currentTime) ? audio.currentTime : null;
            if (d && t !== null && d > 0 && t >= 0) {
                const ratio = Math.max(0, Math.min(1, t / d));
                const chars = Math.max(0, Math.min(full.length, Math.floor(full.length * ratio)));
                return full.slice(0, Math.max(chars, Math.min(140, full.length))).trim();
            }
        } catch (_) {}
        return full.slice(0, Math.min(240, full.length)).trim();
    }

    _markLastAssistantInterrupted(prefixText) {
        try {
            const hist = Array.isArray(this.conversationHistory) ? this.conversationHistory : [];
            for (let i = hist.length - 1; i >= 0; i--) {
                if (hist[i] && hist[i].role === 'assistant' && typeof hist[i].content === 'string') {
                    hist[i].content = (String(prefixText || hist[i].content).trim() + ' …(interrupted)').trim();
                    hist[i].interrupted = true;
                    break;
                }
            }
        } catch (_) {}
    }
    
    speak(text) {
        // ✅ PREVENT MULTIPLE TTS: Kill ALL existing audio before starting new speech
        this._ttsGeneration = (this._ttsGeneration || 0) + 1;
        const myGen = this._ttsGeneration;
        try { this._progressiveTTSAbort(); } catch(_) {}
        try { this.stopSpeaking(); } catch(_) {}
        try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_) {}

        // Cancel any pending mic restart/watchdog from prior turns
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}

        // Prevent overlapping audio between consecutive AI turns
        try {
            if (this.currentAudio) {
                try { this.currentAudio.pause(); } catch(_) {}
                try { this.currentAudio.currentTime = 0; } catch(_) {}
                try { this.currentAudio.removeAttribute && this.currentAudio.removeAttribute('src'); } catch(_) {}
                try { this.currentAudio.load && this.currentAudio.load(); } catch(_) {}
                this.currentAudio = null;
            }
        } catch(_) {}

        this._lastSpokenText = String(text || '');
        this._ttsStartTime = Date.now(); // Track when TTS started for progressive overlap detection
        this._ttsStartedAt = Date.now(); // ✅ SELF-LOOP PREVENTION: Track TTS start
        this._ttsEndedAt = 0; // Reset end time

        // Lock coordinator state, but allow this module to keep mic for barge-in monitoring.
        try {
            window.VMOAudioCoordinator
                && window.VMOAudioCoordinator.onTTSStart
                && window.VMOAudioCoordinator.onTTSStart('consultation', {
                    moduleId: 'consultation',
                    allowBargeInMonitor: true
                });
        } catch (_) {}

        // IMPORTANT: We do NOT keep SpeechRecognition running while the AI is speaking.
        // That's the #1 cause of the "AI hears itself" feedback loop.
        // Instead, we keep a lightweight mic VAD (voice activity detector) running.
        // If the user starts talking, we stop TTS immediately (true barge-in), then start recognition.
        if (this.isVoiceEnabled) {
            try { this.safeStopRecognition && this.safeStopRecognition('ai-speaking'); } catch (_) {}
            try { this.startVADBargeIn && this.startVADBargeIn(); } catch (_) {}
        }
        
        this.isSpeaking = true;
        this.setVoiceStatus('speaking');
        this.showModalPhase('speaking');
        
        // ✅ LIVE TRANSCRIPT: Show AI text while speaking (first ~100 chars)
        const displayText = text.length > 100 ? text.substring(0, 100) + '...' : text;
        this.updateLiveTranscript(displayText, 'ai');
        this._currentSpeakingText = text; // Store for potential progressive reveal
        
        // Callback when AI finishes speaking
        const onSpeechEnd = () => {
            this.isSpeaking = false;
            this._ttsStartTime = 0; // Reset TTS start time to prevent stale calculations
            this._ttsEndedAt = Date.now(); // ✅ SELF-LOOP PREVENTION: Mark TTS end for cooldown
            console.log('✅ AI finished speaking');
            
            // ✅ LIVE TRANSCRIPT: Clear when AI finishes speaking
            this.clearLiveTranscript();
            this._currentSpeakingText = null;

            // Stop voice-activity barge-in monitor (if running)
            try { this.stopVADBargeIn && this.stopVADBargeIn(); } catch (_) {}

            // Unlock coordinator state immediately
            try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop && window.VMOAudioCoordinator.onTTSStop(); } catch (_) {}

            // Clear watchdog
            try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}
            
            // 🔇 CRITICAL FIX: Only restart microphone if voice is actually enabled
            // This prevents auto-listening when user hasn't enabled voice mode
            if (this.isVoiceEnabled && this.recognition) {
                console.log('🎤 Restarting microphone - user has voice enabled');
                try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
                // ✅ SELF-LOOP PREVENTION: Use cooldown delay before restarting mic
                const cooldownDelay = this._ttsEndCooldownMs || 1800;
                this._restartMicTimer = setTimeout(() => {
                    this._restartMicTimer = null;
                    if (!this.isVoiceEnabled || !this.recognition) return;
                    if (this.isProcessing || this.isSpeaking) return;
                    // ✅ Double-check cooldown period hasn't been violated
                    if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < cooldownDelay)) {
                        console.log('🔇 Mic restart delayed - still in TTS cooldown');
                        return;
                    }
                    try {
                        if (window.VMOAudioCoordinator) {
                            try {
                                if (window.VMOAudioCoordinator.isSpeaking && window.VMOAudioCoordinator.stopAllTTS) {
                                    window.VMOAudioCoordinator.stopAllTTS('barge-in', 'consultation');
                                }
                            } catch (_) {}
                            const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                            if (!ok) {
                                console.log('🎤 Mic not granted by coordinator');
                                return;
                            }
                        }
                        this.setVoiceStatus('listening');
                        this.showModalPhase('listening');
                        if (this.safeStartRecognition) {
                            this.safeStartRecognition('after_tts');
                        } else {
                            this.recognition.start();
                        }
                    } catch (e) {
                        // If recognition is already started, don't tear down the session.
                        console.warn('Could not restart microphone:', e.message);
                        try {
                            if (this.isVoiceEnabled && !this.isProcessing && !this.isSpeaking) {
                                setTimeout(() => {
                                    try {
                                        if (this.safeStartRecognition) this.safeStartRecognition('after_tts_retry');
                                        else this.recognition.start();
                                    } catch (_) {}
                                }, 700);
                            }
                        } catch (_) {}
                    }
                }, cooldownDelay);
            } else {
                this.setVoiceStatus('ready');
                this.hideModal();
            }
        };

        // Watchdog: if TTS never fires end/pause/error, force-unblock.
        try {
            const maxMs = 90000;
            this._ttsWatchdogTimer = setTimeout(() => {
                this._ttsWatchdogTimer = null;
                if (this.isSpeaking) {
                    console.warn('⚠️ TTS watchdog fired; forcing onSpeechEnd');
                    try { onSpeechEnd(); } catch (_) {}
                }
            }, maxMs);
        } catch (_) {}
        
        // Prefer OpenAI TTS via REST proxy if available
        if (typeof window.vmoSpeak === 'function') {
            console.log('🎵 Using OpenAI TTS (vmoSpeak)');
            try {
                // Prefer the Jerreme persona voice profile unless overridden
                const bizVoice = (window.vmoBusinessConsultantVoice || 'ash');
                window.vmoSpeak(text, bizVoice, 'mp3', 1.0).then(audio => {
                    // ✅ GENERATION GUARD: If a newer speak() was called while this
                    // promise was pending, discard this stale audio to prevent overlap.
                    if (myGen !== this._ttsGeneration) {
                        console.log('🔇 Discarding stale TTS audio (generation ' + myGen + ' vs ' + this._ttsGeneration + ')');
                        try { if (audio && audio.pause) { audio.pause(); audio.src = ''; } } catch(_) {}
                        return;
                    }
                    if (audio && typeof audio.addEventListener === 'function') {
                        this.currentAudio = audio;
                        try {
                            const muted = !!window.vmoMuteConsultation;
                            audio.muted = muted;
                            audio.volume = muted ? 0 : 1;
                        } catch(_) {}
                        audio.addEventListener('ended', onSpeechEnd, { once: true });
                        audio.addEventListener('error', onSpeechEnd, { once: true });
                    } else {
                        onSpeechEnd();
                    }
                }).catch(err => {
                    if (myGen !== this._ttsGeneration) return; // Stale — skip fallback too
                    console.error('TTS error, falling back to browser synthesis:', err);
                    this.browserSpeak(text, onSpeechEnd);
                });
            } catch (e) {
                console.error('TTS invocation failed, fallback:', e);
                this.browserSpeak(text, onSpeechEnd);
            }
            return;
        }

        // Fallback: Browser speech synthesis
        this.browserSpeak(text, onSpeechEnd);
    }
    
    /**
     * ✅ NEW: Speak with synchronized text reveal
     * Text animates in sync with audio playback, both stop on interrupt
     */
    speakWithTextSync(text, contentElement) {
        // ✅ PREVENT MULTIPLE TTS: Kill ALL existing audio before starting new speech
        this._ttsGeneration = (this._ttsGeneration || 0) + 1;
        const myGen = this._ttsGeneration;
        try { this._progressiveTTSAbort(); } catch(_) {}
        try { this.stopSpeaking(); } catch(_) {}
        try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_) {}

        // Cancel any pending mic restart/watchdog from prior turns
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}

        // Prevent overlapping audio between consecutive AI turns
        try {
            if (this.currentAudio) {
                try { this.currentAudio.pause(); } catch(_) {}
                try { this.currentAudio.currentTime = 0; } catch(_) {}
                try { this.currentAudio.removeAttribute && this.currentAudio.removeAttribute('src'); } catch(_) {}
                try { this.currentAudio.load && this.currentAudio.load(); } catch(_) {}
                this.currentAudio = null;
            }
        } catch(_) {}

        this._lastSpokenText = String(text || '');
        this._ttsStartTime = Date.now();

        // Lock coordinator state
        try {
            window.VMOAudioCoordinator
                && window.VMOAudioCoordinator.onTTSStart
                && window.VMOAudioCoordinator.onTTSStart('consultation', {
                    moduleId: 'consultation',
                    allowBargeInMonitor: true
                });
        } catch (_) {}

        // Stop recognition, start VAD for barge-in
        if (this.isVoiceEnabled) {
            try { this.safeStopRecognition && this.safeStopRecognition('ai-speaking'); } catch (_) {}
            try { this.startVADBargeIn && this.startVADBargeIn(); } catch (_) {}
        }
        
        this.isSpeaking = true;
        this.setVoiceStatus('speaking');
        this.showModalPhase('speaking');
        
        // ✅ LIVE TRANSCRIPT: Show AI text while speaking (first ~100 chars)
        const displayText = text.length > 100 ? text.substring(0, 100) + '...' : text;
        this.updateLiveTranscript(displayText, 'ai');
        this._currentSpeakingText = text;
        
        // Callback when AI finishes speaking
        const onSpeechEnd = () => {
            this.isSpeaking = false;
            this._ttsStartTime = 0;
            console.log('✅ AI finished speaking (synced)');

            // ✅ Complete text animation - show full message
            try { this.completeTextAnimation(); } catch (_) {}
            
            // ✅ LIVE TRANSCRIPT: Clear when AI finishes speaking
            this.clearLiveTranscript();
            this._currentSpeakingText = null;

            // Stop VAD monitor
            try { this.stopVADBargeIn && this.stopVADBargeIn(); } catch (_) {}

            // Unlock coordinator
            try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.onTTSStop && window.VMOAudioCoordinator.onTTSStop(); } catch (_) {}

            // Clear watchdog
            try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}
            
            // Restart microphone
            if (this.isVoiceEnabled && this.recognition) {
                console.log('🎤 Restarting microphone - user has voice enabled');
                try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
                this._restartMicTimer = setTimeout(() => {
                    this._restartMicTimer = null;
                    if (!this.isVoiceEnabled || !this.recognition) return;
                    if (this.isProcessing || this.isSpeaking) return;
                    try {
                        if (window.VMOAudioCoordinator) {
                            try {
                                if (window.VMOAudioCoordinator.isSpeaking && window.VMOAudioCoordinator.stopAllTTS) {
                                    window.VMOAudioCoordinator.stopAllTTS('barge-in', 'consultation');
                                }
                            } catch (_) {}
                            const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                            if (!ok) {
                                console.log('🎤 Mic not granted by coordinator');
                                return;
                            }
                        }
                        this.setVoiceStatus('listening');
                        this.showModalPhase('listening');
                        if (this.safeStartRecognition) {
                            this.safeStartRecognition('after_tts');
                        } else {
                            this.recognition.start();
                        }
                    } catch (e) {
                        console.warn('Could not restart microphone:', e.message);
                    }
                }, this._ttsEndCooldownMs || 1800); // ✅ Use full cooldown before restarting mic after TTS
            } else {
                this.setVoiceStatus('ready');
                this.hideModal();
            }
        };

        // Watchdog
        try {
            const maxMs = 90000;
            this._ttsWatchdogTimer = setTimeout(() => {
                this._ttsWatchdogTimer = null;
                if (this.isSpeaking) {
                    console.warn('⚠️ TTS watchdog fired; forcing onSpeechEnd');
                    try { onSpeechEnd(); } catch (_) {}
                }
            }, maxMs);
        } catch (_) {}
        
        // ✅ Use OpenAI TTS with synchronized text reveal
        if (typeof window.vmoSpeak === 'function') {
            console.log('🎵 Using OpenAI TTS with synchronized text reveal');
            try {
                const bizVoice = (window.vmoBusinessConsultantVoice || 'ash');
                window.vmoSpeak(text, bizVoice, 'mp3', 1.0).then(audio => {
                    // ✅ GENERATION GUARD: If a newer speak/speakWithTextSync was called
                    // while this promise was pending, discard stale audio to prevent overlap.
                    if (myGen !== this._ttsGeneration) {
                        console.log('🔇 Discarding stale synced TTS audio (generation ' + myGen + ' vs ' + this._ttsGeneration + ')');
                        try { if (audio && audio.pause) { audio.pause(); audio.src = ''; } } catch(_) {}
                        if (contentElement) { contentElement.innerHTML = this._formatMessageHtml(text); }
                        return;
                    }
                    if (audio && typeof audio.addEventListener === 'function') {
                        this.currentAudio = audio;
                        try {
                            const muted = !!window.vmoMuteConsultation;
                            audio.muted = muted;
                            audio.volume = muted ? 0 : 1;
                        } catch(_) {}
                        
                        // ✅ FIX: Track if text reveal has started to prevent duplicates
                        let textRevealStarted = false;
                        
                        const startTextRevealOnce = () => {
                            if (textRevealStarted || this._activeTextAnimation) return;
                            textRevealStarted = true;
                            if (contentElement) {
                                const duration = audio.duration || null;
                                this.startSynchronizedTextReveal(contentElement, text, duration);
                            }
                        };
                        
                        // ✅ Start text reveal when audio can play
                        audio.addEventListener('canplaythrough', startTextRevealOnce, { once: true });
                        
                        // Start text reveal immediately if audio is already ready
                        if (audio.readyState >= 3) {
                            startTextRevealOnce();
                        } else {
                            // If audio not ready yet, start with estimated timing after brief delay
                            setTimeout(startTextRevealOnce, 100);
                        }
                        
                        audio.addEventListener('ended', onSpeechEnd, { once: true });
                        audio.addEventListener('error', onSpeechEnd, { once: true });
                    } else {
                        // No audio element - show text immediately
                        if (contentElement) {
                            contentElement.innerHTML = this._formatMessageHtml(text);
                        }
                        onSpeechEnd();
                    }
                }).catch(err => {
                    if (myGen !== this._ttsGeneration) return; // Stale — skip fallback too
                    console.error('TTS error, showing text immediately:', err);
                    if (contentElement) {
                        contentElement.innerHTML = this._formatMessageHtml(text);
                    }
                    this.browserSpeak(text, onSpeechEnd);
                });
            } catch (e) {
                console.error('TTS invocation failed:', e);
                if (contentElement) {
                    contentElement.innerHTML = this._formatMessageHtml(text);
                }
                this.browserSpeak(text, onSpeechEnd);
            }
            return;
        }

        // Fallback: Show text immediately and use browser speech
        if (contentElement) {
            contentElement.innerHTML = this._formatMessageHtml(text);
        }
        this.browserSpeak(text, onSpeechEnd);
    }

    browserSpeak(text, onSpeechEnd) {
        console.log('⚠️ Using browser speech synthesis');
        if (!this.synthesis) {
            console.error('❌ No speech synthesis available');
            onSpeechEnd();
            return;
        }
        const utterance = new SpeechSynthesisUtterance(text);
        const voices = this.synthesis.getVoices();
        const langPref = (document && document.documentElement && document.documentElement.lang)
            ? document.documentElement.lang
            : (navigator.language || 'en-US');
        utterance.lang = langPref;
        const preferredVoice = voices.find(v => v.lang === langPref)
            || voices.find(v => v.lang && v.lang.toLowerCase().startsWith((langPref || 'en').toLowerCase().split('-')[0] + '-'))
            || voices.find(voice => 
                voice.name.includes('Samantha') || 
                voice.name.includes('Google US English') ||
                voice.name.includes('Microsoft Zira') ||
                (voice.lang && voice.lang.startsWith('en-'))
            );
        if (preferredVoice) {
            utterance.voice = preferredVoice;
        }
        utterance.rate = 1.0;
        utterance.pitch = 1.0;
        utterance.volume = 1.0;
        utterance.onend = onSpeechEnd;
        utterance.onerror = (event) => {
            console.error('Speech synthesis error:', event);
            onSpeechEnd();
        };
        this.synthesis.speak(utterance);
    }

    // ═══════════════════════════════════════════════════════════════
    // PROGRESSIVE STREAMING TTS
    // Speaks AI responses as they stream in, sentence by sentence
    // so the user hears the voice almost immediately — not after
    // the entire response is generated.
    // ═══════════════════════════════════════════════════════════════

    _progressiveTTSInit() {
        // ✅ PREVENT MULTIPLE TTS: Cancel any existing TTS before starting progressive streaming
        this._progressiveTTSAbort();
        // Also stop any non-progressive speech/audio
        try {
            if (this.currentAudio) {
                try { this.currentAudio.pause(); } catch(_) {}
                try { this.currentAudio.removeAttribute('src'); } catch(_) {}
                try { this.currentAudio.load(); } catch(_) {}
                this.currentAudio = null;
            }
        } catch(_) {}
        try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_) {}
        try { this.stopTextAnimation(); } catch(_) {}

        this._ptts = {
            buffer: '',           // Accumulates text until sentence boundary
            queue: [],            // Array of { text, audioPromise, index }
            playing: false,       // Is currently playing a chunk
            aborted: false,       // Barge-in cancelled
            currentAudio: null,   // Currently playing audio element
            totalChunks: 0,
            playedChunks: 0,
            streamingDone: false, // Whether the SSE stream has ended
            started: false,       // Whether TTS session setup is done
        };

        // Cancel timers
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}

        // Stop recognition during AI response
        if (this.isVoiceEnabled) {
            try { this.safeStopRecognition && this.safeStopRecognition('ai-streaming'); } catch (_) {}
        }

        console.log('🔊 Progressive TTS initialized');
    }

    _progressiveTTSAppend(token) {
        if (!this._ptts || this._ptts.aborted) return;
        this._ptts.buffer += token;

        // Try to extract a speakable chunk
        const chunk = this._extractSpeakableChunk();
        if (chunk) {
            this._queueTTSChunk(chunk);
        }
    }

    _extractSpeakableChunk() {
        const buf = this._ptts.buffer;

        // ═══════════════════════════════════════════════════════════════
        // ✅ FAST FIRST-CHUNK: For the very first TTS chunk, allow much
        // shorter text (≥20 chars at a sentence boundary) so the AI
        // starts speaking almost immediately while the rest streams in.
        // Subsequent chunks use larger thresholds for natural pacing.
        // ═══════════════════════════════════════════════════════════════
        const isFirstChunk = this._ptts.totalChunks === 0;
        const minBufferLen = isFirstChunk ? 20 : 60;       // was 80 for all
        const minSentencePos = isFirstChunk ? 10 : 40;     // was 50 for all

        if (buf.length < minBufferLen) return null;

        // Find a sentence boundary (.!?\n) with at least minSentencePos chars
        let cutPoint = -1;
        for (let i = buf.length - 1; i >= minSentencePos; i--) {
            const ch = buf[i];
            if ((ch === '.' || ch === '!' || ch === '?' || ch === '\n') &&
                (i >= buf.length - 1 || buf[i + 1] === ' ' || buf[i + 1] === '\n' || buf[i + 1] === '<')) {
                cutPoint = i + 1;
                break;
            }
        }

        // If buffer is long but no sentence boundary, try comma/colon
        const commaThreshold = isFirstChunk ? 120 : 250;
        const commaMinPos = isFirstChunk ? 40 : 100;
        if (cutPoint === -1 && buf.length > commaThreshold) {
            for (let i = buf.length - 1; i >= commaMinPos; i--) {
                if (',;:\u2014'.includes(buf[i])) {
                    cutPoint = i + 1;
                    break;
                }
            }
        }

        // If buffer is very long, cut at a word boundary
        if (cutPoint === -1 && buf.length > 400) {
            cutPoint = buf.lastIndexOf(' ', 350);
            if (cutPoint === -1) cutPoint = 350;
        }

        if (cutPoint === -1) return null;

        const chunk = buf.substring(0, cutPoint).trim();
        this._ptts.buffer = buf.substring(cutPoint);
        const cleaned = this._stripForTTS(chunk);
        const minCleanLen = isFirstChunk ? 8 : 15;
        return (cleaned && cleaned.length >= minCleanLen) ? cleaned : null;
    }

    _stripForTTS(text) {
        return text
            .replace(/<[^>]*>/g, ' ')                // Strip HTML tags
            .replace(/\*\*([^*]+)\*\*/g, '$1')       // Strip bold markdown
            .replace(/\*([^*]+)\*/g, '$1')            // Strip italic markdown
            .replace(/#+\s*/g, '')                    // Strip heading markers
            .replace(/[\u2501\u2550\u2500\u2503\u2502\u2517\u251B\u250F\u2513\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2022\u25CF\u25C6\u25C9\u25B8\u25B9\u25BA\u25BB\u2192\u2190\u2191\u2193\u2194]+/g, '') // Strip decorative
            .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')  // Strip markdown links
            .replace(/\s+/g, ' ')                     // Collapse whitespace
            .trim();
    }

    async _queueTTSChunk(text) {
        if (!this._ptts || this._ptts.aborted || !text) return;

        const chunkIdx = this._ptts.totalChunks++;
        console.log(`\uD83D\uDD0A TTS chunk ${chunkIdx} queued (${text.length} chars): "${text.substring(0, 60)}..."`);

        const bizVoice = window.vmoBusinessConsultantVoice || 'ash';

        // ───────────────────────────────────────────────────
        // ✅ CONCURRENCY GATE: Only prefetch up to 5 chunks ahead of the current
        // playing position. This prevents firing 80+ simultaneous requests
        // which overwhelms nginx with 429 Too Many Requests.
        // ───────────────────────────────────────────────────
        const MAX_PREFETCH = 5;
        const audioPromise = (async () => {
            // Wait until we're within the prefetch window
            while (this._ptts && !this._ptts.aborted) {
                const played = this._ptts.playedChunks || 0;
                if (chunkIdx < played + MAX_PREFETCH) break; // OK to fetch
                await new Promise(r => setTimeout(r, 300)); // Wait and re-check
            }
            if (!this._ptts || this._ptts.aborted) throw new Error('TTS aborted');

            // Multi-strategy TTS fetch: admin-ajax POST → REST POST
            const strategies = [];

            // Strategy 1: admin-ajax POST (most reliable — cookie auth, no REST routing needed)
            const ajaxUrl = window.vmo_ajax?.ajaxurl || window.vmoAjax?.ajaxurl || '/wp-admin/admin-ajax.php';
            strategies.push({
                label: 'admin-ajax POST',
                fetch: () => fetch(ajaxUrl, {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_tts_stream',
                        text: text.trim(),
                        voice: bizVoice,
                        format: 'mp3'
                    }).toString()
                })
            });

            // Strategy 2: REST API POST (original path — needs working permalink routing + nonce)
            const restUrl = (window.wpApiSettings?.root || '/wp-json/') + 'vmo/v1/tts/speak';
            const nonce = window.wpApiSettings?.nonce || '';
            strategies.push({
                label: 'REST POST',
                fetch: () => fetch(restUrl, {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
                    body: JSON.stringify({ text: text.trim(), voice: bizVoice, format: 'mp3', speed: 1.0 })
                })
            });

            let lastError = null;
            for (const strategy of strategies) {
                try {
                    const resp = await strategy.fetch();
                    const ct = (resp.headers.get('Content-Type') || '').toLowerCase();

                    if (!resp.ok || !ct.startsWith('audio/')) {
                        // ✅ FIX: On 429, wait before trying next strategy (rate-limit backoff)
                        if (resp.status === 429) {
                            console.warn(`TTS chunk ${chunkIdx} [${strategy.label}] rate-limited (429). Waiting 2s...`);
                            await new Promise(r => setTimeout(r, 2000));
                        }
                        // Log the actual error response for diagnosis
                        let errBody = '';
                        try { errBody = await resp.clone().text(); } catch (_) {}
                        const snippet = errBody.substring(0, 200);
                        console.warn(`TTS chunk ${chunkIdx} [${strategy.label}] failed: status=${resp.status}, type=${ct}, body=${snippet}`);
                        lastError = new Error(`TTS ${strategy.label}: ${resp.status} ${ct}`);
                        continue; // try next strategy
                    }

                    const blob = await resp.blob();
                    if (blob.size < 100) {
                        console.warn(`TTS chunk ${chunkIdx} [${strategy.label}] returned suspiciously small audio (${blob.size} bytes)`);
                        lastError = new Error(`TTS ${strategy.label}: audio too small (${blob.size}B)`);
                        continue;
                    }

                    console.log(`\uD83D\uDD0A TTS chunk ${chunkIdx} fetched via ${strategy.label} (${blob.size} bytes)`);
                    const url = URL.createObjectURL(blob);
                    const audio = new Audio(url);
                    audio.preload = 'auto';
                    return { audio, url };
                } catch (e) {
                    if (e?.name === 'AbortError') throw e; // Don't retry on barge-in abort
                    console.warn(`TTS chunk ${chunkIdx} [${strategy.label}] error:`, e.message);
                    lastError = e;
                }
            }

            throw lastError || new Error('All TTS strategies failed');
        })();

        this._ptts.queue.push({ text, audioPromise, index: chunkIdx });

        // Start the playback loop if not running
        if (!this._ptts.playing) {
            this._progressiveTTSPlayLoop();
        }
    }

    _progressiveTTSFlush() {
        if (!this._ptts || this._ptts.aborted) return;

        const remaining = this._stripForTTS(this._ptts.buffer || '');
        this._ptts.buffer = '';

        if (remaining && remaining.length >= 10) {
            console.log(`\uD83D\uDD0A TTS flush: ${remaining.length} chars remaining`);
            this._queueTTSChunk(remaining);
        }

        this._ptts.streamingDone = true;

        // If nothing queued or playing, finish now
        if (!this._ptts.playing && this._ptts.queue.length === 0) {
            this._progressiveTTSFinish();
        }
    }

    async _progressiveTTSPlayLoop() {
        if (!this._ptts || this._ptts.aborted) return;
        this._ptts.playing = true;

        // Set up speaking state on first chunk
        if (!this._ptts.started) {
            this._ptts.started = true;
            this.isSpeaking = true;
            this._ttsStartTime = Date.now();
            this.setVoiceStatus('speaking');
            this.showModalPhase('speaking');

            try {
                window.VMOAudioCoordinator?.onTTSStart?.('progressive', {
                    moduleId: 'consultation', allowBargeInMonitor: true
                });
            } catch (_) {}

            if (this.isVoiceEnabled) {
                try { this.startVADBargeIn?.(); } catch (_) {}
            }

            // Watchdog (2 minutes max for progressive TTS)
            this._ttsWatchdogTimer = setTimeout(() => {
                this._ttsWatchdogTimer = null;
                if (this.isSpeaking) {
                    console.warn('\u26A0\uFE0F Progressive TTS watchdog fired');
                    this._progressiveTTSFinish();
                }
            }, 120000);
        }

        while (this._ptts && this._ptts.queue.length > 0 && !this._ptts.aborted) {
            const item = this._ptts.queue.shift();
            try {
                const { audio, url } = await item.audioPromise;
                if (!this._ptts || this._ptts.aborted) {
                    URL.revokeObjectURL(url);
                    break;
                }

                this._ptts.currentAudio = audio;
                this.currentAudio = audio;

                // Apply mute setting
                try {
                    const muted = !!window.vmoMuteConsultation;
                    audio.muted = muted;
                    audio.volume = muted ? 0 : 1;
                } catch (_) {}

                console.log(`\uD83D\uDD0A Playing TTS chunk ${item.index}`);

                await new Promise((resolve) => {
                    audio.addEventListener('ended', resolve, { once: true });
                    audio.addEventListener('error', (e) => {
                        console.warn(`TTS chunk ${item.index} error:`, e);
                        resolve();
                    }, { once: true });
                    audio.play().catch((e) => {
                        console.warn(`TTS chunk ${item.index} play failed:`, e);
                        resolve();
                    });
                });

                URL.revokeObjectURL(url);
                this._ptts.playedChunks++;

            } catch (e) {
                console.warn(`TTS chunk ${item.index} failed:`, e);
            }
        }

        if (this._ptts) {
            this._ptts.playing = false;
            this._ptts.currentAudio = null;
        }

        // If streaming is done and no more chunks, finish
        if (this._ptts && this._ptts.streamingDone && this._ptts.queue.length === 0) {
            this._progressiveTTSFinish();
        }
    }

    _progressiveTTSFinish() {
        if (!this._ptts) return;
        const played = this._ptts.playedChunks;
        const total = this._ptts.totalChunks;
        console.log(`\u2705 Progressive TTS complete (${played}/${total} chunks)`);

        this.isSpeaking = false;
        this._ttsStartTime = 0;
        this._ttsEndedAt = Date.now(); // ✅ SELF-LOOP PREVENTION: Mark TTS end for cooldown

        try { this.clearLiveTranscript(); } catch (_) {}
        try { this.stopVADBargeIn?.(); } catch (_) {}
        try { window.VMOAudioCoordinator?.onTTSStop?.(); } catch (_) {}
        try {
            if (this._ttsWatchdogTimer) {
                clearTimeout(this._ttsWatchdogTimer);
                this._ttsWatchdogTimer = null;
            }
        } catch (_) {}

        this._ptts = null;

        // Restart microphone after AI finishes speaking
        // ✅ FIX: Also clear _micMutedByUser if the user hasn't explicitly muted
        // This prevents the mic from staying permanently muted after barge-in cycles
        if (this.isVoiceEnabled && !window.vmoMuteConsultation) {
            this._micMutedByUser = false;
        }
        if (this.isVoiceEnabled && this.recognition) {
            this._restartMicTimer = setTimeout(() => {
                this._restartMicTimer = null;
                if (!this.isVoiceEnabled || !this.recognition) return;
                if (this.isProcessing || this.isSpeaking) return;
                // ✅ FIX: Skip restart only if user explicitly muted via the UI button
                if (window.vmoMuteConsultation) {
                    console.log('🔇 Mic restart skipped: user has muted consultation');
                    return;
                }
                try {
                    if (window.VMOAudioCoordinator) {
                        const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                        if (!ok) return;
                    }
                    this.setVoiceStatus('listening');
                    this.showModalPhase('listening');
                    if (this.safeStartRecognition) {
                        this.safeStartRecognition('after_progressive_tts');
                    } else {
                        this.recognition.start();
                    }
                } catch (e) {
                    console.warn('Could not restart mic after progressive TTS:', e.message);
                }
            }, 1800); // ✅ Increased from 800ms — gives TTS audio time to fully dissipate before mic restarts
        } else {
            this.setVoiceStatus('ready');
            this.hideModal();
        }
    }

    _progressiveTTSAbort() {
        if (!this._ptts) return;
        console.log('\uD83D\uDD07 Progressive TTS aborted (barge-in)');
        this._ptts.aborted = true;
        this._ptts.queue = [];

        if (this._ptts.currentAudio) {
            try { this._ptts.currentAudio.pause(); } catch (_) {}
            try { this._ptts.currentAudio.currentTime = 0; } catch (_) {}
            this._ptts.currentAudio = null;
        }

        this.isSpeaking = false;
        this._ttsStartTime = 0;
        this._ttsEndedAt = Date.now(); // ✅ SELF-LOOP PREVENTION: Mark abort time for cooldown
        try { this.stopVADBargeIn?.(); } catch (_) {}
        try { window.VMOAudioCoordinator?.onTTSStop?.(); } catch (_) {}
        try {
            if (this._ttsWatchdogTimer) {
                clearTimeout(this._ttsWatchdogTimer);
                this._ttsWatchdogTimer = null;
            }
        } catch (_) {}
        this._ptts = null;
    }

    // Compact status pill similar to Jarvis states
    setVoiceStatus(state) {
        if (!this.statusEl || !this.statusDot || !this.statusText) return;
        const map = {
            listening: { text: 'Listening…', color: '#e53935' },
            thinking:  { text: 'Processing…', color: '#ffb300' },
            speaking:  { text: 'Speaking…', color: '#42a5f5' },
            ready:     { text: 'Ready', color: '#2e7d32' }
        };
        const picked = map[state] || map.ready;
        this.statusText.textContent = picked.text;
        this.statusDot.style.background = picked.color;
        
        // Update Voice Button — keep as red STOP while voice is enabled
        const voiceBtn = this.voiceButton || document.querySelector('#vmo-wizard-voice');
        if (voiceBtn) {
            if (this.isVoiceEnabled) {
                // ✅ Voice is active: always show red stop button regardless of sub-state
                voiceBtn.innerHTML = '<span aria-hidden="true" style="font-size:22px;line-height:1;color:#fff">⏹</span>';
                voiceBtn.title = "Stop Voice Conversation";
                voiceBtn.style.backgroundColor = '#e53935';
                voiceBtn.style.color = '#fff';
                voiceBtn.classList.add('vmo-voice-stop');
            } else {
                // Voice is off: show mic icon
                voiceBtn.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1">🎤</span>';
                voiceBtn.title = "Start Voice Conversation";
                voiceBtn.style.backgroundColor = '';
                voiceBtn.style.color = '';
                voiceBtn.classList.remove('vmo-voice-stop', 'vmo-speaking-stop');
            }
        }
    }
    
    toggleVoice() {
        const voiceButton = document.querySelector('#vmo-wizard-voice');

        if (this.isGreetingPlaying) {
            // ✅ FIX: Immediately abort TTS when user types
            try { this._progressiveTTSAbort(); } catch(_) {}

            this.addMessageToUI('system', 'Voice options unlock as soon as this personal greeting finishes. Hang tight for just a few seconds.');
            return;
        }
        
        if (this.isVoiceEnabled) {
            // ✅ SINGLE BUTTON: Stop the ENTIRE voice conversation (both speaking and listening)
            console.log('🛑 User clicked stop — stopping ALL voice activity');
            this.hardStopVoice('toggle_off');
            this.hideModal();

            // ✅ Restore mic button appearance
            if (voiceButton) {
                voiceButton.classList.remove('active', 'recording', 'vmo-voice-stop');
                voiceButton.style.backgroundColor = '';
                voiceButton.style.color = '';
                voiceButton.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1">🎤</span>';
                voiceButton.title = "Start Voice Conversation";
            }
            
            const muteBtn = document.querySelector('#vmo-wizard-mute');
            if (muteBtn) {
                muteBtn.style.display = 'none';
            }

            console.log('✅ Voice conversation stopped — mic button restored');
        } else {
            // ENABLE VOICE
            console.log('🎤 User enabled voice - starting voice conversation');
            this.isVoiceEnabled = true;
            
            const muteBtn = document.querySelector('#vmo-wizard-mute');
            if (muteBtn) {
                // Ensure it displays properly with SVG/Span
                muteBtn.style.display = 'inline-flex';
                this._micMutedByUser = false; 
                muteBtn.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1">🎙️</span>';
                muteBtn.title = "Mute Microphone";
            }
            
            if (this.recognition) {
                if (window.VMOAudioCoordinator) {
                    // True barge-in: if TTS is speaking, stop it and start listening now.
                    try {
                        if (window.VMOAudioCoordinator.isSpeaking && window.VMOAudioCoordinator.stopAllTTS) {
                            window.VMOAudioCoordinator.stopAllTTS('barge-in', 'consultation');
                        }
                    } catch(_) {}
                    const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                    if (!ok) {
                        console.log('🎤 Mic not granted by coordinator');
                        return;
                    }
                }
                // Warm up mic to improve reliability
                this.micWarmup && this.micWarmup();
                this.recognition.start();
                this.showModalPhase('listening');
            }
            
            // ✅ Update button to RED STOP button
            if (voiceButton) {
                voiceButton.classList.add('active', 'vmo-voice-stop');
                voiceButton.style.backgroundColor = '#e53935';
                voiceButton.style.color = '#fff';
                voiceButton.innerHTML = '<span aria-hidden="true" style="font-size:22px;line-height:1;color:#fff">⏹</span>';
                voiceButton.title = "Stop Voice Conversation";
            }
            
            console.log('🎤 Voice enabled — button is now red stop');
        }
    }

    interruptSpeakingAndResume(reason = 'barge-in') {
        // Cancel any pending mic restart/watchdog timers
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}

        // ✅ FIX: Stop text animation on interrupt (unified text+audio interrupt)
        try { this.stopTextAnimation(); } catch (_) {}

        // ✅ Abort progressive TTS if active (sentence-by-sentence streaming)
        try { this._progressiveTTSAbort(); } catch (_) {}

        // Stop any active TTS (streaming or buffered) immediately
        try {
            if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.stopAllTTS) {
                window.VMOAudioCoordinator.stopAllTTS(reason, 'consultation');
            }
        } catch (_) {}

        // Stop browser synthesis + any audio elements used by consultation
        try { this.stopSpeaking(); } catch (_) {}

        // Update local state
        this.isSpeaking = false;
        this.isProcessing = false;

        // Resume listening if voice remains enabled
        if (!this.isVoiceEnabled || !this.recognition) {
            return;
        }

        try {
            if (window.VMOAudioCoordinator) {
                const ok = window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true });
                if (!ok) {
                    console.log('🎤 Mic not granted by coordinator');
                    return;
                }
            }
        } catch (_) {}

        try {
            this.setVoiceStatus('listening');
            this.showModalPhase('listening');
        } catch (_) {}

        try { this.safeStartRecognition(reason + '_resume'); } catch (_) {}
    }

    // Hard-stop voice immediately and ensure no further voice-triggered responses occur.
    hardStopVoice(reason = 'end') {
        this.isVoiceEnabled = false;
        this._voiceEndedAt = Date.now();

        // Cancel any pending mic restart/watchdog timers
        try { if (this._restartMicTimer) { clearTimeout(this._restartMicTimer); this._restartMicTimer = null; } } catch (_) {}
        try { if (this._ttsWatchdogTimer) { clearTimeout(this._ttsWatchdogTimer); this._ttsWatchdogTimer = null; } } catch (_) {}

        // Prevent any background voice system from auto-restarting mic
        try { window.dispatchEvent(new CustomEvent('vmo:voice-hard-stop', { detail: { from: 'consultation', reason } })); } catch (_) {}

        // Stop listening immediately
        if (this.recognition) {
            try { this.recognition.stop(); } catch (_) {}
            try { this.recognition.abort(); } catch (_) {}
        }

        // Stop speaking immediately (including progressive TTS)
        try { this._progressiveTTSAbort(); } catch (_) {}
        try { this.stopSpeaking(); } catch (_) {}

        // Release mic ownership
        try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic && window.VMOAudioCoordinator.releaseMic('consultation'); } catch (_) {}

        // ✅ Restore voice button to mic icon
        const voiceBtn = this.voiceButton || document.querySelector('#vmo-wizard-voice');
        if (voiceBtn) {
            voiceBtn.classList.remove('active', 'recording', 'vmo-voice-stop', 'vmo-speaking-stop');
            voiceBtn.style.backgroundColor = '';
            voiceBtn.style.color = '';
            voiceBtn.innerHTML = '<span aria-hidden="true" style="font-size:18px;line-height:1">🎤</span>';
            voiceBtn.title = "Start Voice Conversation";
        }

        // Hide modal UI
        try { this.hideModal(); } catch (_) {}
    }

    micWarmup() {
        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) return;
        navigator.mediaDevices.getUserMedia({ audio: { echoCancellation:true, noiseSuppression:true, autoGainControl:true } })
            .then(stream => setTimeout(() => stream.getTracks().forEach(t => t.stop()), 100))
            .catch(() => {});
    }
    
    generateSessionId() {
        return 'vmo_consultation_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }

    resolveGreetingText() {
        const localized = (window?.vmoGreetingBootstrap?.text) || window?.vmoGreetingText;
        const source = (localized && typeof localized === 'string' && localized.trim().length > 0)
            ? localized.trim()
            : this.defaultGreetingText.trim();
        if (!this.userDisplayName || this.userDisplayName.trim().length === 0) {
            this.userDisplayName = this.resolveUserDisplayName();
        }
        return this.personalizeGreetingText(source);
    }

    personalizeGreetingText(text) {
        const template = (typeof text === 'string' && text.trim().length > 0)
            ? text
            : this.defaultGreetingText;
        const displayName = (() => {
            if (this.userDisplayName && this.userDisplayName.trim().length > 0) {
                return this.userDisplayName.trim();
            }
            const fallback = this.resolveUserDisplayName();
            return fallback && fallback.trim().length > 0 ? fallback.trim() : 'there';
        })();
        const placeholderPattern = /\{\{\s*user_name\s*\}\}|\[user_name\]|\[USER_NAME\]|\[username\]|\[UserName\]/gi;
        return template.replace(placeholderPattern, displayName);
    }

    resolveUserDisplayName() {
        const candidates = [
            window?.vmoGreetingBootstrap?.userName,
            window?.vmoAjax?.user_display_name,
            window?.vmo_ajax?.user_display_name,
            window?.currentUser?.displayName
        ];
        for (const candidate of candidates) {
            if (typeof candidate === 'string') {
                const trimmed = candidate.trim();
                if (trimmed.length > 0) {
                    return trimmed;
                }
            }
        }
        return 'there';
    }

    resolveGreetingAudioUrl() {
        const candidate = (window?.vmoGreetingBootstrap?.audioUrl) || window?.vmoGreetingAudioUrl;
        if (!candidate || typeof candidate !== 'string') {
            return null;
        }
        if (candidate.includes('undefined') || candidate.trim() === '') {
            return null;
        }
        return candidate;
    }

    preloadGreetingAudio() {
        if (this.greetingPreloadPromise) {
            return this.greetingPreloadPromise;
        }
        const url = this.resolveGreetingAudioUrl();
        if (!url) {
            this.greetingPreloadPromise = Promise.resolve(null);
            return this.greetingPreloadPromise;
        }
        this.greetingPreloadPromise = new Promise(resolve => {
            try {
                const audio = new Audio();
                audio.preload = 'auto';
                audio.autoplay = false;
                audio.dataset.vmoGreeting = 'true';
                audio.src = url;
                let settled = false;
                const complete = (result) => {
                    if (settled) return;
                    settled = true;
                    if (result) {
                        this.preloadedGreetingAudio = audio;
                        this.pendingGreetingAudio = audio;
                    }
                    resolve(result ? audio : null);
                };
                audio.addEventListener('canplaythrough', () => complete(audio), { once: true });
                audio.addEventListener('loadeddata', () => complete(audio), { once: true });
                audio.addEventListener('error', () => complete(null), { once: true });
                audio.load();
            } catch (err) {
                console.warn('Greeting audio preload failed.', err);
                resolve(null);
            }
        });
        return this.greetingPreloadPromise;
    }

    async getPrimedGreetingAudioElement() {
        if (this.preloadedGreetingAudio) {
            try { this.preloadedGreetingAudio.currentTime = 0; } catch (_) {}
            return this.preloadedGreetingAudio;
        }
        try {
            return await this.preloadGreetingAudio();
        } catch (err) {
            console.warn('Greeting audio preload promise rejected.', err);
            return null;
        }
    }

    disableGreetingInteractions(lock) {
        const interactiveEls = [
            this.chatInput,
            this.sendButton,
            this.voiceButton,
            this.transcribeButton,
            this.uploadButton,
            this.videoButton
        ];
        interactiveEls.forEach(el => {
            if (!el) return;
            if (lock) {
                el.dataset.vmoGreetingLock = el.disabled ? 'locked' : 'free';
                el.disabled = true;
            } else {
                if (el.dataset.vmoGreetingLock === 'free') {
                    el.disabled = false;
                }
                delete el.dataset.vmoGreetingLock;
            }
        });
    }

    async playInitialGreetingSequence() {
        if (!this.initialGreetingText || this.greetingHasPlayed) {
            return;
        }
        this.greetingHasPlayed = true;
        this.isGreetingPlaying = true;
        this.disableGreetingInteractions(true);
        this.showModalPhase('speaking');
        this.setVoiceStatus('speaking');

        // ✅ REAL-TIME TRANSCRIPTION: Add greeting to chat NOW with progressive reveal
        const greetingText = this.initialGreetingText.trim();
        const contentEl = this.addMessageToUI('ai', greetingText, { progressive: true });

        try {
            await this.preloadGreetingAudio().catch(() => null);
            const audioElement = await this.requestGreetingAudioElement(greetingText);
            if (audioElement) {
                // ✅ Start synchronized text reveal when audio starts playing
                const startReveal = () => {
                    const duration = audioElement.duration || null;
                    if (contentEl) {
                        this.startSynchronizedTextReveal(contentEl, greetingText, duration);
                    }
                };
                if (audioElement.readyState >= 3) {
                    startReveal();
                } else {
                    audioElement.addEventListener('canplaythrough', startReveal, { once: true });
                    // Fallback: start reveal after short delay if canplaythrough doesn't fire
                    setTimeout(startReveal, 300);
                }
                await this.waitForAudioPlayback(audioElement);
            } else {
                // Browser TTS fallback — start text reveal with estimated timing
                if (contentEl) {
                    this.startSynchronizedTextReveal(contentEl, greetingText, null);
                }
                await this.browserSpeakOnce(greetingText);
            }
        } catch (err) {
            console.warn('Greeting playback failed, falling back to text only.', err);
        } finally {
            // ✅ Ensure full text is displayed when greeting finishes
            try { this.completeTextAnimation(); } catch (_) {}
            if (contentEl) {
                contentEl.innerHTML = this._formatMessageHtml(greetingText);
            }
            this._greetingAddedToUI = true; // Flag so startConsultation doesn't duplicate
            this.isGreetingPlaying = false;
            this.disableGreetingInteractions(false);
            this.hideModal();
            this.setVoiceStatus('ready');
            this.pendingGreetingAudio = null;
        }
    }

    async requestGreetingAudioElement(text) {
        // ✅ FIX: For returning users, skip pre-packaged audio and use TTS directly
        // This ensures the "Welcome back" message is spoken correctly
        if (this._isReturningUser) {
            console.log('👋 Returning user - using TTS for personalized greeting');
            const preferredVoice = window.vmoBusinessConsultantVoice || 'ash';
            if (typeof window.vmoSpeak === 'function') {
                try {
                    const audio = await window.vmoSpeak(text, preferredVoice, 'mp3', 1.02);
                    if (audio) {
                        audio.dataset.vmoGreeting = 'true';
                        this.pendingGreetingAudio = audio;
                        return audio;
                    }
                } catch (err) {
                    console.warn('OpenAI TTS returning user greeting failed.', err);
                }
            }
            // Fallback to browser TTS for returning users
            return null;
        }
        
        const primedAudio = await this.getPrimedGreetingAudioElement();
        if (primedAudio) {
            primedAudio.dataset.vmoGreeting = 'true';
            this.pendingGreetingAudio = primedAudio;
            return primedAudio;
        }

        const resolvedUrl = this.resolveGreetingAudioUrl();
        if (resolvedUrl) {
            try {
                const audio = new Audio(resolvedUrl);
                audio.preload = 'auto';
                audio.autoplay = false;
                audio.dataset.vmoGreeting = 'true';
                this.pendingGreetingAudio = audio;
                return audio;
            } catch (err) {
                console.warn('Resolved greeting audio failed to initialize.', err);
            }
        }

        const preferredVoice = window.vmoBusinessConsultantVoice || 'ash';
        if (typeof window.vmoSpeak === 'function') {
            try {
                const audio = await window.vmoSpeak(text, preferredVoice, 'mp3', 1.02);
                if (audio) {
                    audio.dataset.vmoGreeting = 'true';
                    this.pendingGreetingAudio = audio;
                    return audio;
                }
            } catch (err) {
                console.warn('OpenAI TTS greeting failed, trying packaged asset.', err);
            }
        }

        const packagedFallback = `${window?.vmoAssetsBase || ''}assets/audio/vmo-consultation-greeting.mp3`;
        if (packagedFallback && packagedFallback.includes('undefined') === false && packagedFallback.trim() !== '') {
            try {
                const audio = new Audio(packagedFallback);
                audio.preload = 'auto';
                audio.autoplay = false;
                audio.dataset.vmoGreeting = 'true';
                this.pendingGreetingAudio = audio;
                return audio;
            } catch (err) {
                console.warn('Static greeting audio fallback failed to initialize.', err);
            }
        }

        return null;
    }

    async waitForAudioPlayback(audio) {
        return new Promise(resolve => {
            const cleanup = () => {
                audio.removeEventListener('ended', onDone);
                audio.removeEventListener('error', onDone);
                audio.removeEventListener('pause', onDone);
            };
            const onDone = () => {
                cleanup();
                resolve();
            };
            audio.addEventListener('ended', onDone, { once: true });
            audio.addEventListener('error', onDone, { once: true });
            audio.addEventListener('pause', onDone, { once: true });
            try {
                const playResult = audio.play();
                if (playResult && typeof playResult.catch === 'function') {
                    playResult.catch(err => {
                        console.warn('Greeting audio play promise rejected:', err);
                        onDone();
                    });
                }
            } catch (err) {
                console.warn('Unable to start greeting audio playback.', err);
                onDone();
            }
        });
    }

    async browserSpeakOnce(text) {
        if (!this.synthesis) {
            return;
        }
        return new Promise(resolve => {
            const utterance = new SpeechSynthesisUtterance(text);
            const voices = this.synthesis.getVoices();
            const langPref = (document?.documentElement?.lang) || navigator.language || 'en-US';
            utterance.lang = langPref;
            const preferredVoice = voices.find(v => v.lang === langPref)
                || voices.find(v => v.lang?.toLowerCase().startsWith(langPref.split('-')[0].toLowerCase() + '-'))
                || voices.find(v => /samantha|google us english|microsoft zira|en-/i.test(v.name || ''));
            if (preferredVoice) {
                utterance.voice = preferredVoice;
            }
            utterance.rate = 1.0;
            utterance.pitch = 1.0;
            utterance.volume = 1.0;
            utterance.onend = resolve;
            utterance.onerror = resolve;
            try {
                this.synthesis.speak(utterance);
            } catch (err) {
                console.warn('Browser speech synthesis failed for greeting.', err);
                resolve();
            }
        });
    }
    
    // Check if consultation is complete and ready to generate website
    isConsultationComplete() {
        const required = ['business_concept', 'target_market', 'offerings'];
        return required.every(topic => this.topicsCovered.has(topic));
    }
    
    // Generate greeting message - AI STARTS THE CONVERSATION
    async startConsultation() {
        // ✅ FIX: Guard to prevent double-trigger of consultation start
        if (this._consultationStarted) {
            console.log('⚠️ startConsultation() already called - skipping to prevent double greeting');
            return;
        }
        this._consultationStarted = true;
        
        console.log('🎬 Starting consultation greeting sequence...');
        
        // ✅ FIX: Check session state BEFORE greeting so we can use returning user greeting
        console.log('🔄 Checking for existing session data before greeting...');
        await this.bootstrapSessionState(true);
        console.log('✅ Session state checked. Is returning user:', this._isReturningUser);
        
        // ✅ Update greeting text if returning user
        if (this._isReturningUser) {
            this.initialGreetingText = this.getReturningUserGreeting();
            console.log('👋 Using returning user greeting');
        }
        
        await this.playInitialGreetingSequence();
        if (this.initialGreetingText) {
            // ✅ Only add to UI if greeting sequence didn't already handle it
            if (!this._greetingAddedToUI) {
                this.addMessageToUI('ai', this.initialGreetingText);
            }
            await this.saveMessageToDatabase('ai', this.initialGreetingText);

            // ✅ Add greeting to conversation history so the AI knows
            // it already asked the first question and can follow up properly
            this.conversationHistory.push({
                role: 'assistant',
                content: this.initialGreetingText,
                timestamp: new Date().toISOString()
            });
        }

        // ✅ After greeting finishes, auto-start the microphone so the user
        // can respond immediately via voice — the AI asked a question and is
        // now waiting for the answer.
        if (this.isVoiceEnabled && this.recognition) {
            console.log('🎤 Greeting complete — starting microphone for user response');
            setTimeout(() => {
                if (!this.isSpeaking && !this.isProcessing) {
                    this.setVoiceStatus('listening');
                    this.showModalPhase('listening');
                    if (this.safeStartRecognition) {
                        this.safeStartRecognition('after_greeting');
                    }
                }
            }, this._ttsEndCooldownMs || 1800);
        }

        console.log('✅ Consultation started');
    }
    
    // ✅ NEW: Generate greeting for returning users
    getReturningUserGreeting() {
        const name = this.userDisplayName || 'there';
        const docCount = this._restoredDocumentCount || 0;
        const phaseIndex = this._restoredPhaseIndex || this.consultation_data?.consultation_flow?.current_index || 0;
        const phases = this.consultation_data?.consultation_flow?.phases || [];
        const currentPhase = phases[phaseIndex] || `Phase ${phaseIndex + 1}`;
        const extractedInfo = this._restoredExtractedInfo || this.consultation_data?.extracted_information || {};
        const businessName = extractedInfo.business_name || null;
        
        // Phase names for better context
        const phaseNames = [
            'Business Foundations',
            'Executive Blueprint', 
            'Deep Research',
            'Recommendations',
            'Website Architecture',
            'Systems & Compliance',
            'Roadmap & Tasks',
            'Launch Readiness'
        ];
        const phaseName = phaseNames[phaseIndex] || `Phase ${phaseIndex + 1}`;
        
        // Show progress context based on phase
        if (phaseIndex >= 2) {
            // Phase 3 or later - significant progress
            return `Welcome back, ${name}! 👋 Great to see you again!

I have all your consultation data${businessName ? ` for **${businessName}**` : ''} from our previous session. You're currently at **Phase ${phaseIndex + 1}: ${phaseName}**${docCount > 0 ? ` with ${docCount} document(s) already processed` : ''}.

Just say "continue" or "let's proceed" and I'll pick up right where we left off!`;
        }
        
        if (phaseIndex === 1) {
            // Phase 2 - Blueprint confirmation
            return `Welcome back, ${name}! 👋 I still have all your business information${businessName ? ` for **${businessName}**` : ''}.

You're at **Phase 2: Executive Blueprint**. Let me show you the summary for confirmation, or just say "continue" to proceed.`;
        }
        
        // Phase 1 with some info collected
        if (businessName || docCount > 0) {
            return `Welcome back, ${name}! 👋 I remember you${businessName ? ` - we were working on **${businessName}**` : ''}.

I still have ${docCount > 0 ? `your ${docCount} document(s) and ` : ''}all the information you shared. We're at **Phase 1: ${phaseName}** - gathering the final details.

Just say "continue" and I'll pick up right where we left off, or share any updates you have!`;
        }
        
        // Default returning user greeting
        return `Welcome back, ${name}! 👋 Great to see you again. I have your previous consultation data, so we can continue right where we left off.

What would you like to work on today?`;
    }
    
    // Open Business Wizard Modal
    async openBusinessWizard() {
        console.log('🎯 Opening Business Wizard Modal (Intelligent Consultation v9.0.1)');
        
        // ✅ Set global flag so vmo-conversational.js knows NOT to rebind events
        window._vmoIntelligentConsultationActive = true;
        
        const modal = document.getElementById('vmo-business-wizard-modal');
        if (!modal) {
            console.error('❌ Business Wizard Modal NOT FOUND!');
            alert('Business Wizard modal not found. Please refresh the page.');
            return false;
        }
        
        console.log('✅ Business Wizard Modal Found - Opening');
        modal.style.display = 'flex';
        modal.removeAttribute('inert'); // Remove inert attribute
        modal.setAttribute('aria-hidden', 'false'); // Add aria-hidden for accessibility

        // Add background blur when opening
        document.body.classList.add('vmo-wizard-active');
        
        // ✅ FIX: Move quote popup into modal so it appears above the overlay
        if (this._quotePopup && this._quotePopup.parentNode !== modal) {
            modal.appendChild(this._quotePopup);
            console.log('💬 Quote popup moved into modal container');
        }
        
    // Initialize chat UI (align with template IDs)
    this.chatMessages = document.getElementById('vmo-wizard-conversation');
    this.chatInput = document.getElementById('vmo-wizard-response');
        
        if (!this.chatMessages) {
            console.error('❌ Chat messages container not found!');
            return false;
        }
        
        // Clear any existing messages
        this.chatMessages.innerHTML = '';
        
        // ✅ CRITICAL: Show legal disclaimer first
        this.showLegalDisclaimer();
        
        // ✅ CRITICAL: Rebind events to wizard modal elements (they exist now!)
        console.log('🔧 Rebinding events to wizard elements...');
        this.bindEvents();
        
        // Show toast
        if (window.showToast) {
            window.showToast('Business Consultation opened. Let\'s build your website!', 'success');
        }
        
        // ✅ FIX: Removed direct startConsultation() call here to prevent double-trigger!
        // The legal disclaimer's "Continue" button now handles starting the consultation.
        // This prevents the greeting from playing twice.
        console.log('🎯 Waiting for user to accept disclaimer before starting consultation...');
        
        return true;
    }

    // Get the detected/enforced domain for this installation
    getEnforcedDomain() {
        const host = this.getProvisioningHostFallback();
        if (!host || host === 'example.com') {
            // Try to get from window.location
            try {
                const loc = window.location.hostname || '';
                const clean = loc.replace(/^www\./, '').toLowerCase();
                if (clean && clean !== 'localhost' && !clean.includes('127.0.0.1')) {
                    return clean;
                }
            } catch (_) {}
            return null;
        }
        return host;
    }

    // Validate that an email matches the enforced domain
    validateEmailDomain(email) {
        const enforced = this.getEnforcedDomain();
        if (!enforced) return { valid: false, error: 'Domain not detected' };
        
        const emailDomain = (email || '').split('@')[1]?.toLowerCase();
        if (!emailDomain) return { valid: false, error: 'Invalid email format' };
        
        // Check for protected/government domains that should be rejected
        const protectedDomains = [
            'gov', 'mil', 'edu', 'fbi.gov', 'cia.gov', 'state.gov', 'whitehouse.gov',
            'senate.gov', 'house.gov', 'treasury.gov', 'doj.gov', 'dhs.gov'
        ];
        const isProtected = protectedDomains.some(pd => emailDomain.endsWith(pd) || emailDomain === pd);
        if (isProtected) {
            return { valid: false, error: 'Protected/government domain - cannot be used' };
        }
        
        if (emailDomain !== enforced && !emailDomain.endsWith('.' + enforced)) {
            return { valid: false, error: `Email must be @${enforced}` };
        }
        
        return { valid: true, domain: enforced };
    }
    
    // Show legal disclaimer with domain confirmation
    showLegalDisclaimer() {
        const detectedDomain = this.getEnforcedDomain() || 'your domain';
        const disclaimer = document.createElement('div');
        disclaimer.className = 'vmo-wizard-message vmo-wizard-system-message';
        disclaimer.id = 'vmo-legal-disclaimer-container';
        disclaimer.innerHTML = `
            <div class="vmo-disclaimer-box" style="background: rgba(255, 193, 7, 0.1); border: 2px solid #ffc107; border-radius: 8px; padding: 16px; margin: 10px 0;">
                <h4 style="color: #ffc107; margin: 0 0 12px 0; font-size: 16px;">
                    ⚠️ Important Legal Disclaimer
                </h4>
                <div style="color: #e6f3ff; font-size: 14px; line-height: 1.6;">
                    <p style="margin: 0 0 10px 0;">
                        <strong>By using this Business Consultation service, you acknowledge and agree that:</strong>
                    </p>
                    <ul style="margin: 10px 0; padding-left: 20px;">
                        <li>All recommendations and advice are provided in good faith for informational purposes only</li>
                        <li>You retain full discretion in implementing any suggestions or recommendations</li>
                        <li>Virtual Media Office (VMO), its owners <strong>Jerreme Josephson LLC</strong>, and its AI consultation system are <strong>NOT</strong> responsible for any business decisions you make</li>
                        <li>Neither VMO nor Jerreme Josephson LLC can be held liable for any injury, loss, damages, or issues of any type arising from your use of this plugin</li>
                        <li>This service does not constitute professional legal, financial, or business advice</li>
                        <li>You should consult appropriate licensed professionals for specific business decisions</li>
                    </ul>

                    <div style="background: rgba(220, 38, 38, 0.1); border: 1px solid rgba(220, 38, 38, 0.4); border-radius: 8px; padding: 12px; margin: 16px 0;">
                        <p style="margin: 0 0 8px 0; color: #f87171; font-weight: 600;">
                            🚫 Prohibited Use
                        </p>
                        <ul style="margin: 0; padding-left: 20px; font-size: 13px;">
                            <li>This plugin must <strong>not</strong> be used for any fraudulent, deceptive, or unlawful purposes</li>
                            <li>You may not use VMO to create websites, businesses, or content intended to scam, mislead, or defraud others</li>
                            <li>Any use that violates applicable local, state, federal, or international laws is strictly prohibited</li>
                            <li>Jerreme Josephson LLC reserves the right to terminate access for any user found engaging in prohibited activities</li>
                        </ul>
                    </div>
                    
                    <div style="background: rgba(59, 130, 246, 0.15); border: 1px solid rgba(59, 130, 246, 0.4); border-radius: 8px; padding: 12px; margin: 16px 0;">
                        <p style="margin: 0 0 8px 0; color: #60a5fa; font-weight: 600;">
                            🌐 Domain Confirmation: <strong>${detectedDomain}</strong>
                        </p>
                        <p style="margin: 0 0 12px 0; font-size: 13px;">
                            By continuing, you confirm that:
                        </p>
                        <ul style="margin: 0 0 12px 0; padding-left: 20px; font-size: 13px;">
                            <li>You have the right to build a website and business on this domain</li>
                            <li>You are authorized to use this domain for emails and communications</li>
                            <li>You understand that you are fully responsible for how this domain is used</li>
                        </ul>
                        <label style="display: flex; align-items: flex-start; gap: 10px; cursor: pointer; padding: 8px; background: rgba(34, 197, 94, 0.1); border-radius: 6px;">
                            <input type="checkbox" id="vmo-domain-consent-checkbox" style="margin-top: 3px; width: 18px; height: 18px; cursor: pointer;">
                            <span style="font-size: 13px; line-height: 1.5;">
                                <strong>I confirm that I have the right to use this domain and accept full responsibility</strong>
                            </span>
                        </label>
                    </div>
                    
                    <p style="margin: 10px 0 0 0; font-style: italic; opacity: 0.9;">
                        By continuing, you agree to use this consultation service at your own risk and hold Virtual Media Office (VMO) and its owners, Jerreme Josephson LLC, harmless from any claims, damages, or liabilities.
                    </p>
                </div>
                <button id="vmo-disclaimer-continue-btn" disabled style="
                    margin-top: 16px;
                    padding: 12px 24px;
                    background: #4a5568;
                    color: #a0aec0;
                    border: none;
                    border-radius: 8px;
                    font-size: 14px;
                    font-weight: 600;
                    cursor: not-allowed;
                    width: 100%;
                    transition: all 0.2s ease;
                ">
                    ✓ I Understand and Agree — Continue
                </button>
            </div>
        `;
        
        if (this.chatMessages) {
            this.chatMessages.appendChild(disclaimer);
            
            // Auto-scroll to show disclaimer
            this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
            
            // Bind checkbox and button events
            const checkbox = document.getElementById('vmo-domain-consent-checkbox');
            const continueBtn = document.getElementById('vmo-disclaimer-continue-btn');
            
            if (checkbox && continueBtn) {
                checkbox.addEventListener('change', () => {
                    if (checkbox.checked) {
                        continueBtn.disabled = false;
                        continueBtn.style.background = '#22c55e';
                        continueBtn.style.color = '#fff';
                        continueBtn.style.cursor = 'pointer';
                        // Store domain consent
                        this.state.domainConsentConfirmed = true;
                        this.state.enforcedDomain = this.getEnforcedDomain();
                    } else {
                        continueBtn.disabled = true;
                        continueBtn.style.background = '#4a5568';
                        continueBtn.style.color = '#a0aec0';
                        continueBtn.style.cursor = 'not-allowed';
                        this.state.domainConsentConfirmed = false;
                    }
                });
                
                continueBtn.addEventListener('click', () => {
                    if (checkbox.checked) {
                        // Hide the disclaimer and start consultation
                        disclaimer.style.display = 'none';
                        this.startConsultation();
                    }
                });
            }
        }
    }
    
    // Setup event listeners for wizard buttons
    setupEventListeners() {
        // ✅ FIX: Guard to prevent double-registration of event listeners
        if (this._setupEventListenersRan) {
            console.log('⚠️ setupEventListeners() already ran - skipping to prevent duplicate listeners');
            return;
        }
        this._setupEventListenersRan = true;
        
        console.log('🔧 Setting up intelligent consultation event listeners...');
        
        // Business Wizard Header Button (red button at top)
        const headerBtn = document.getElementById('vmo-business-wizard-btn');
        if (headerBtn && !headerBtn.dataset.vmoListenerAttached) {
            headerBtn.dataset.vmoListenerAttached = 'true';
            // Capture phase: prefer Intelligent Consultation, but don't dead-end the click.
            // Only block other handlers if we actually launch successfully.
            headerBtn.addEventListener('click', (e) => {
                let launched = false;
                try {
                    const res = (typeof window.launchBusinessWizard === 'function')
                        ? window.launchBusinessWizard()
                        : this.openBusinessWizard();
                    launched = (res !== false);
                } catch (err) {
                    console.error('❌ Failed to launch Intelligent Consultation wizard:', err);
                    launched = false;
                }

                if (launched) {
                    e.preventDefault();
                    e.stopPropagation();
                    if (typeof e.stopImmediatePropagation === 'function') e.stopImmediatePropagation();
                    console.log('🎯 Business Wizard Header Button Clicked!');
                }
            }, true);
            console.log('✅ Header button listener attached');
        } else if (!headerBtn) {
            console.warn('⚠️ Header button #vmo-business-wizard-btn not found');
        }
        
        // Data-action buttons (in placeholders, etc.) - use delegate pattern with single listener
        document.addEventListener('click', (e) => {
            const target = e.target.closest('[data-action="start-business-wizard"]');
            if (target) {
                e.preventDefault();
                e.stopPropagation();
                console.log('🎯 Data-action start-business-wizard clicked!');
                this.openBusinessWizard();
            }
        });
        
        // Close button
        const closeBtn = document.getElementById('vmo-close-business-wizard');
        if (closeBtn && !closeBtn.dataset.vmoListenerAttached) {
            closeBtn.dataset.vmoListenerAttached = 'true';
            closeBtn.addEventListener('click', (e) => {
                e.preventDefault();
                console.log('🚪 Close button clicked');
                this.closeBusinessWizard();
            });
        }
        
        console.log('✅ Event listeners setup complete');
    }
    
    // ===== ADDITIONAL BUSINESS WIZARD METHODS =====
    
    // Close Business Wizard Modal
    closeBusinessWizard() {
        console.log('🚪 Closing Business Wizard - STOPPING ALL VOICE ACTIVITY');

        const modal = document.getElementById('vmo-business-wizard-modal');
        if (modal) {
            modal.style.display = 'none';
            modal.setAttribute('inert', ''); // Add inert attribute
            modal.removeAttribute('aria-hidden'); // Remove aria-hidden
            document.body.classList.remove('vmo-wizard-active');
        }
        
        // CRITICAL: Disable voice FIRST to prevent any new listening/speaking
        this.isVoiceEnabled = false;
        
        // ABSOLUTE KILL for TTS
        this.isGreetingPlaying = false; 
        try { this._progressiveTTSAbort(); } catch(_) {}
        try { this.stopTextAnimation(); } catch(_) {}
        try { window.speechSynthesis.cancel(); } catch(_) {}
        
        // CRITICAL: Stop listening IMMEDIATELY (privacy protection)
        if (this.recognition) {
            try {
                this.recognition.stop();
                this.recognition.abort(); // Force immediate stop
                console.log('✅ Voice recognition stopped - no longer listening');
            } catch (e) {
                // Already stopped - that's fine
            }
        }
        
        // CRITICAL: Stop any active transcription (speech-to-text)
        if (this.activeTranscription) {
            try {
                this.activeTranscription.stop();
                this.activeTranscription.abort();
                console.log('✅ Active transcription stopped');
            } catch (e) {
                // Already stopped
            }
            this.activeTranscription = null;
        }
        
        // CRITICAL: Stop speaking IMMEDIATELY (even mid-sentence)
        this.stopSpeaking();
        
        // CRITICAL: Stop VMO Voice Panel (separate system that auto-starts)
        if (window.vmoVoicePanel && window.vmoVoicePanel.endVoiceSession) {
            try {
                window.vmoVoicePanel.endVoiceSession();
                console.log('✅ VMO Voice Panel stopped - no longer auto-starting');
            } catch (e) {
                console.log('⚠️ Could not stop VMO Voice Panel:', e);
            }
        }
        
        // CRITICAL: Release microphone access
        if (window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic) {
            window.VMOAudioCoordinator.releaseMic('consultation');
            window.VMOAudioCoordinator.releaseMic('voice-panel'); // Also release voice panel's mic
            console.log('✅ Microphone released - no longer accessing mic');
        }
        
        // Hide voice modal if open
        this.hideModal();
        
        // Update voice button UI to show voice is OFF
        const voiceButton = document.querySelector('#vmo-wizard-voice');
        if (voiceButton) {
            voiceButton.classList.remove('active', 'recording');
            voiceButton.style.backgroundColor = '';
            voiceButton.style.color = '';
        }
        
        // Update transcribe button UI
        if (this.transcribeButton) {
            this.transcribeButton.classList.remove('active');
            this.transcribeButton.style.backgroundColor = '';
            this.transcribeButton.style.color = '';
        }
        
        if (window.showToast) {
            window.showToast('Business Consultation closed - Voice disabled', 'info');
        }

        // Optionally clear consultation session exposure when wizard closes
        try { window.vmoCurrentConsultationSessionId = this.sessionId; } catch(_) {}
        
        console.log('🔒 Privacy protected: AI is no longer listening or speaking');
    }
    
    // Approve business plan and start generation
    async approveBusinessPlan() {
        console.log('✅ Business plan approved - starting generation');
        
        if (window.showToast) {
            window.showToast('Generating your complete business ecosystem...', 'success');
        }
        
        // Keep consultation visible: show single overall progress + Next.
        const progressCard = document.createElement('section');
        progressCard.className = 'vmo-insight-card';
        progressCard.innerHTML = `
            <h5>🚀 Generating Your Business Package</h5>
            <div style="margin-top:10px;">
                <div style="height:12px; background:rgba(255,255,255,0.12); border-radius:999px; overflow:hidden;">
                    <div data-vmo-progress-fill style="height:12px; width:0%; background:rgba(0,226,255,0.55);"></div>
                </div>
                <div style="display:flex; justify-content:space-between; margin-top:8px; font-size:12px; opacity:0.9;">
                    <span data-vmo-progress-status>Starting…</span>
                    <span data-vmo-progress-pct>0%</span>
                </div>
            </div>
            <div data-vmo-next-wrap style="margin-top:12px; display:none;"></div>
        `;
        this.addNodeMessageToUI('ai', progressCard);

        const fillEl = progressCard.querySelector('[data-vmo-progress-fill]');
        const pctEl = progressCard.querySelector('[data-vmo-progress-pct]');
        const statusEl = progressCard.querySelector('[data-vmo-progress-status]');
        const nextWrap = progressCard.querySelector('[data-vmo-next-wrap]');

        let pct = 0;
        const bump = (to, label) => {
            pct = Math.max(pct, Math.min(100, to));
            if (fillEl) fillEl.style.width = pct + '%';
            if (pctEl) pctEl.textContent = pct + '%';
            if (label && statusEl) statusEl.textContent = label;
        };

        bump(5, 'Initializing…');
        const timer = setInterval(() => {
            // Smoothly approach 95% while waiting for backend
            if (pct < 95) bump(pct + 1, 'Working…');
        }, 700);

        // Call backend to start website generation
        try {
            bump(12, 'Starting chunked generation…');
            await this.triggerFullBusinessGeneration();

            if (this._draftWebsite && (this._draftWebsite.postId || this._draftWebsite.previewUrl || this._draftWebsite.homepageHtml)) {
                clearInterval(timer);
                bump(100, 'Complete');
                this.addMessageToUI('ai', '✅ Done. Click Next to return to the main dashboard.');

                if (nextWrap) {
                    const nextBtn = document.createElement('button');
                    nextBtn.type = 'button';
                    nextBtn.className = 'vmo-wizard-btn vmo-wizard-btn-primary';
                    nextBtn.textContent = 'Next';
                    nextBtn.addEventListener('click', () => {
                        try { this.closeBusinessWizard(); } catch(_) {}
                        try { window.location.reload(); } catch(_) {}
                    });
                    nextWrap.style.display = 'block';
                    nextWrap.appendChild(nextBtn);
                }
            } else {
                throw new Error('Chunked generation did not return a draft website.');
            }
        } catch (error) {
            console.error('Generation error:', error);
            try { clearInterval(timer); } catch(_) {}
            bump(Math.min(95, pct), 'Error');
            this.addMessageToUI('ai', '❌ Sorry, there was an error starting the generation. Please try again.');
        }
    }
    
    // Generate business plan summary
    async generateBusinessPlan() {
        console.log('📋 Generating business plan summary');
        
        this.addMessageToUI('ai', 'Let me create a comprehensive business plan based on our conversation...');
        
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams({
                    action: 'vmo_generate_business_summary',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    business_data: JSON.stringify(this.collectedInformation),
                    consultation_history: JSON.stringify(this.conversationHistory),
                    session_id: this.sessionId
                })
            });
            
            const data = await response.json();
            
            if (data.success && data.data.summary) {
                this.addMessageToUI('ai', data.data.summary);
                this.addMessageToUI('ai', 'Does this look good? If yes, I\'ll start generating your website!');
            } else {
                throw new Error('Failed to generate summary');
            }
        } catch (error) {
            console.error('Summary generation error:', error);
            this.addMessageToUI('ai', 'I have all the information I need. Ready to generate your website?');
        }
    }
    
    // Edit business plan
    editBusinessPlan() {
        console.log('✏️ Edit business plan');
        this.addMessageToUI('ai', 'Sure! What would you like to change? Just tell me and I\'ll update the plan.');
    }
    
    // Save business plan  
    saveBusinessPlan() {
        console.log('💾 Save business plan');
        this.generateBusinessPlan();
    }
    
    // Camera capture (mobile will open camera; desktop opens file dialog)
    openCameraCapture() {
        let input = document.getElementById('vmo-camera-capture');
        if (!input) {
            input = document.createElement('input');
            input.type = 'file';
            input.id = 'vmo-camera-capture';
            input.accept = 'image/*,video/*';
            input.capture = 'environment';
            input.style.display = 'none';
            document.body.appendChild(input);
            input.addEventListener('change', async () => {
                const file = input.files && input.files[0];
                if (!file) return;
                await this.uploadFileDirect(file);
                input.value = '';
            });
        }
        input.click();
    }

    // General file upload (for + uploads or camera capture) - legacy immediate upload
    openMediaUpload(typeFilter = null) {
        let input = document.getElementById('vmo-file-upload');
        if (!input) {
            input = document.createElement('input');
            input.type = 'file';
            input.id = 'vmo-file-upload';
            input.accept = typeFilter === 'video' ? 'video/*' : undefined;
            input.style.display = 'none';
            document.body.appendChild(input);
            input.addEventListener('change', async () => {
                const file = input.files && input.files[0];
                if (!file) return;
                await this.uploadFileDirect(file);
                input.value = '';
            });
        }
        input.click();
    }

    // ChatGPT-style: open file picker and stage files for attachment
    openFilePickerForStaging() {
        let input = document.getElementById('vmo-file-staging-input');
        if (!input) {
            input = document.createElement('input');
            input.type = 'file';
            input.id = 'vmo-file-staging-input';
            input.multiple = true; // Allow multiple files
            input.style.display = 'none';
            document.body.appendChild(input);
            input.addEventListener('change', () => {
                const files = input.files;
                if (!files || files.length === 0) return;
                for (const file of files) {
                    this.stageFileForAttachment(file);
                }
                input.value = '';
            });
        }
        input.click();
    }

    stageFileForAttachment(file) {
        if (!this.stagedAttachments) {
            this.stagedAttachments = [];
        }
        
        // Avoid duplicates by name+size
        const exists = this.stagedAttachments.some(f => f.name === file.name && f.size === file.size);
        if (exists) {
            console.log('📎 File already staged:', file.name);
            return;
        }
        
        this.stagedAttachments.push(file);
        console.log('📎 Staged file for attachment:', file.name, '| Total staged:', this.stagedAttachments.length);
        // ✅ FIX: Instantly interrupt any active TTS so voice playback does not bleed into the file upload processing
        try { this._progressiveTTSAbort(); } catch(_) {}
        this.updateAttachmentPreviewUI();
        // ✅ FIX: Instantly interrupt any active TTS so voice playback does not bleed into the file upload processing
        try { this._progressiveTTSAbort(); } catch(_) {}
    }

    removeStashedAttachment(index) {
        if (!this.stagedAttachments || index < 0 || index >= this.stagedAttachments.length) return;
        const removed = this.stagedAttachments.splice(index, 1);
        console.log('🗑️ Removed staged attachment:', removed[0]?.name);
        this.updateAttachmentPreviewUI();
        // ✅ FIX: Instantly interrupt any active TTS so voice playback does not bleed into the file upload processing
        try { this._progressiveTTSAbort(); } catch(_) {}
    }

    updateAttachmentPreviewUI() {
        // Find or create attachment preview container
        let previewContainer = document.getElementById('vmo-attachment-preview');
        const inputContainer = document.querySelector('.vmo-wizard-input-container');
        
        if (!inputContainer) return;
        
        if (!previewContainer) {
            previewContainer = document.createElement('div');
            previewContainer.id = 'vmo-attachment-preview';
            previewContainer.className = 'vmo-attachment-preview';
            previewContainer.style.cssText = `
                display: none;
                flex-wrap: wrap;
                gap: 8px;
                padding: 8px 12px;
                background: rgba(59, 130, 246, 0.1);
                border: 1px dashed rgba(59, 130, 246, 0.3);
                border-radius: 8px;
                margin-bottom: 8px;
            `;
            // Insert before the textarea
            const textarea = inputContainer.querySelector('textarea');
            if (textarea) {
                inputContainer.insertBefore(previewContainer, textarea);
            } else {
                inputContainer.prepend(previewContainer);
            }
        }
        
        // Clear and rebuild
        previewContainer.innerHTML = '';
        
        if (!this.stagedAttachments || this.stagedAttachments.length === 0) {
            previewContainer.style.display = 'none';
            return;
        }
        
        previewContainer.style.display = 'flex';
        
        this.stagedAttachments.forEach((file, index) => {
            const chip = document.createElement('div');
            chip.className = 'vmo-attachment-chip';
            chip.style.cssText = `
                display: flex;
                align-items: center;
                gap: 6px;
                padding: 4px 8px 4px 10px;
                background: rgba(59, 130, 246, 0.15);
                border: 1px solid rgba(59, 130, 246, 0.3);
                border-radius: 16px;
                font-size: 12px;
                color: #60a5fa;
                max-width: 200px;
            `;
            
            const emoji = this.getFileEmoji(file.type);
            const name = file.name.length > 20 ? file.name.slice(0, 17) + '…' : file.name;
            
            chip.innerHTML = `
                <span style="flex-shrink:0">${emoji}</span>
                <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${file.name}">${name}</span>
                <button type="button" class="vmo-attachment-remove" data-index="${index}" style="
                    background: none;
                    border: none;
                    color: #f87171;
                    cursor: pointer;
                    padding: 0 2px;
                    font-size: 14px;
                    line-height: 1;
                    flex-shrink: 0;
                " title="Remove">✕</button>
            `;
            
            previewContainer.appendChild(chip);
        });
        
        // Add click handlers for remove buttons
        previewContainer.querySelectorAll('.vmo-attachment-remove').forEach(btn => {
            btn.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                const idx = parseInt(btn.dataset.index, 10);
                this.removeStashedAttachment(idx);
            });
        });
    }

    async uploadFileAndGetInfo(file) {
        try {
            const AX = this.getAjax();
            const tryUpload = async (mode) => {
                const fd = new FormData();
                if (mode === 'consultation') {
                    fd.append('action', 'vmo_upload_consultation_media');
                    fd.append('security', AX.nonce);
                    fd.append('session_id', this.sessionId);
                    fd.append('context', 'consultation_upload');
                    fd.append('media', file, file.name);
                } else {
                    fd.append('action', 'vmo_upload_media');
                    fd.append('security', AX.nonce);
                    fd.append('file', file, file.name);
                }
                const res = await fetch(AX.url, { method: 'POST', body: fd });
                let json;
                try {
                    json = await this.parseAjaxJsonResponse(res, `${mode} upload`);
                } catch (error) {
                    console.warn(`Upload response parsing failed for ${mode} upload:`, error);
                    return {
                        ok: false,
                        json: {
                            success: false,
                            data: {
                                message: error?.message || 'Upload failed'
                            }
                        }
                    };
                }
                if (!json || !json.success) {
                    return { ok: false, json };
                }
                return { ok: true, json };
            };

            let uploadResult = await tryUpload('consultation');
            if (!uploadResult.ok) {
                uploadResult = await tryUpload('legacy');
            }
            const data = uploadResult.json;
            if (!data || !data.success) {
                console.error('Upload failed for', file.name);
                return null;
            }

            const payload = data.data || data;
            const id = payload.id || payload.attachment_id || payload.attachmentId || payload.media_id || payload.mediaId;
            const url = payload.url || payload.media_url || payload.mediaUrl;
            const mime = payload.mime || payload.media_type || payload.mediaType || '';
            const type = payload.type || payload.kind || '';
            const resolvedType = mime || type || file.type || '';
            
            const fileInfo = {
                id,
                url,
                filename: file.name,
                type: resolvedType,
                title: file.name,
                size: file.size,
                kind: resolvedType.startsWith('image') ? 'image' : resolvedType.startsWith('video') ? 'video' : 'document'
            };
            
            // For text files, read content for later ingestion
            const lower = file.name.toLowerCase();
            const isTextLike = file.type.startsWith('text/') || ['.txt','.md','.markdown','.csv','.json'].some(ext => lower.endsWith(ext));
            if (isTextLike) {
                fileInfo._rawText = await new Promise((resolve) => {
                    const reader = new FileReader();
                    reader.onload = () => resolve(String(reader.result || ''));
                    reader.onerror = () => resolve('');
                    reader.readAsText(file);
                });
            }
            
            // Register as asset
            this.registerUploadedAsset(fileInfo);
            
            return fileInfo;
        } catch (e) {
            console.error('Upload error for', file.name, e);
            return null;
        }
    }

    async uploadFileDirect(file) {
        try {
            const AX = this.getAjax();
            // Prefer consultation-specific upload endpoint (links file to session in DB)
            const tryUpload = async (mode) => {
                const fd = new FormData();
                if (mode === 'consultation') {
                    fd.append('action', 'vmo_upload_consultation_media');
                    fd.append('security', AX.nonce);
                    fd.append('session_id', this.sessionId);
                    fd.append('context', 'consultation_upload');
                    fd.append('media', file, file.name);
                } else {
                    fd.append('action', 'vmo_upload_media');
                    fd.append('security', AX.nonce);
                    fd.append('file', file, file.name);
                }
                const res = await fetch(AX.url, { method: 'POST', body: fd });
                let json;
                try {
                    json = await this.parseAjaxJsonResponse(res, `${mode} upload`);
                } catch (error) {
                    console.warn(`Upload response parsing failed for ${mode} upload:`, error);
                    return {
                        ok: false,
                        json: {
                            success: false,
                            data: {
                                message: error?.message || 'Upload failed'
                            }
                        }
                    };
                }
                if (!json || !json.success) {
                    return { ok: false, json };
                }
                return { ok: true, json };
            };

            let data = null;
            let uploadResult = await tryUpload('consultation');
            if (!uploadResult.ok) {
                uploadResult = await tryUpload('legacy');
            }
            data = uploadResult.json;
            if (!data || !data.success) {
                const msg = data?.data?.message || data?.message || 'Upload failed';
                this.addMessageToUI('ai', 'Upload failed: ' + msg);
                return;
            }

            // Normalize response shapes
            const payload = data.data || data;
            const id = payload.id || payload.attachment_id || payload.attachmentId || payload.media_id || payload.mediaId;
            const url = payload.url || payload.media_url || payload.mediaUrl;
            const mime = payload.mime || payload.media_type || payload.mediaType || '';
            const type = payload.type || payload.kind || '';

            const resolvedType = mime || type || file.type || '';
            const fileInfo = {
                id,
                url,
                filename: file.name,
                type: resolvedType,
                title: file.name,
                size: file.size
            };
            const emoji = resolvedType.startsWith('image') ? '🖼️' : resolvedType.startsWith('video') ? '🎥' : resolvedType.startsWith('audio') ? '🎵' : '📄';
            this.addMessageToUI('user', `${emoji} Uploaded: ${fileInfo.title}`);
            const assetRecord = this.registerUploadedAsset(fileInfo);
            this.saveMessageToDatabase('user', `${emoji} Uploaded: ${fileInfo.title}`, [fileInfo]);
            // Immediate analysis/summarization for all uploads
            const lower = (file.name || '').toLowerCase();
            const isTextLike = file.type.startsWith('text/')
                || ['.txt', '.md', '.markdown', '.csv', '.json', '.log', '.rtf'].some(ext => lower.endsWith(ext));
            if (isTextLike) {
                this._showAnalyzingIndicator(file.name);
                const text = await new Promise((resolve) => {
                    const reader = new FileReader();
                    reader.onload = () => resolve(String(reader.result || ''));
                    reader.readAsText(file);
                });
                await this.ingestBlueprintText(text, file.name, url);
                this._removeAnalyzingIndicator();
            } else {
                // Non-text media: request AI to analyze by URL (vision-capable backend expected)
                await this.analyzeUploadedMedia({ ...fileInfo, kind: assetRecord.kind });
            }
        } catch (e) {
            console.error('Upload error:', e);
            this._removeAnalyzingIndicator();
            this.addMessageToUI('ai', 'Upload error. Please try again.');
        }
    }

    async analyzeUploadedMedia(fileInfo) {
        try {
            const { url, type, title } = fileInfo;
            const AX = this.getAjax();
            // Show a friendly AI status with animated indicator
            // ✅ FIX: Instantly interrupt TTS on media analysis
            try { this._progressiveTTSAbort(); } catch(_) {}
            this._showAnalyzingIndicator(title);
            // Show a friendly AI status without exposing system instructions
            this.addMessageToUI('ai', `Analyzing “${title}”…`);
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_analyze_uploaded_media',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    url,
                    title,
                    type,
                    session_id: this.sessionId
                })
            });
            const data = await this.parseAjaxJsonResponse(response, 'media analysis');
            if (data.success) {
                // Remove the analyzing indicator now that we have a result
                this._removeAnalyzingIndicator();
                const payload = data.data || {};
                const structuredSummaryReady = !!payload.structuredSummaryReady;
                if (!structuredSummaryReady) {
                    this.addMessageToUI('ai', payload.response || 'Analysis complete.');
                    await this.saveMessageToDatabase('ai', payload.response || '');
                }

                if (structuredSummaryReady) {
                    await this.handleStructuredDocumentPayload(payload);
                } else {
                    const normalizedInfo = this.normalizeDocumentExtractionPayload(payload.extractedInfo || {});
                    if (Object.keys(normalizedInfo).length > 0) {
                        this.mergeKnownAnswers(normalizedInfo);
                    }
                    if (payload.documentRecord) {
                        this.upsertDocumentUpload(payload.documentRecord, { persist: false });
                    }
                    if (window.vmo_consultant_context_v1) {
                        try {
                            const canonical = {
                                extracted_information: this._toCanonicalExtracted(),
                                media_files: this._toCanonicalMediaFiles()
                            };
                            if (payload.documentRecord) {
                                canonical.documents = this.consultation_data.documents;
                                canonical.document_store = this.buildDocumentStorePayload();
                            }
                            await this.saveConsultationData(canonical);
                        } catch (e) { /* non-blocking */ }
                    }
                    
                    // ✅ AUTO-CONTINUE: Trigger an AI follow-up so the conversation
                    // doesn't dead-end at "Analysis complete" or "Received your image".
                    const isDocFile = /\.(docx?|pdf|txt|rtf)$/i.test(String(fileInfo?.title || ''))
                        || /wordprocessingml|pdf|msword|plain|rtf/i.test(String(fileInfo?.type || ''));
                    const isImageFile = /\.(jpe?g|png|gif|svg|webp|bmp|ico|tiff?)$/i.test(String(fileInfo?.title || ''))
                        || /^image\//i.test(String(fileInfo?.type || ''));

                    if (isDocFile && payload.response) {
                        console.log('📄 Document analyzed without structured summary — auto-triggering AI follow-up');
                        if (payload.rawText) {
                            this.blueprintRaw = payload.rawText;
                        }
                        setTimeout(async () => {
                            try {
                                await this.getAIResponse(
                                    'I just uploaded my business document. Please review what you extracted and present a summary of my business.',
                                    { isInternalTrigger: true }
                                );
                            } catch (e) {
                                console.warn('Auto-continue after document failed:', e);
                            }
                        }, 500);
                    } else if (isImageFile && payload.response) {
                        // ✅ FIX: Auto-continue after IMAGE upload so conversation doesn't stall
                        console.log('🖼️ Image analyzed — auto-triggering AI follow-up to continue consultation');
                        const pendingCtx = this._pendingFileContext || '';
                        this._pendingFileContext = null;
                        const imageTitle = fileInfo?.title || 'an image';
                        const continueMsg = pendingCtx
                            ? `I uploaded ${imageTitle} for my business. ${pendingCtx}. Please acknowledge the image and continue the consultation.`
                            : `I uploaded ${imageTitle} for my business. Please acknowledge the image and continue the consultation where we left off.`;
                        setTimeout(async () => {
                            try {
                                await this.getAIResponse(continueMsg, { isInternalTrigger: true });
                            } catch (e) {
                                console.warn('Auto-continue after image failed:', e);
                            }
                        }, 500);
                    }
                }
            } else {
                this._removeAnalyzingIndicator();
                const err = data?.data || {};
                const errMsg = err.message || data?.message || 'Analysis failed.';
                const errStatus = err.status || err.code || data?.status || data?.code || '';

                if (errStatus === 'insufficient_tokens') {
                    this.addMessageToUI('ai', errMsg);
                    return;
                }

                const isDocx = /\.docx$/i.test(String(title || ''))
                    || /wordprocessingml|docx/i.test(String(type || ''));
                const isPdf = /\.pdf$/i.test(String(title || ''))
                    || /pdf/i.test(String(type || ''));

                if (isDocx || isPdf) {
                    this.addMessageToUI(
                        'ai',
                        errMsg || 'I received your document but had trouble reading its contents. If this keeps happening, please export it to .txt or copy/paste the text into the chat.'
                    );
                } else {
                    this.addMessageToUI(
                        'ai',
                        (errMsg ? `I received your file but couldn't analyze it: ${errMsg}` : 'I received your file but couldn\'t analyze it.') +
                        ' If you want detailed analysis, please upload a .txt (or paste the text into the chat).'
                    );
                }
            }
        } catch (e) {
            console.warn('Media analysis request failed:', e);
            this._removeAnalyzingIndicator();
            const title = fileInfo?.title || '';
            const type = fileInfo?.type || '';
            const isDocx = /\.docx$/i.test(String(title || ''))
                || /wordprocessingml|docx/i.test(String(type || ''));
            const isPdf = /\.pdf$/i.test(String(title || ''))
                || /pdf/i.test(String(type || ''));

            if (isDocx || isPdf) {
                this.addMessageToUI('ai', 'I received your document but had trouble analyzing it. Please try again; if it persists, export to .txt or copy/paste the text into the chat.');
            } else {
                this.addMessageToUI('ai', 'I received your file but had trouble analyzing it. For detailed analysis, please upload a .txt (or paste the text into the chat).');
            }
        }
    }

    async ingestBlueprintText(rawText, title, url) {
        // Remove analyzing indicator when ingestion starts (it will show its own progress)
        this._removeAnalyzingIndicator();
        try {
            // ✅ GPT-5.2 UPGRADE: Raised from 18K to 400K to support 100+ page documents
            const MAX = 400000;
            const text = (rawText || '').slice(0, MAX);
            this.blueprintRaw = text;
            // ✅ FIX: Immediately interrupt TTS on document ingestion
            try { this._progressiveTTSAbort(); } catch(_) {}
            this.applyScenario('scenario_c_document', 'document_first');
            const AX = this.getAjax();
            // Hidden analysis: send to backend without exposing prompt text in chat
            this.addMessageToUI('ai', `Analyzing “${title}”…`);
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_analyze_text_document',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    title,
                    text,
                    session_id: this.sessionId
                })
            });
            const data = await this.parseAjaxJsonResponse(response, 'text document analysis');
            if (data.success) {
                const payload = data.data || {};
                
                // Hydrate blueprint summary in-memory for prompt building
                if (payload.summary) this.blueprintSummary = payload.summary;
                
                // ✅ CRITICAL: Use structured extractedInfo from backend
                const extractedInfo = this.applyDocumentRecommendations(payload.extractedInfo || {}, text);
                const parsedDoc = {
                    summary: extractedInfo.conciseSummary || payload.summary || '',
                    missingItems: extractedInfo.missingItems || [],
                    isComplete: extractedInfo.isComplete || false,
                    extractedInfo: extractedInfo
                };
                
                // Merge extracted info into consultation_data immediately
                if (extractedInfo.businessName) {
                    this.consultation_data.extracted_information.businessName = extractedInfo.businessName;
                }
                if (extractedInfo.industry) {
                    this.consultation_data.extracted_information.industry = extractedInfo.industry;
                }
                if (extractedInfo.businessStage) {
                    this.consultation_data.extracted_information.businessStage = extractedInfo.businessStage;
                }
                if (extractedInfo.location) {
                    this.consultation_data.extracted_information.location = extractedInfo.location;
                }
                if (extractedInfo.offerings && extractedInfo.offerings.length > 0) {
                    this.consultation_data.extracted_information.offerings = extractedInfo.offerings;
                }
                if (extractedInfo.targetAudience) {
                    this.consultation_data.extracted_information.targetAudience = extractedInfo.targetAudience;
                }
                if (extractedInfo.businessGoals) {
                    this.consultation_data.extracted_information.businessGoals = extractedInfo.businessGoals;
                }
                if (extractedInfo.businessVision) {
                    this.consultation_data.extracted_information.businessVision = extractedInfo.businessVision;
                }
                if (extractedInfo.visionTimeframe) {
                    this.consultation_data.extracted_information.visionTimeframe = extractedInfo.visionTimeframe;
                }
                if (extractedInfo.pricing) {
                    this.consultation_data.extracted_information.pricing = extractedInfo.pricing;
                }
                if (extractedInfo.websitePreferences) {
                    this.consultation_data.extracted_information.websitePreferences = extractedInfo.websitePreferences;
                }
                if (typeof extractedInfo.ecommerce_needed === 'boolean') {
                    this.consultation_data.extracted_information.needsEcommerce = extractedInfo.ecommerce_needed;
                }
                // ✅ NEW: Merge custom pages from document
                if (extractedInfo.customPages && Array.isArray(extractedInfo.customPages) && extractedInfo.customPages.length > 0) {
                    this.consultation_data.extracted_information.customPages = extractedInfo.customPages;
                    console.log('📄 Extracted custom pages from document:', extractedInfo.customPages);
                }
                // ✅ NEW: Merge page descriptions from document
                if (extractedInfo.pageDescriptions && typeof extractedInfo.pageDescriptions === 'object') {
                    this.consultation_data.extracted_information.pageDescriptions = extractedInfo.pageDescriptions;
                    console.log('📄 Extracted page descriptions from document:', Object.keys(extractedInfo.pageDescriptions));
                }
                // ✅ NEW: Merge email preferences from document
                if (extractedInfo.email_preferences && typeof extractedInfo.email_preferences === 'object') {
                    this.consultation_data.extracted_information.email_preferences = extractedInfo.email_preferences;
                    console.log('📧 Extracted email preferences from document:', extractedInfo.email_preferences);
                }
                if (parsedDoc && parsedDoc.isComplete) {
                    this.consultationTopics.forEach(topic => this.topicsCovered.add(topic));
                } else {
                    this.topicsCovered.add('business_concept');
                    this.topicsCovered.add('target_market');
                    this.topicsCovered.add('offerings');
                }
                
                // Enter document mode for the session
                this.mode = 'document';
                
                // Canonical merge + save if feature enabled
                if (window.vmo_consultant_context_v1) {
                    try {
                        if (payload.extractedInfo) this.mergeKnownAnswers(payload.extractedInfo);
                        
                        if (payload.documentRecord) {
                            this.upsertDocumentUpload(payload.documentRecord, { persist: false });
                        }
                        const canonical = {
                            extracted_information: this._toCanonicalExtracted(),
                            documents: this.consultation_data.documents,
                            document_store: this.buildDocumentStorePayload(),
                            media_files: this._toCanonicalMediaFiles(),
                            // ✅ FIX: Persist blueprintRaw and blueprintSummary to database
                            blueprintRaw: this.blueprintRaw,
                            blueprintSummary: this.blueprintSummary
                        };
                        await this.saveConsultationData(canonical);
                    } catch (e) { /* non-blocking */ }
                }
                
                // ✅ FIX: Do NOT auto-advance phase from frontend on document upload.
                // Phase advancement is controlled by the backend via [[PHASE_COMPLETE]] markers
                // and phase gating (business_name, offers, target_audience required).
                // Auto-advancing here bypasses those checks and desynchronizes frontend/backend.
                this.enforcePhaseAdherence();
                
                
                // Display analysis ONLY if document is incomplete
                // If complete, autoConfirmDocument will handle messaging
                if (parsedDoc?.isComplete) {
                    // 🎯 Verbally acknowledge document analysis if voice is enabled
                    if (this.isVoiceEnabled) {
                        const businessName = extractedInfo.businessName || 'your business';
                        this.speak(`Excellent! I've analyzed your business document for ${businessName}. I found all the key information I need. Let me show you what I've captured so you can confirm everything is correct.`);
                    }
                    // Skip showing raw analysis—autoConfirmDocument will display business summary
                    await this.autoConfirmDocument(parsedDoc);
                } else {
                    // 🎯 Verbally acknowledge document analysis if voice is enabled (incomplete doc)
                    if (this.isVoiceEnabled) {
                        this.speak(`I've analyzed your document and found some great information. I have a few questions to fill in the gaps. Let me show you what I found.`);
                    }
                    // Display extracted summary in Q&A format for incomplete documents
                    const analysisText = payload.response || 'Analysis complete.';
                    this.addMessageToUI('ai', analysisText);
                    await this.saveMessageToDatabase('ai', analysisText);

                    const docSummaryMsg = this.buildDocumentSummaryMessage(parsedDoc);
                    if (docSummaryMsg) {
                        this.addMessageToUI('ai', docSummaryMsg);
                        await this.saveMessageToDatabase('ai', docSummaryMsg);
                    }

                    const mergedExtracted = this.getDisplayExtractedPayload(parsedDoc?.extractedInfo);
                    const qaForOutstanding = this.buildDocumentQuestionnaireReview(mergedExtracted);
                    const outstandingList = this.collectOutstandingQuestions(qaForOutstanding.unansweredQuestions, parsedDoc?.missingItems || []);
                    if (outstandingList.length > 0) {
                        const reminderMsg = '⚠️ **Additional Details Needed**\nYour document covered a lot, but I still need a few more answers to move forward.';
                        this.addMessageToUI('ai', reminderMsg);
                        await this.saveMessageToDatabase('ai', reminderMsg);
                        const followUp = this.buildOutstandingQuestionsPrompt(outstandingList);
                        if (followUp) {
                            this.addMessageToUI('ai', followUp);
                            await this.saveMessageToDatabase('ai', followUp);
                        }
                    } else {
                        const confirmationMsg = "\n\n✅ **Please review the above summary carefully.**\nIs everything correct? If it is, reply with a confirmation (in any language). If anything is missing or incorrect, tell me what needs to be fixed.\n\nOnce confirmed, I will move to the next phase: **Deep Research & Market Scan**.";
                        this.addMessageToUI('ai', confirmationMsg);
                        await this.saveMessageToDatabase('ai', confirmationMsg);
                    // ✅ CRITICAL FIX: Auto-trigger AI to continue instead of waiting
                    setTimeout(async () => {
                        try {
                            console.log('🚀 VMO: Auto-continuing after incomplete doc summary (no outstanding questions)');
                            await this.getAIResponse(
                                'My document has been analyzed and the summary is shown above. Please review what you extracted, confirm the key details with me, and guide me through what comes next. You are my business consultant - drive this consultation forward.',
                                { isInternalTrigger: true }
                            );
                        } catch (e) { console.warn('Auto-continue failed:', e); }
                    }, 1200);
                    }
                }
                
            } else {
                this.addMessageToUI('ai', 'I analyzed your document. If you want deeper extraction, upload a .docx or .txt version too.');
            }
        } catch (e) {
            console.warn('Blueprint ingestion failed:', e);
            this.addMessageToUI('ai', 'I analyzed your document. If you want deeper extraction, upload a .docx or .txt version too.');
        }
    }
    
    mergeKnownAnswers(extractedInfo) {
        if (!extractedInfo || typeof extractedInfo !== 'object') return;
        const target = this.consultation_data.extracted_information;
        
        const pickFirst = (...keys) => {
            for (const key of keys) {
                if (!key) continue;
                const value = extractedInfo[key];
                if (value === undefined || value === null) continue;
                if (typeof value === 'string' && !value.trim()) continue;
                if (Array.isArray(value) && value.length === 0) continue;
                if (this.isPlainObject(value) && Object.keys(value).length === 0) continue;
                return value;
            }
            return undefined;
        };

        // Helper: assign value if non-empty
        const assignIf = (key, val) => {
            if (val === undefined || val === null || val === '') return;
            if (Array.isArray(val)) {
                const existing = Array.isArray(target[key]) ? target[key] : [];
                target[key] = Array.from(new Set([ ...existing, ...val.filter(Boolean) ]));
                return;
            }
            if (typeof val === 'boolean') {
                target[key] = val;
                return;
            }
            target[key] = val;
        };
        
        const fieldMappings = {
            businessOverview: ['businessOverview', 'business_summary', 'conciseSummary'],
            businessName: ['businessName', 'business_name'],
            industry: ['industry', 'businessIndustry'],
            businessStage: ['businessStage', 'business_stage', 'businessType'],
            offerings: ['offerings', 'products', 'services'],
            targetAudience: ['targetAudience', 'target_audience'],
            location: ['location', 'businessLocation'],
            pricing: ['pricing', 'pricingModel'],
            social: ['social', 'socialLinks'],
            hours: ['hours', 'businessHours'],
            businessIdea: ['businessIdea', 'businessConcept'],
            offeringType: ['offeringType'],
            businessGoals: ['businessGoals', 'goals'],
            successDefinition: ['successDefinition', 'success_definition', 'northStarMetric'],
            scaleIntent: ['scaleIntent', 'scale_intent'],
            automationPreference: ['automationPreference', 'automation_preference'],
            businessVision: ['businessVision', 'vision', 'visionStatement'],
            visionTimeframe: ['visionTimeframe', 'vision_timeframe', 'visionTimeline'],
            websitePreferences: ['websitePreferences', 'website_preferences'],
            brandingInfo: ['brandingInfo', 'branding'],
            needsEcommerce: ['needsEcommerce', 'ecommerce_needed'],
            needsBooking: ['needsBooking', 'needs_booking'],
            needsPhoneSystem: ['needsPhoneSystem', 'needs_phone_system'],
            needsEmailMarketing: ['needsEmailMarketing', 'needs_email_marketing']
        };

        Object.entries(fieldMappings).forEach(([targetKey, candidates]) => {
            assignIf(targetKey, pickFirst(...candidates));
        });

        if (this.isPlainObject(extractedInfo.needs)) {
            assignIf('needsEcommerce', extractedInfo.needs.ecommerce ?? extractedInfo.needs.ecommerce_needed);
            assignIf('needsBooking', extractedInfo.needs.booking ?? extractedInfo.needs.booking_needed);
            assignIf('needsPhoneSystem', extractedInfo.needs.phone_system ?? extractedInfo.needs.phoneSystem);
            assignIf('needsEmailMarketing', extractedInfo.needs.email_marketing ?? extractedInfo.needs.emailMarketing);
        }

        // Operations object merge
        if (this.isPlainObject(extractedInfo.operations)) {
            if (!this.isPlainObject(target.operations)) {
                target.operations = {};
            }
            Object.entries(extractedInfo.operations).forEach(([k, v]) => {
                if (v !== null && v !== undefined) {
                    target.operations[k] = v;
                }
            });
        }
        
        // Covered topics (legacy compat)
        if (extractedInfo.topicCovered) this.topicsCovered.add(extractedInfo.topicCovered);
        
        // Save to backend if feature enabled
        this.consultation_data.extracted_information = this.applyDocumentRecommendations(target, this.blueprintRaw || '');
        this.collectedInformation = this.consultation_data.extracted_information;

        if (window.vmo_consultant_context_v1) {
            this.saveConsultationData(this.consultation_data);
        }
    }

    normalizeDocumentExtractionPayload(info = {}) {
        if (!info || typeof info !== 'object') return {};
        const normalized = { ...info };
        if (normalized.offerings) {
            if (Array.isArray(normalized.offerings)) {
                normalized.offerings = normalized.offerings.filter(Boolean);
            } else if (typeof normalized.offerings === 'string') {
                normalized.offerings = normalized.offerings.split(/[,•\n]+/).map(v => v.trim()).filter(Boolean);
            }
        }
        if (typeof normalized.ecommerce_needed === 'boolean') {
            normalized.needsEcommerce = normalized.ecommerce_needed;
        }
        if (typeof normalized.needs_booking === 'boolean') {
            normalized.needsBooking = normalized.needs_booking;
        }
        if (typeof normalized.needs_phone_system === 'boolean') {
            normalized.needsPhoneSystem = normalized.needs_phone_system;
        }
        if (typeof normalized.needs_email_marketing === 'boolean') {
            normalized.needsEmailMarketing = normalized.needs_email_marketing;
        }
        if (!normalized.businessOverview && normalized.conciseSummary) {
            normalized.businessOverview = normalized.conciseSummary;
        }
        return normalized;
    }

    async handleStructuredDocumentPayload(payload = {}) {
        const normalizedInfo = this.applyDocumentRecommendations(
            this.normalizeDocumentExtractionPayload(payload.extractedInfo || {}),
            payload.blueprintRaw || ''
        );
        if (Object.keys(normalizedInfo).length > 0) {
            this.mergeKnownAnswers(normalizedInfo);
        }
        if (payload.documentRecord) {
            this.upsertDocumentUpload(payload.documentRecord, { persist: false });
        }
        if (payload.blueprintRaw) {
            this.blueprintRaw = payload.blueprintRaw;
        }
        if (payload.blueprintSummary || normalizedInfo.conciseSummary) {
            this.blueprintSummary = payload.blueprintSummary || normalizedInfo.conciseSummary || this.blueprintSummary;
        }
        this.documentFollowupActive = false;
        this.pendingDocumentQuestions = [];
        this.totalDocumentFollowups = 0;
        this.awaitingDocumentAnswer = null;
        this.applyScenario('scenario_c_document', 'document_first');
        this.mode = 'document';
        const missingItems = Array.isArray(payload.missingItems)
            ? payload.missingItems
            : Array.isArray(normalizedInfo.missingItems) ? normalizedInfo.missingItems : [];
        const parsedDoc = {
            summary: payload.summary || normalizedInfo.conciseSummary || '',
            missingItems,
            isComplete: typeof payload.isComplete === 'boolean'
                ? payload.isComplete
                : missingItems.length === 0,
            extractedInfo: normalizedInfo,
            documentId: payload.documentRecord?.id || null
        };
        await this.autoConfirmDocument(parsedDoc);
        this.enforceSystemRecommendations(payload.blueprintRaw || '');
        if (window.vmo_consultant_context_v1) {
            const canonical = {
                extracted_information: this._toCanonicalExtracted(),
                documents: this.consultation_data.documents,
                document_store: this.buildDocumentStorePayload(),
                media_files: this._toCanonicalMediaFiles(),
                blueprintRaw: this.blueprintRaw,
                blueprintSummary: this.blueprintSummary
            };
            await this.saveConsultationData(canonical);
        }
    }

    parseDocumentAnalysisResponse(responseText = '') {
        const result = {
            summary: '',
            qaPairs: [],
            missingItems: [],
            isComplete: false
        };
        if (!responseText) return result;

        const clean = responseText.replace(/\r/g, '');
        const section = (label) => {
            const regex = new RegExp(`${label}[^\n]*\n([\s\S]*?)(?=\n\d\)|$)`, 'i');
            const match = clean.match(regex);
            return match ? match[1].trim() : '';
        };

        result.summary = section('1\)');

        const qaBlock = section('2\)');
        if (qaBlock) {
            qaBlock.split(/\n+/).map(line => line.trim()).filter(Boolean).forEach(line => {
                const questionBreak = line.indexOf('?');
                let question = line;
                let answer = '';
                if (questionBreak !== -1) {
                    question = line.slice(0, questionBreak + 1);
                    answer = line.slice(questionBreak + 1).trim();
                } else {
                    const colonBreak = line.indexOf(':');
                    if (colonBreak !== -1) {
                        question = line.slice(0, colonBreak + 1);
                        answer = line.slice(colonBreak + 1).trim();
                    }
                }
                if (question && answer) {
                    result.qaPairs.push({ question: question.trim(), answer });
                }
            });
        }

        const missingBlock = section('3\)');
        if (missingBlock) {
            const normalized = missingBlock.trim();
            if (/^none$/i.test(normalized)) {
                result.missingItems = [];
                result.isComplete = true;
            } else {
                result.missingItems = normalized.split(/\n+/).map(item => item.trim()).filter(Boolean);
            }
        }

        const readyBlock = section('4\)');
        if (readyBlock && /nothing is missing/i.test(readyBlock)) {
            result.isComplete = true;
        }

        return result;
    }

    applyDocumentInsightsFromAnalysis(parsed = {}) {
        if (!parsed) return;
        const info = this.consultation_data.extracted_information || {};
        const assign = (key, value) => {
            if (!value) return;
            if (!info[key] || (typeof info[key] === 'string' && !info[key].trim())) {
                info[key] = value.trim();
            }
        };
        const pushArray = (key, value) => {
            if (!value) return;
            const items = Array.isArray(info[key]) ? info[key] : [];
            const pieces = value.split(/[,•;\-]/).map(v => v.trim()).filter(Boolean);
            info[key] = Array.from(new Set([...items, ...pieces]));
        };

        if (parsed.summary) {
            assign('businessOverview', parsed.summary);
            assign('businessIdea', parsed.summary);
        }

        parsed.qaPairs.forEach(({ question, answer }) => {
            const q = (question || '').toLowerCase();
            const val = (answer || '').trim();
            if (!val) return;
            if (q.includes('business name') || q.includes('company name') || q.includes('product name')) {
                assign('businessName', val);
            } else if (q.includes('industry') || q.includes('sector') || q.includes('niche')) {
                assign('industry', val);
            } else if (q.includes('target audience') || q.includes('target market') || q.includes('customers') || q.includes('client')) {
                assign('targetAudience', val);
            } else if (q.includes('pricing') || q.includes('price') || q.includes('plans')) {
                pushArray('pricing', val);
            } else if (q.includes('product') || q.includes('service') || q.includes('offerings') || q.includes('feature')) {
                pushArray('offerings', val);
            } else if (q.includes('goal') || q.includes('vision') || q.includes('objective') || q.includes('strategy')) {
                assign('businessGoals', val);
            } else if (q.includes('location') || q.includes('operate')) {
                assign('location', val);
            } else if (q.includes('support') || q.includes('customer support')) {
                if (!info.operations) info.operations = {};
                info.operations.support = val;
            } else if (q.includes('payment')) {
                if (!info.operations) info.operations = {};
                info.operations.payments = val;
            } else if (q.includes('communication') || q.includes('channels') || q.includes('email')) {
                if (!info.operations) info.operations = {};
                info.operations.email = val;
            }
        });

        const allTopics = ['business_concept', 'target_market', 'offerings'];
        if (parsed.qaPairs.length > 0 || parsed.summary) {
            allTopics.forEach(topic => this.topicsCovered.add(topic));
        }

        this.consultation_data.extracted_information = info;
        this.collectedInformation = info;
        if (window.vmo_consultant_context_v1) {
            this.saveConsultationData({
                extracted_information: this._toCanonicalExtracted(),
                blueprintSummary: this.blueprintSummary,
                blueprintRaw: this.blueprintRaw
            });
        }
    }

    getDocumentIntakeQuestions() {
        return [
            { field: 'businessName', question: 'What is your business or brand name?' },
            { field: 'industry', question: 'Which industry or niche do you operate in?' },
            { field: 'businessIdea', question: 'What core product or service are you offering?' },
            { field: 'offerings', question: 'List your flagship products or services.' },
            { field: 'targetAudience', question: 'Who is your ideal customer or audience?' },
            { field: 'location', question: 'Where do you operate from (city/state/country)?' },
            { field: 'businessGoals', question: 'What goals or outcomes are you aiming for?' },
            { field: 'businessVision', question: 'What is the long-term vision for this business?' },
            { field: 'visionTimeframe', question: 'What is the timeframe for achieving that vision?' },
            { field: 'pricing', question: 'How are your products or services priced?' },
            { field: 'websitePreferences', question: 'Do you have any website style or feature preferences?' }
        ];
    }

    getDocumentQuestionByField(field) {
        if (!field) return null;
        return this.getDocumentIntakeQuestions().find(item => item.field === field) || null;
    }

    resolveFieldFromMissingLabel(label = '') {
        if (!label) return null;
        const lower = label.toLowerCase();
        if (lower.includes('business name') || lower.includes('brand name')) return 'businessName';
        if (lower.includes('industry') || lower.includes('niche')) return 'industry';
        if (lower.includes('product') || lower.includes('service') || lower.includes('offering')) return 'offerings';
        if (lower.includes('customer') || lower.includes('audience')) return 'targetAudience';
        if (lower.includes('operate') || lower.includes('location') || lower.includes('city')) return 'location';
        if (lower.includes('goal') || lower.includes('objective')) return 'businessGoals';
        if (lower.includes('vision statement') || lower.includes('long-term vision')) return 'businessVision';
        if (lower.includes('timeframe') || lower.includes('timeline')) return 'visionTimeframe';
        if (lower.includes('pricing') || lower.includes('price')) return 'pricing';
        if (lower.includes('website') || lower.includes('style') || lower.includes('feature preference')) return 'websitePreferences';
        return null;
    }

    isPlaceholderValue(value = '') {
        if (typeof value !== 'string') {
            return false;
        }
        const normalized = value.replace(/\s+/g, ' ').trim().toLowerCase();
        if (!normalized) {
            return false;
        }
        const placeholders = new Set([
            'not specified',
            'not provided',
            'not available',
            'n/a',
            'na',
            'null',
            'none',
            'unknown',
            'tbd',
            'pending',
            'not sure',
            'unspecified',
            'not captured',
            'not listed',
            'not answered',
            'no data',
            'not noted',
            'not applicable',
            'undefined'
        ]);
        if (placeholders.has(normalized)) {
            return true;
        }
        if (/^not (yet )?(provided|specified|shared|set)/.test(normalized)) {
            return true;
        }
        if (/^to be (determined|decided)/.test(normalized)) {
            return true;
        }
        return false;
    }

    hasMeaningfulValue(value) {
        if (value === null || value === undefined) return false;
        if (typeof value === 'boolean') return true;
        if (typeof value === 'number') return true;
        if (Array.isArray(value)) return value.length > 0;
        if (this.isPlainObject(value)) return Object.keys(value).length > 0;
        const text = String(value).trim();
        if (!text.length) return false;
        return !this.isPlaceholderValue(text);
    }

    isPlainObject(value) {
        return value !== null && typeof value === 'object' && !Array.isArray(value);
    }

    cleanDisplayValue(value = '') {
        if (value === null || value === undefined) return '';
        let text = String(value);
        text = text.replace(/```[a-z]*\n?([\s\S]*?)```/gi, '$1');
        text = text.replace(/```/g, '');
        text = text.replace(/<[^>]+>/g, '');
        text = text.replace(/\r/g, '');
        text = text.replace(/\u00a0/g, ' ');
        text = text.replace(/\s+\n/g, '\n');
        return text.trim();
    }

    normalizeUserUtterance(text = '') {
        const cleaned = this.cleanDisplayValue(text);
        return cleaned.replace(/\s+/g, ' ').trim();
    }

    containsUrlOrDomain(text = '') {
        const t = (text || '').trim();
        if (!t) return false;
        if (/https?:\/\//i.test(t)) return true;
        return /\b[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\.[a-z]{2,}\b/i.test(t);
    }

    isLowSignalUtterance(text = '') {
        const t = this.normalizeUserUtterance(text).toLowerCase();
        if (!t) return true;
        if (this.containsUrlOrDomain(t)) return false;

        if (/^(hi|hello|hey|yo|ok|okay|k|kk|sure|cool|nice|thanks|thank you|yep|yup|yeah|nah|nope|idk|i don't know|dont know|not sure)$/i.test(t)) {
            return true;
        }
        const wordCount = t.split(' ').filter(Boolean).length;
        if (wordCount <= 2 && t.length < 12) {
            return true;
        }
        return false;
    }
    
    /**
     * 🔄 Detect if user wants to reset/restart the consultation
     * Returns true if the message indicates intent to start over
     */
    detectResetIntent(message) {
        if (!message || typeof message !== 'string') return false;
        
        const lowerMessage = message.toLowerCase().trim();
        
        // Skip very long messages (likely not a reset request)
        if (lowerMessage.length > 200) return false;
        
        // Exact/near-exact matches (high confidence)
        const exactPhrases = [
            'start over',
            'start fresh',
            'start again',
            'reset consultation',
            'reset everything',
            'forget everything',
            'forget all',
            'clear everything',
            'clear consultation',
            'new consultation',
            'new session',
            'begin again',
            'begin fresh',
            'wipe everything',
            'wipe the slate',
            'clean slate',
            'restart',
            'start from scratch',
            'start from the beginning',
            'erase everything',
            'delete everything',
            'delete my consultation',
            'i want to start over',
            "let's start over",
            "let's begin again",
            'can we start over',
            'can i start over',
            'i want to reset',
            'please reset',
            'reset this',
            'reset my session',
            'forget what i said',
            'forget our conversation',
            'clear our conversation',
            'start a new business',
            'different business',
            'new business idea',
            'change my business',
            'scratch that start over'
        ];
        
        // Check for exact phrase matches
        for (const phrase of exactPhrases) {
            if (lowerMessage.includes(phrase)) {
                console.log('🔄 Reset intent detected (phrase match):', phrase);
                return true;
            }
        }
        
        // Pattern-based detection (more flexible)
        const resetPatterns = [
            /^reset$/i,
            /^start\s*(over|fresh|again)$/i,
            /^forget\s*(it\s*)?all$/i,
            /^clear\s*(it\s*)?all$/i,
            /want\s+to\s+(start|begin)\s+(over|fresh|again)/i,
            /can\s+(we|i|you)\s+(start|begin)\s+(over|fresh|again)/i,
            /(please|pls)\s+(reset|restart|start\s*over)/i,
            /let'?s?\s+(reset|restart|start\s*(over|fresh))/i,
            /forget\s+(everything|all|this|what)\s*(i|we|you)?\s*(said|told|discussed)?/i,
            /wipe\s+(the\s+)?slate/i,
            /(new|different|another)\s+business\s+(idea|concept|venture)?/i,
            /scratch\s+(that|this)/i
        ];
        
        for (const pattern of resetPatterns) {
            if (pattern.test(lowerMessage)) {
                console.log('🔄 Reset intent detected (pattern match):', pattern);
                return true;
            }
        }
        
        return false;
    }

    isExtractionEligible(text = '') {
        const t = this.normalizeUserUtterance(text);
        if (!t) return false;
        if (this.containsUrlOrDomain(t)) return true;
        if (this.isLowSignalUtterance(t)) return false;
        if (/@/.test(t)) return true;
        if (/\d/.test(t)) return true;
        const words = t.split(' ').filter(Boolean);
        return words.length >= 3 || t.length >= 14;
    }

    getCurrentExpectedTopic() {
        const remaining = this.consultationTopics.filter(t => !this.topicsCovered.has(t));
        return remaining.length ? remaining[0] : null;
    }

    buildExpectedTopicPrompt(topic) {
        switch (topic) {
            case 'website_status':
                return 'Quick clarification so I don\'t assume: do you already have a website you want to improve, or are we starting from scratch?';
            case 'business_concept':
                return 'To keep this accurate: what are you trying to sell or offer (in 1–2 sentences)?';
            case 'target_market':
                return 'Who is your ideal customer (and what problem are you solving for them)?';
            case 'offerings':
                return 'What specific products/services will you offer? (A short list is perfect.)';
            case 'business_goals':
                return 'What does success look like for you in the next 3–12 months?';
            case 'website_vision':
                return 'What should your website help people do (book, buy, request a quote, join a list), and any style examples you like?';
            default:
                return 'Could you share a bit more detail so I can capture this correctly?';
        }
    }

    isMeaningfulForTopic(message = '', topic) {
        const t = this.normalizeUserUtterance(message);
        if (!t) return false;
        if (this.containsUrlOrDomain(t) && topic === 'website_status') return true;
        if (this.isLowSignalUtterance(t)) return false;
        const words = t.split(' ').filter(Boolean);

        switch (topic) {
            case 'website_status':
                return /(from scratch|starting|new site|no website|don't have|do not have|i have|we have|yes|no|redesign|upgrade|improve|keep)/i.test(t) || this.containsUrlOrDomain(t);
            case 'offerings':
                return words.length >= 2 || /,|\n|•|\//.test(t);
            case 'business_concept':
            case 'target_market':
            case 'business_goals':
            case 'website_vision':
                return words.length >= 4 || t.length >= 20;
            default:
                return this.isExtractionEligible(t);
        }
    }

    shouldBypassAIForLowSignal(message = '', expectedTopic) {
        const t = this.normalizeUserUtterance(message);
        if (!t) return true;
        if (!this.isLowSignalUtterance(t)) return false;
        return !this.isMeaningfulForTopic(t, expectedTopic);
    }

    applyDocumentRecommendations(extractedInfo = {}, referenceText = '') {
        if (!extractedInfo || typeof extractedInfo !== 'object') {
            return extractedInfo;
        }
        const info = extractedInfo;
        const phoneStatus = this.getPhoneProvisioningStatus();
        info.needsPhoneSystem = !!(phoneStatus && phoneStatus.has_phone) ? false : true;
        if (phoneStatus && phoneStatus.has_phone) {
            if (!info.operations || typeof info.operations !== 'object' || Array.isArray(info.operations)) {
                info.operations = {};
            }
            if (!info.operations.phone) {
                info.operations.phone = phoneStatus.phone_formatted || phoneStatus.phone_number || 'Business phone already provisioned in dashboard';
            }
        }
        info.needsEmailMarketing = true;

        const haystack = this.buildCommerceReferenceText(info, referenceText);
        const commerceKeywords = ['sell','sale','store','shop','cart','checkout','purchase','subscription','plan','plans','pricing','invoice','payment','payments','ecommerce','order','booking','reservation'];
        const needsCommerce = commerceKeywords.some(term => haystack.includes(term));
        if (needsCommerce) {
            info.needsEcommerce = true;
            if (!this.isPlainObject(info.operations)) {
                info.operations = {};
            }
            if (!info.operations.payments) {
                info.operations.payments = 'Payments & invoicing required for online plans and conversions.';
            }
        }

        if (!this.hasMeaningfulValue(info.visionTimeframe)) {
            const derivedTimeframe = this.deriveVisionTimeframeFromGoals(info, referenceText);
            if (this.hasMeaningfulValue(derivedTimeframe)) {
                info.visionTimeframe = derivedTimeframe;
            }
        }
        return info;
    }

    deriveVisionTimeframeFromGoals(info = {}, referenceText = '') {
        const collected = [];
        const pushValue = (value) => {
            if (!value) return;
            if (Array.isArray(value)) {
                collected.push(value.join('\n'));
                return;
            }
            if (this.isPlainObject(value)) {
                collected.push(Object.values(value).join('\n'));
                return;
            }
            collected.push(String(value));
        };

        pushValue(info.businessGoals);
        pushValue(info.businessVision);
        pushValue(referenceText);
        pushValue(this.blueprintRaw || '');

        const combined = this.cleanDisplayValue(collected.join('\n'));
        if (!combined) {
            return '';
        }

        const summarySegments = [];
        const termPattern = /(short|mid|medium|long)\s*-?\s*term[^:]{0,40}[:\-]\s*([^\n\.]+(?:\d[^\n\.]*)?)/gi;
        let match;
        while ((match = termPattern.exec(combined))) {
            const label = match[1].toLowerCase().startsWith('mid') ? 'Mid term' : `${this.toSentenceCase(match[1])} term`;
            const detail = match[2].trim().replace(/\s+/g, ' ');
            summarySegments.push(`${label}: ${detail}`);
        }

        if (summarySegments.length) {
            return summarySegments.join(' | ');
        }

        const timelineMatches = combined.match(/\b\d+[\-–]?\d*\s*(?:days?|weeks?|months?|years?)\b/gi);
        if (timelineMatches && timelineMatches.length) {
            const unique = Array.from(new Set(timelineMatches.map(item => item.replace(/\s+/g, ' ').trim())));
            return unique.slice(0, 3).join(' • ');
        }

        return '';
    }

    detectAutoTimeframeRequest(answerText = '') {
        if (!answerText) {
            return false;
        }
        const normalized = answerText.toLowerCase();
        const mentionsTimeframe = /(time\s*frame|timeline|time line)/.test(normalized);
        const directsAI = /(you|ya|ur|you're|youre)/.test(normalized) && /(figure|calculate|determine|decide|provide|give|tell|handle|supposed)/.test(normalized);
        return mentionsTimeframe && directsAI;
    }

    buildCommerceReferenceText(info = {}, referenceText = '') {
        const pieces = [
            referenceText || '',
            this.blueprintRaw || '',
            Array.isArray(info.offerings) ? info.offerings.join(' ') : '',
            info.pricing || '',
            info.conciseSummary || '',
            info.businessGoals || '',
            info.businessVision || ''
        ];
        return pieces.join(' ').toLowerCase();
    }

    enforceSystemRecommendations(referenceText = '') {
        if (!this.consultation_data || !this.consultation_data.extracted_information) {
            return;
        }
        const updated = this.applyDocumentRecommendations(this.consultation_data.extracted_information, referenceText);
        this.consultation_data.extracted_information = updated;
        this.collectedInformation = updated;
    }

    filterResolvedMissingItems(items = []) {
        if (!Array.isArray(items)) return [];
        return items
            .map(item => typeof item === 'string' ? item.trim() : '')
            .filter(item => item.length && !this.isMissingItemResolved(item));
    }

    storeDocumentParseState(parsedDoc = {}) {
        const doc = parsedDoc ? { ...parsedDoc } : {};
        doc.missingItems = this.filterResolvedMissingItems(doc.missingItems || []);
        if (!doc.documentId && this.lastParsedDocument?.documentId) {
            doc.documentId = this.lastParsedDocument.documentId;
        }
        this.lastParsedDocument = doc;
        this.lastDocMissingItems = doc.missingItems;
    }

    isMissingItemResolved(label = '') {
        if (!label) return true;
        const ex = this.consultation_data?.extracted_information || {};
        const lower = label.toLowerCase();
        const has = (field) => this.hasMeaningfulValue(ex[field]);

        if (lower.includes('complete business information')) {
            return this.hasCriticalKnownAnswers();
        }
        if (lower.includes('business name') || lower.includes('brand name')) return has('businessName');
        if (lower.includes('industry') || lower.includes('niche')) return has('industry');
        if (lower.includes('product') || lower.includes('service') || lower.includes('offering')) return has('offerings');
        if (lower.includes('target') || lower.includes('audience') || lower.includes('customer')) return has('targetAudience');
        if (lower.includes('location')) return has('location');
        if (lower.includes('goal')) return has('businessGoals');
        if (lower.includes('vision timeframe') || lower.includes('timeframe')) return has('visionTimeframe');
        if (lower.includes('vision')) return has('businessVision');
        if (lower.includes('pricing') || lower.includes('price')) return has('pricing');
        if (lower.includes('website')) return has('websitePreferences');
        if (lower.includes('ecommerce')) return this.consultation_data?.extracted_information?.needsEcommerce === true;
        if (lower.includes('booking')) return this.consultation_data?.extracted_information?.needsBooking === true;
        if (lower.includes('phone')) {
            const hasProvisionedPhone = !!this.getPhoneProvisioningStatus()?.has_phone;
            return hasProvisionedPhone || this.consultation_data?.extracted_information?.needsPhoneSystem === true;
        }
        if (lower.includes('email')) return this.consultation_data?.extracted_information?.needsEmailMarketing === true;
        return false;
    }

    getOutstandingDocumentQuestions(parsedDoc = null, extractedOverride = null) {
        const source = extractedOverride || this.consultation_data?.extracted_information || {};
        const qaReview = this.buildDocumentQuestionnaireReview(source);
        const baseDoc = parsedDoc || this.lastParsedDocument || {};
        const missing = Array.isArray(baseDoc.missingItems) ? baseDoc.missingItems : (this.lastDocMissingItems || []);
        this.lastDocMissingItems = this.filterResolvedMissingItems(missing);
        if (this.lastParsedDocument) {
            this.lastParsedDocument = { ...this.lastParsedDocument, missingItems: this.lastDocMissingItems };
        }
        const outstanding = this.collectOutstandingQuestions(qaReview.unansweredQuestions, this.lastDocMissingItems);
        return outstanding;
    }

    async maybeFinalizeDocumentIntake() {
        const flags = this.consultation_data?.phase_flags || {};
        if (flags.phase2_confirmed) return;
        const docMode = this.consultation_data?.doc_locked || this.mode === 'document';
        if (!docMode) return;
        if (!this.lastParsedDocument) return;
        const outstanding = this.getOutstandingDocumentQuestions(
            this.lastParsedDocument,
            this.getDisplayExtractedPayload(this.lastParsedDocument?.extractedInfo)
        );
        if (outstanding.length === 0 && this.hasCriticalKnownAnswers()) {
            await this.autoConfirmDocument(this.lastParsedDocument);
        }
    }

    formatAnswerValue(value) {
        if (typeof value === 'string') {
            const trimmed = value.trim();
            if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
                try {
                    const parsed = JSON.parse(trimmed);
                    return this.formatAnswerValue(parsed);
                } catch (_) {
                    // Fall through to default handling if JSON parse fails
                }
            }
        }
        if (Array.isArray(value)) {
            const cleaned = value
                .map(item => this.formatAnswerValue(item))
                .filter(Boolean);
            return cleaned.length ? cleaned.map(entry => `• ${entry}`).join('\n      ') : '';
        }
        if (this.isPlainObject(value)) {
            const entries = Object.entries(value)
                .map(([key, val]) => {
                    const formatted = this.formatAnswerValue(val);
                    return formatted ? `${this.toSentenceCase(key)}: ${formatted}` : '';
                })
                .filter(Boolean);
            return entries.length ? entries.join('\n      ') : '';
        }
        if (typeof value === 'boolean') {
            return value ? 'Yes' : 'No';
        }
        const cleaned = this.cleanDisplayValue(String(value).trim());
        if (!cleaned || this.isPlaceholderValue(cleaned)) {
            return '';
        }
        return cleaned;
    }

    toSentenceCase(text = '') {
        if (!text) return '';
        const normalized = text.replace(/[_-]+/g, ' ');
        return normalized.charAt(0).toUpperCase() + normalized.slice(1);
    }

    extractAnswerForField(extracted = {}, field) {
        let value = null;
        switch (field) {
            case 'businessName':
                value = extracted.businessName || extracted.business_name;
                break;
            case 'industry':
                value = extracted.industry;
                break;
            case 'businessIdea':
                value = extracted.businessIdea || extracted.businessOverview;
                break;
            case 'offerings':
                value = extracted.offerings;
                break;
            case 'targetAudience':
                value = extracted.targetAudience;
                break;
            case 'location':
                value = extracted.location;
                break;
            case 'businessGoals':
                value = extracted.businessGoals;
                break;
            case 'businessVision':
                value = extracted.businessVision;
                break;
            case 'visionTimeframe':
                value = extracted.visionTimeframe;
                break;
            case 'pricing':
                value = extracted.pricing;
                break;
            case 'websitePreferences':
                value = extracted.websitePreferences;
                break;
            case 'needsEcommerce':
                value = typeof extracted.needsEcommerce === 'boolean'
                    ? extracted.needsEcommerce
                    : extracted.needs?.ecommerce;
                break;
            case 'needsBooking':
                value = typeof extracted.needsBooking === 'boolean'
                    ? extracted.needsBooking
                    : extracted.needs?.booking;
                break;
            case 'needsPhoneSystem':
                value = typeof extracted.needsPhoneSystem === 'boolean'
                    ? extracted.needsPhoneSystem
                    : extracted.needs?.phone_system;
                break;
            case 'needsEmailMarketing':
                value = typeof extracted.needsEmailMarketing === 'boolean'
                    ? extracted.needsEmailMarketing
                    : extracted.needs?.email_marketing;
                break;
            default:
                value = extracted[field];
        }
        const answerText = this.formatAnswerValue(value);
        const hasAnswer = this.hasMeaningfulValue(value);
        return {
            hasAnswer,
            answerText: hasAnswer ? answerText : 'Not captured yet—please provide it.'
        };
    }

    buildDocumentQuestionnaireReview(extracted = {}) {
        const source = this.getDisplayExtractedPayload(extracted);
        const questions = this.getDocumentIntakeQuestions().filter(q => !q.autoRecommend);
        const answered = [];
        const unanswered = [];
        questions.forEach((item, index) => {
            const { hasAnswer, answerText } = this.extractAnswerForField(source, item.field);
            if (!hasAnswer) {
                if (!item.autoRecommend) {
                    unanswered.push(item);
                }
                return;
            }
            const responseBlock = answerText && answerText.includes('\n')
                ? `\n      ${answerText}`
                : ` ${answerText}`;
            answered.push(`${index + 1}. **${item.question}**\n      ${responseBlock.trim()}`);
        });
        const text = [
            '🧠 **Document Intake Review**',
            answered.length
                ? 'Here are the answers I captured directly from your upload:'
                : 'Your document did not include answers to my standard intake questions, so I still need to collect them from you.',
            answered.join('\n\n'),
            unanswered.length
                ? '🔎 **Still Need To Confirm**\nI still need a few more details to complete your blueprint. I will ask these next if they are not covered elsewhere.'
                : '✅ Your document already covers every standard intake question.'
        ].filter(Boolean).join('\n\n');
        return { text, unansweredQuestions: unanswered };
    }

    composeDocumentSummarySections(extracted = {}, parsed = {}) {
        const source = this.getDisplayExtractedPayload(extracted, parsed?.extractedInfo);
        const lines = [];
        lines.push('# Executive Blueprint Summary');
        lines.push('---');
        lines.push('_Professional intake summary generated from your uploaded document and consultation context._');
        lines.push('');
        const industryStageParts = [];
        const industryValue = this.formatAnswerValue(source.industry);
        if (industryValue) {
            industryStageParts.push(industryValue);
        }
        const stageLabel = this.normalizeStageLabel(source.businessStage || this.businessStage);
        if (stageLabel) {
            industryStageParts.push(stageLabel);
        }
        const industryStageDisplay = industryStageParts.length ? industryStageParts.join(' • ') : 'Not specified';
        const summaryText = (() => {
            const raw = parsed?.summary
                || source.conciseSummary
                || this.blueprintSummary
                || this.formatAnswerValue(source.businessOverview)
                || '';
            const cleaned = this.cleanDisplayValue(raw);
            if (/^\{\s*"/m.test(cleaned)) {
                return 'Not provided in document';
            }
            return cleaned || 'Not provided in document';
        })();
        const sections = [
            {
                title: 'Identity Snapshot',
                rows: [
                    ['Business Name', this.formatAnswerValue(source.businessName)],
                    ['Industry & Stage', industryStageDisplay],
                    ['Document Summary', summaryText]
                ]
            },
            {
                title: 'Products & Services',
                rows: [
                    ['Core Offerings', this.formatAnswerValue(source.offerings)],
                    ['Pricing Structure', this.formatAnswerValue(source.pricing) || 'Not provided yet']
                ]
            },
            {
                title: 'Audience & Operations',
                rows: [
                    ['Target Audience', this.formatAnswerValue(source.targetAudience)],
                    ['Location', this.formatAnswerValue(source.location) || 'Not specified'],
                    ['Operations', this.formatAnswerValue(source.operations)]
                ]
            },
            {
                title: 'Goals, Vision & Website Direction',
                rows: [
                    ['Business Goals', this.formatAnswerValue(source.businessGoals)],
                    ['Vision & Timeframe', [this.formatAnswerValue(source.businessVision), this.formatAnswerValue(source.visionTimeframe)].filter(Boolean).join(' • ') || 'Not specified'],
                    ['Website Preferences', this.formatAnswerValue(source.websitePreferences) || 'Not provided']
                ]
            }
        ];

        // ✅ NEW: Add Website Pages section if page descriptions exist
        const pageDescriptions = source.pageDescriptions || {};
        const customPages = source.customPages || [];
        const pageKeys = Object.keys(pageDescriptions);
        
        if (pageKeys.length > 0 || customPages.length > 0) {
            const pageRows = [];
            
            // Add each page description
            pageKeys.forEach(pageName => {
                const desc = pageDescriptions[pageName];
                if (desc && typeof desc === 'string' && desc.trim()) {
                    const displayName = pageName.charAt(0).toUpperCase() + pageName.slice(1).replace(/[-_]/g, ' ');
                    // Truncate long descriptions for display
                    const truncatedDesc = desc.length > 150 ? desc.slice(0, 150) + '...' : desc;
                    pageRows.push([displayName + ' Page', truncatedDesc]);
                }
            });
            
            // Add custom pages list if not already in descriptions
            if (customPages.length > 0) {
                const customPagesNotInDescriptions = customPages.filter(p => !pageKeys.includes(p.toLowerCase()));
                if (customPagesNotInDescriptions.length > 0) {
                    pageRows.push(['Additional Pages', customPagesNotInDescriptions.join(', ')]);
                }
            }
            
            if (pageRows.length > 0) {
                sections.push({
                    title: 'Website Pages & Structure',
                    rows: pageRows
                });
            }
        }
        
        // ✅ NEW: Add Email & Communication section if email preferences exist
        const emailPrefs = source.email_preferences || {};
        const hasEmailPrefs = emailPrefs.business_email || emailPrefs.email_campaign_types?.length > 0 || emailPrefs.email_frequency;
        
        if (hasEmailPrefs) {
            const emailRows = [];
            if (emailPrefs.business_email && emailPrefs.business_email !== 'Not specified') {
                emailRows.push(['Business Email', emailPrefs.business_email]);
            }
            if (emailPrefs.wants_branded_email !== undefined) {
                emailRows.push(['Branded Email', emailPrefs.wants_branded_email ? 'Yes (e.g. sales@yourdomain.com)' : 'No']);
            }
            if (emailPrefs.email_campaign_types && Array.isArray(emailPrefs.email_campaign_types) && emailPrefs.email_campaign_types.length > 0) {
                emailRows.push(['Email Types', emailPrefs.email_campaign_types.join(', ')]);
            }
            if (emailPrefs.email_frequency) {
                emailRows.push(['Email Frequency', emailPrefs.email_frequency]);
            }
            
            if (emailRows.length > 0) {
                sections.push({
                    title: 'Email & Communication',
                    rows: emailRows
                });
            }
        }

        sections.forEach((section, idx) => {
            lines.push(`## ${idx + 1}. ${section.title}`);
            section.rows.forEach(([label, value]) => {
                lines.push(`- **${label}:** ${value || 'Not captured yet'}`);
            });
            lines.push('');
        });

        return lines.join('\n').trim();
    }

    normalizeStageLabel(value) {
        if (!value) {
            return '';
        }
        const trimmed = String(value).trim();
        if (!trimmed || trimmed === 'document_first') {
            return '';
        }
        return this.toSentenceCase(trimmed.replace(/[_-]+/g, ' '));
    }

    getDisplayExtractedPayload(...additionalSources) {
        const sources = [
            this.consultation_data?.extracted_information || {},
            this.lastParsedDocument?.extractedInfo || {},
            ...additionalSources
        ];
        const merged = this.mergeExtractedSources(...sources);
        if (!merged.businessStage && this.businessStage) {
            merged.businessStage = this.businessStage;
        }
        if (merged.businessStage === 'document_first') {
            delete merged.businessStage;
        }
        return merged;
    }

    mergeExtractedSources(...sources) {
        const target = {};
        const assign = (key, val) => {
            if (val === undefined || val === null) {
                return;
            }
            if (Array.isArray(val)) {
                const existing = Array.isArray(target[key]) ? target[key] : [];
                target[key] = Array.from(new Set([...existing, ...val.filter(Boolean)]));
                return;
            }
            if (this.isPlainObject(val)) {
                const existing = this.isPlainObject(target[key]) ? target[key] : {};
                target[key] = { ...existing, ...val };
                return;
            }
            target[key] = val;
        };
        sources.forEach(source => {
            if (!source || typeof source !== 'object') {
                return;
            }
            Object.entries(source).forEach(([key, val]) => assign(key, val));
        });
        return target;
    }

    collectOutstandingQuestions(unansweredQuestions = [], missingItems = []) {
        const outstanding = [];
        unansweredQuestions.forEach(item => {
            if (item?.question) {
                outstanding.push(item.question);
            }
        });
        (missingItems || []).forEach(item => {
            if (typeof item === 'string') {
                const trimmed = item.trim();
                if (trimmed.length && !this.isMissingItemResolved(trimmed)) {
                    outstanding.push(trimmed);
                }
            }
        });
        return Array.from(new Set(outstanding));
    }

    buildOutstandingQuestionObjects(unansweredQuestions = [], missingItems = []) {
        const queue = [];
        const seen = new Set();
        (unansweredQuestions || []).forEach(item => {
            if (!item || !item.question) return;
            const key = item.question.trim().toLowerCase();
            if (seen.has(key)) return;
            seen.add(key);
            queue.push({ field: item.field || null, question: item.question.trim(), type: 'standard' });
        });
        (missingItems || []).forEach(item => {
            if (typeof item !== 'string') return;
            const text = this.cleanDisplayValue(item);
            if (!text) return;
            if (this.isMissingItemResolved(text)) return;
            const key = text.toLowerCase();
            if (seen.has(key)) return;
            seen.add(key);
            const mappedField = this.resolveFieldFromMissingLabel(text);
            const template = mappedField ? this.getDocumentQuestionByField(mappedField) : null;
            const questionText = template?.question || text;
            queue.push({ field: mappedField, question: questionText, type: 'missing' });
        });
        return queue;
    }

    async beginDocumentFollowupFlow(outstandingQueue = [], options = {}) {
        if (!Array.isArray(outstandingQueue) || !outstandingQueue.length) {
            this.documentFollowupActive = false;
            this.pendingDocumentQuestions = [];
            this.totalDocumentFollowups = 0;
            return;
        }
        if (options?.forceRestart) {
            this.documentFollowupActive = false;
        }
        if (this.documentFollowupActive) {
            return;
        }
        // ✅ CAP: Maximum 3 follow-up questions to prevent frustrating loops
        const MAX_FOLLOWUP_QUESTIONS = 3;
        const cappedQueue = outstandingQueue.slice(0, MAX_FOLLOWUP_QUESTIONS);
        this.pendingDocumentQuestions = cappedQueue;
        this.totalDocumentFollowups = cappedQueue.length;
        this.awaitingDocumentAnswer = null;
        this.documentFollowupActive = true;
        const skipIntro = options?.skipIntro === true;
        if (!skipIntro) {
            const intro = 'To capture your full context, I just need a few more quick details. Let’s walk through them together.';
            this.addMessageToUI('ai', intro);
            await this.saveMessageToDatabase('ai', intro);
        }
        const delayMs = Number(options?.delayMs) || 0;
        if (delayMs > 0) {
            await this.delay(delayMs);
        }
        await this.askNextDocumentQuestion();
    }

    async askNextDocumentQuestion() {
        if (!this.pendingDocumentQuestions.length) {
            this.awaitingDocumentAnswer = null;
            this.documentFollowupActive = false;
            return;
        }
        const next = this.pendingDocumentQuestions[0];
        this.awaitingDocumentAnswer = next;
        const prompt = `${this.cleanDisplayValue(next.question)}\n(Share it in your own words—I'll take notes for you.)`;
        this.addMessageToUI('ai', prompt);
        await this.saveMessageToDatabase('ai', prompt);
    }

    parseBooleanResponse(input = '') {
        if (typeof input !== 'string') {
            return null;
        }
        const normalized = input.trim().toLowerCase();
        if (!normalized) return null;
        if (/^(yes|yep|y|sure|of course|absolutely|definitely|please do|affirmative|correct)$/i.test(normalized)) {
            return true;
        }
        if (/^(no|nope|n|not really|negative|nah|not at the moment|don’t|dont|do not)$/i.test(normalized)) {
            return false;
        }
        return null;
    }

    normalizeAnswerList(raw = '') {
        if (Array.isArray(raw)) {
            return raw.map(entry => this.cleanDisplayValue(String(entry))).filter(Boolean);
        }
        if (typeof raw !== 'string') {
            return [];
        }
        return raw
            .split(/\n+|[,•;]/)
            .map(part => this.cleanDisplayValue(part))
            .filter(Boolean);
    }

    setAnswerForField(field, rawValue) {
        if (!field) return { value: rawValue, autoGenerated: false };
        const info = this.consultation_data.extracted_information || {};
        let value = typeof rawValue === 'string' ? rawValue.trim() : rawValue;
        const providedMeaningful = this.hasMeaningfulValue(value);
        const result = { value, autoGenerated: false };
        if (typeof value === 'string' && !providedMeaningful) {
            value = '';
        }
        const allowEmpty = field === 'visionTimeframe';
        if (!providedMeaningful && !allowEmpty) {
            return result;
        }
        const assign = (key, val) => {
            if (!key) return;
            result.value = val;
            info[key] = val;
            this.collectedInformation[key] = info[key];
            if (this.lastParsedDocument && this.isPlainObject(this.lastParsedDocument.extractedInfo)) {
                this.lastParsedDocument.extractedInfo[key] = val;
            }
        };

        switch (field) {
            case 'businessName':
                assign('businessName', value);
                break;
            case 'industry':
                assign('industry', value);
                break;
            case 'businessIdea':
                assign('businessIdea', value);
                if (value) assign('businessOverview', value);
                break;
            case 'offerings':
                assign('offerings', this.normalizeAnswerList(value));
                break;
            case 'targetAudience':
                assign('targetAudience', value);
                break;
            case 'location':
                assign('location', value);
                break;
            case 'businessGoals':
                assign('businessGoals', value);
                break;
            case 'businessVision':
                assign('businessVision', value);
                break;
            case 'visionTimeframe':
                {
                    let resolved = value;
                    const wantsAuto = typeof value === 'string' && this.detectAutoTimeframeRequest(value);
                    if (!this.hasMeaningfulValue(resolved) || wantsAuto) {
                        const derived = this.deriveVisionTimeframeFromGoals(this.consultation_data.extracted_information, this.blueprintRaw || '');
                        if (this.hasMeaningfulValue(derived)) {
                            resolved = derived;
                            result.autoGenerated = true;
                        }
                    }
                    assign('visionTimeframe', resolved || value || '');
                }
                break;
            case 'pricing':
                assign('pricing', this.normalizeAnswerList(value));
                break;
            case 'websitePreferences':
                assign('websitePreferences', value);
                break;
            case 'needsEcommerce':
                {
                    const boolVal = this.parseBooleanResponse(value);
                    assign('needsEcommerce', boolVal !== null ? boolVal : value);
                }
                break;
            case 'needsBooking':
                {
                    const boolVal = this.parseBooleanResponse(value);
                    assign('needsBooking', boolVal !== null ? boolVal : value);
                }
                break;
            case 'needsPhoneSystem':
                {
                    const boolVal = this.parseBooleanResponse(value);
                    assign('needsPhoneSystem', boolVal !== null ? boolVal : value);
                }
                break;
            case 'needsEmailMarketing':
                {
                    const boolVal = this.parseBooleanResponse(value);
                    assign('needsEmailMarketing', boolVal !== null ? boolVal : value);
                }
                break;
            default:
                assign(field, value);
        }

        if (Array.isArray(this.lastDocMissingItems) && this.lastDocMissingItems.length) {
            this.lastDocMissingItems = this.filterResolvedMissingItems(this.lastDocMissingItems);
        }
        if (this.lastParsedDocument) {
            this.lastParsedDocument.missingItems = this.lastDocMissingItems;
        }
        this.enforceSystemRecommendations();
        return result;
    }

    async recordDocumentAnswer(answerText) {
        if (!this.pendingDocumentQuestions.length) {
            return false;
        }
        const current = this.awaitingDocumentAnswer || this.pendingDocumentQuestions[0];

        const cleanedAnswer = this.cleanDisplayValue(answerText || '');
        if (!this.isMeaningfulAnswerForField(current.field, cleanedAnswer)) {
            const fieldLabel = current.field ? `\`${current.field}\`` : 'this';
            const booleanFields = ['needsEcommerce','needsBooking','needsPhoneSystem','needsEmailMarketing'];
            const nudge = current.field && booleanFields.includes(current.field)
                ? `Just to confirm ${fieldLabel}: please reply **yes** or **no**.`
                : `I didn\'t catch a clear answer for ${fieldLabel}. Could you share a bit more detail?`;
            this.addMessageToUI('ai', nudge);
            await this.saveMessageToDatabase('ai', nudge);
            await this.askNextDocumentQuestion();
            return false;
        }

        const assignment = this.setAnswerForField(current.field, answerText);
        if (current.field === 'visionTimeframe' && assignment?.autoGenerated) {
            const reassurance = '⌛ Got it—I’ll calculate the launch timeline for you so you can stay focused on the big picture.';
            this.addMessageToUI('ai', reassurance);
            await this.saveMessageToDatabase('ai', reassurance);
        }
        this.awaitingDocumentAnswer = null;
        this.pendingDocumentQuestions.shift();
        await this.persistExtractedInformation();
        if (!this.pendingDocumentQuestions.length) {
            const closing = '✅ Thanks, that completes the info I needed. Saving this to your blueprint now...';
            this.addMessageToUI('ai', closing);
            await this.saveMessageToDatabase('ai', closing);
            this.totalDocumentFollowups = 0;
            this.documentFollowupActive = false;
            const latestDoc = this.lastParsedDocument || { extractedInfo: this.getDisplayExtractedPayload(), missingItems: [], isComplete: false };
            await this.autoConfirmDocument(latestDoc);
        } else {
            await this.askNextDocumentQuestion();
        }
        return true;
    }

    isMeaningfulAnswerForField(field, answerText = '') {
        const cleaned = this.normalizeUserUtterance(answerText);
        if (!cleaned) return false;
        if (this.isLowSignalUtterance(cleaned)) return false;

        const booleanFields = new Set(['needsEcommerce','needsBooking','needsPhoneSystem','needsEmailMarketing']);
        if (booleanFields.has(field)) {
            return this.parseBooleanResponse(cleaned) !== null;
        }
        if (field === 'businessName') {
            return cleaned.length >= 2 && /[a-z]/i.test(cleaned) && !/^my (business|company|brand)$/i.test(cleaned);
        }
        if (field === 'industry') {
            return cleaned.length >= 3 && /[a-z]/i.test(cleaned);
        }
        if (field === 'offerings' || field === 'pricing') {
            return this.isExtractionEligible(cleaned) || /,|\n|•|\//.test(cleaned);
        }
        return this.isExtractionEligible(cleaned);
    }

    async persistExtractedInformation() {
        if (!window.vmo_consultant_context_v1) {
            return;
        }
        try {
            await this.saveConsultationData({
                extracted_information: this._toCanonicalExtracted()
            });
        } catch (e) {
            console.warn('Unable to persist extracted information', e);
        }
    }

    buildOutstandingQuestionsPrompt(outstanding = []) {
        if (!outstanding || outstanding.length === 0) {
            return '';
        }
        // ✅ FIX: No more "one at a time" language — batch questions together
        return '🔎 I have a couple of quick clarifications to fill in the gaps. Let me ask them together so we can keep moving.';
    }

    composeDocumentSummaryPayload(parsed = {}) {
        const extracted = this.getDisplayExtractedPayload(parsed?.extractedInfo);
        const summarySections = this.composeDocumentSummarySections(extracted, parsed);
        const qaReview = this.buildDocumentQuestionnaireReview(extracted);
        return { extracted, summarySections, qaReview };
    }

    buildDocumentSummaryMessage(parsed = {}) {
        const { summarySections, qaReview } = this.composeDocumentSummaryPayload(parsed);
        const parts = [
            '📄 **Document Intake Summary**',
            summarySections,
            qaReview.text
        ].filter(Boolean);
        return parts.join('\n\n').trim();
    }

    async autoConfirmDocument(parsedDoc = {}) {
        this.storeDocumentParseState(parsedDoc);
        const { extracted, summarySections, qaReview } = this.composeDocumentSummaryPayload(parsedDoc);
        const outstandingQuestions = this.getOutstandingDocumentQuestions(parsedDoc, extracted);
        const phaseFlags = this.consultation_data?.phase_flags || {};
        const summarySignature = JSON.stringify({
            docKey: parsedDoc?.documentId || this.lastParsedDocument?.documentId || null,
            businessName: extracted.businessName || '',
            industry: extracted.industry || '',
            missing: outstandingQuestions
        });
        const summary = [
            '📄 **Document Intake Summary**',
            summarySections,
            qaReview.text
        ].filter(Boolean).join('\n\n').trim();
        const summaryAlreadyShown = this.lastDocumentSummarySignature === summarySignature;

        if (summaryAlreadyShown && phaseFlags.phase2_summary_presented && this.consultation_data?.doc_locked && outstandingQuestions.length === 0) {
            console.log('📄 Phase 2 summary already presented for this document—skipping duplicate prompt.');
            return;
        }

        if (!summaryAlreadyShown) {
            this.addMessageToUI('ai', summary);
            await this.saveMessageToDatabase('ai', summary);
            this.lastDocumentSummaryDocId = parsedDoc?.documentId || this.lastParsedDocument?.documentId || null;
            this.lastDocumentSummarySignature = summarySignature;
        }

        if (outstandingQuestions.length) {
            const followUp = this.buildOutstandingQuestionsPrompt(outstandingQuestions);
            if (followUp) {
                this.addMessageToUI('ai', followUp);
                await this.saveMessageToDatabase('ai', followUp);
            }
            const outstandingQueue = this.buildOutstandingQuestionObjects(
                qaReview.unansweredQuestions,
                parsedDoc?.missingItems || this.lastDocMissingItems || []
            );
            if (outstandingQueue.length) {
                await this.beginDocumentFollowupFlow(outstandingQueue, { skipIntro: true, delayMs: 800 });
            }
            return;
        }

        // No outstanding questions — lock document context and present confirmation to user.
        // ✅ FIX: Do NOT auto-continue. Show the summary and WAIT for the user to confirm
        // their blueprint before proceeding to deep research. The old code sent a hidden
        // prompt that said "Do NOT ask me to review" which bypassed user confirmation entirely.
        this.consultationTopics.forEach(topic => this.topicsCovered.add(topic));

        await this.lockDocumentContext();
        await this.ensurePhaseAtLeast(1);
        if (!phaseFlags.phase2_summary_presented) {
            try { this.updatePhaseFlag('phase2_summary_presented', true); } catch (_) {}
        }

        // ✅ FIX: Ask the user to confirm their blueprint before proceeding.
        // They can review the summary above and say "yes" / click confirm to proceed.
        this.addMessageToUI('ai', `✅ **Your business blueprint has been analyzed and locked in!**\n\nPlease take a moment to review the summary above. If everything looks good, just say **"looks good"** or **"proceed"** and I\'ll begin the comprehensive deep research phase.\n\nIf you\'d like to make any changes or add more details, just let me know before we proceed.`);
        await this.saveMessageToDatabase('ai', 'Blueprint analyzed and locked. Waiting for user confirmation before proceeding to deep research.');

        // ✅ Voice mode: Speak the confirmation prompt
        if (this.isVoiceEnabled && typeof this.speak === 'function') {
            try {
                this.speak('Your business blueprint has been analyzed and locked in. Please review the summary above. If everything looks good, say looks good or proceed and I will begin the deep research phase. If you want to make any changes, just let me know.');
            } catch (_) {}
        }
    }
    async ensurePhaseAtLeast(targetIndex = 0) {
        const flow = this.consultation_data.consultation_flow;
        if (!flow || typeof flow.current_index === 'undefined') {
            console.warn('ensurePhaseAtLeast: flow not initialised, skipping');
            return;
        }
        let safety = 0;
        const MAX_ITERATIONS = 20; // hard limit — 8 phases, so 20 is more than enough
        while (flow.current_index < targetIndex && safety++ < MAX_ITERATIONS) {
            // If advancePhase is already running, wait for it to finish
            // instead of calling it (which would just return immediately
            // without incrementing — causing an infinite tight loop).
            if (this._advancePhaseInProgress) {
                await new Promise(r => setTimeout(r, 250));
                continue;
            }
            const before = flow.current_index;
            await this.advancePhase();
            // If the phase didn't actually advance, stop to prevent spinning
            if (flow.current_index <= before) {
                console.warn(`ensurePhaseAtLeast: phase stuck at ${before}, aborting loop`);
                break;
            }
        }
    }

    async lockDocumentContext() {
        try {
            const AX = this.getAjax();
            await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_update_consultation_context',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    session_id: this.sessionId,
                    context_action: 'set_doc_locked',
                    value: '1'
                })
            });
            this.consultation_data.doc_locked = true;
            this.refreshAllowedActions();
        } catch (_) {
            // Non-blocking
        }
    }
    
    // Check if critical business information is already known (context gate)
    hasCriticalKnownAnswers() {
        const ex = this.consultation_data.extracted_information;
        const required = ['businessName', 'industry', 'targetAudience'];
        const hasOfferings = Array.isArray(ex.offerings) && ex.offerings.length > 0;
        const hasCore = required.every(k => ex[k] && String(ex[k]).trim().length > 0);
        return hasCore && hasOfferings;
    }

    // Convert local state into canonical payload shapes expected by backend merger
    _toCanonicalExtracted() {
        const c = this.collectedInformation || {};
        return {
            business_idea: c.businessIdea || null,
            business_name: c.businessName || null,
            industry: c.industry || null,
            target_audience: c.targetAudience || null,
            offering_type: c.offeringType || null,
            offerings: Array.isArray(c.offerings) ? c.offerings : [],
            business_goals: c.businessGoals || null,
            success_definition: c.successDefinition || null,
            scale_intent: c.scaleIntent || null,
            automation_preference: c.automationPreference || null,
            website_preferences: c.websitePreferences || null,
            branding_info: c.brandingInfo || null,
            location: c.location || null,
            businessIdea: c.businessIdea || null,
            businessName: c.businessName || null,
            targetAudience: c.targetAudience || null,
            offeringType: c.offeringType || null,
            businessGoals: c.businessGoals || null,
            successDefinition: c.successDefinition || null,
            scaleIntent: c.scaleIntent || null,
            automationPreference: c.automationPreference || null,
            websitePreferences: c.websitePreferences || null,
            brandingInfo: c.brandingInfo || null,
            payment_collection: (c.payment_collection && typeof c.payment_collection === 'object') ? c.payment_collection : {
                collects_payments: null,
                mode: null,
                confirmed: false
            },
            ecommerce: (c.ecommerce && typeof c.ecommerce === 'object') ? c.ecommerce : {
                collection_mode: null,
                donation_funds: []
            },
            products_inventory: Array.isArray(c.products_inventory) ? c.products_inventory : [],
            services_inventory: Array.isArray(c.services_inventory) ? c.services_inventory : [],
            needs: {
                ecommerce: !!c.needsEcommerce,
                booking: !!c.needsBooking,
                phone_system: !!c.needsPhoneSystem,
                email_marketing: !!c.needsEmailMarketing
            }
        };
    }

    _toCanonicalMediaFiles() {
        const files = Array.isArray(this.collectedInformation.uploadedFiles) ? this.collectedInformation.uploadedFiles : [];
        const roles = Array.isArray(this.collectedInformation.mediaRoles) ? this.collectedInformation.mediaRoles : [];
        const roleMap = new Map();
        roles.forEach(r => {
            if (!r || !r.url) return;
            if (!roleMap.has(r.url)) roleMap.set(r.url, []);
            roleMap.get(r.url).push(r.role);
        });
        return files
            .filter(f => this.isVisualMedia(f))
            .map(f => ({
            id: f.id || undefined,
            url: f.url,
            title: f.title || f.filename || '',
            type: f.type || '',
            roles: roleMap.get(f.url) || []
        }));
    }

    // Save canonical consultation_data to backend (additive merge)
    // ✅ FIX: Debounce overlapping saves to prevent race conditions.
    // Multiple rapid calls (e.g., during phase transitions) are coalesced
    // into a single request using the latest payload.
    async saveConsultationData(canonical = null) {
        if (!window.vmo_consultant_context_v1) return;

        // Merge incoming canonical into the pending payload
        const incoming = canonical && typeof canonical === 'object' && Object.keys(canonical).length > 0
            ? canonical
            : null;
        if (incoming) {
            this._pendingSavePayload = Object.assign(this._pendingSavePayload || {}, incoming);
        }

        // If a save is already scheduled, let it pick up the merged payload
        if (this._saveDebounceTimer) return;

        // Debounce: wait 300ms to coalesce rapid-fire calls
        this._saveDebounceTimer = setTimeout(async () => {
            this._saveDebounceTimer = null;
            const pendingPayload = this._pendingSavePayload;
            this._pendingSavePayload = null;

            // If another save is in-flight, abort it (last-write-wins intentionally)
            if (this._saveAbortController) {
                try { this._saveAbortController.abort(); } catch (_) {}
            }

            // ✅ FIX: Declare timer OUTSIDE try/catch so it's accessible in catch block
            let timer = null;
            try {
                const AX = this.getAjax();
                const basePayload = pendingPayload || (this.state || {});
                const payload = this.buildSavePayload(basePayload);
                const controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
                this._saveAbortController = controller;
                const timeoutMs = 12000;
                if (controller) {
                    timer = setTimeout(() => {
                        try { controller.abort(); } catch (_) {}
                    }, timeoutMs);
                }

                await fetch(AX.url, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    signal: controller ? controller.signal : undefined,
                    body: new URLSearchParams({
                        action: 'vmo_update_consultation_context',
                        security: AX.nonce,
                        nonce: AX.nonce,
                        _ajax_nonce: AX.nonce,
                        session_id: this.sessionId,
                        context_action: 'save_consultation_data',
                        consultation_data: JSON.stringify(payload || {})
                    })
                });

                if (timer) clearTimeout(timer);
                this._saveRetryCount = 0; // Reset on success
            } catch (e) {
                // ✅ MEMORY SAFETY: Retry with exponential backoff to prevent silent data loss.
                // The user's consultation data (including their business vision, uploaded documents,
                // and conversation progress) MUST be persisted. Silent failures are unacceptable.
                if (timer) clearTimeout(timer);
                const retryCount = (this._saveRetryCount || 0) + 1;
                this._saveRetryCount = retryCount;
                const maxRetries = 3;
                if (retryCount <= maxRetries && !(e && e.name === 'AbortError')) {
                    const backoffMs = Math.min(1000 * Math.pow(2, retryCount - 1), 8000);
                    console.warn(`⚠️ VMO: Save failed (attempt ${retryCount}/${maxRetries}), retrying in ${backoffMs}ms...`, e?.message || e);
                    setTimeout(() => {
                        this._pendingSavePayload = payload;
                        this.saveConsultationData();
                    }, backoffMs);
                } else if (retryCount > maxRetries) {
                    console.error('❌ VMO: Save failed after', maxRetries, 'retries. Data may not be persisted.', e?.message || e);
                    // Surface a non-blocking warning to the user
                    try {
                        if (typeof this.showNotification === 'function') {
                            this.showNotification('Your progress may not have been saved. Please check your internet connection.', 'warning');
                        }
                    } catch (_) {}
                    this._saveRetryCount = 0;
                }
            } finally {
                this._saveAbortController = null;
            }
        }, 300);
    }

    buildSavePayload(basePayload = {}) {
        let payload = basePayload;
        if (basePayload === this.state) {
            payload = JSON.parse(JSON.stringify(basePayload));
        } else if (typeof basePayload === 'object') {
            payload = JSON.parse(JSON.stringify(basePayload || {}));
        } else {
            payload = {};
        }
        const docStore = this.buildDocumentStorePayload();
        const hasDocStoreContent = docStore.blueprint_raw || docStore.blueprint_summary || (docStore.uploads && docStore.uploads.length);
        if (hasDocStoreContent && !payload.document_store) {
            payload.document_store = docStore;
        }
        if (!payload.documents && Array.isArray(this.consultation_data.documents) && this.consultation_data.documents.length) {
            payload.documents = this.consultation_data.documents;
        }
        if (this.blueprintRaw && !payload.blueprintRaw) {
            payload.blueprintRaw = this.blueprintRaw;
        }
        if (this.blueprintSummary && !payload.blueprintSummary) {
            payload.blueprintSummary = this.blueprintSummary;
        }
        
        // ✅ CRITICAL FIX: Always include consultation_flow for phase persistence
        if (!payload.consultation_flow && this.consultation_data.consultation_flow) {
            payload.consultation_flow = {
                phases: this.consultation_data.consultation_flow.phases,
                current_index: this.consultation_data.consultation_flow.current_index,
                current_phase_index: this.consultation_data.consultation_flow.current_index, // Backend uses both
                status: this.consultation_data.consultation_flow.status,
                timestamps: this.consultation_data.consultation_flow.timestamps
            };
            console.log('📤 Saving consultation_flow with current_index=' + payload.consultation_flow.current_index);
        }
        
        return payload;
    }

    // ✅ FIX: Trigger Website Generation with CHUNKED approach to prevent timeouts
    // Uses the same 12-chunk approach as full business generation for reliability
    async triggerWebsiteGeneration() {
        console.log('🌐 Phase 8: Triggering CHUNKED Website Generation...');
        
        // ✅ CHUNKED: Delegate to the full business generation which already has retry/timeout handling
        // This prevents the nginx 504 timeout issues that occur with single large requests
        try {
            await this.triggerFullBusinessGeneration();
        } catch (error) {
            console.error('Website Generation Error (chunked):', error);
            
            // ✅ FALLBACK: Try quick preview if full generation fails
            this.addMessageToUI('ai', '⏳ Full generation encountered an issue. Generating a quick preview...');
            await this.triggerQuickWebsitePreview();
        }
    }
    
    // ✅ Quick preview fallback (single call, smaller scope - for when full generation fails)
    async triggerQuickWebsitePreview() {
        console.log('🌐 Quick Preview: Generating simplified website preview...');
        
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_generate_website_preview',
                    security: AX.nonce,
                    session_id: this.sessionId,
                    consultation_data: JSON.stringify(this.consultation_data),
                    tech_stack: 'nextjs_react_tailwind_gsap_threejs'
                })
            });
            
            const data = await response.json();
            if (data.success && data.data?.html) {
                let previewHtml = data.data.html;
                
                // ✅ GrapesJS: If adapter returned strategy plan, compose premium output
                if (data.data.grapes_plan && window.vmoGrapesAdapter && typeof window.vmoGrapesAdapter.isReady === 'function' && window.vmoGrapesAdapter.isReady()) {
                    try {
                        const composed = window.vmoGrapesAdapter.composeWebsite(data.data.grapes_plan);
                        if (composed && composed.ok) previewHtml = composed.html;
                    } catch (e) { console.warn('[VMO] GrapesJS compose skipped for quick preview:', e); }
                }
                
                const iframe = document.getElementById('vmo-preview-iframe');
                if (iframe) {
                    const fullHtml = window.wrapIframeContent ? window.wrapIframeContent(previewHtml) : previewHtml;
                    iframe.srcdoc = fullHtml;
                    
                    if (window.vmoState) {
                        window.vmoState.blocks = []; 
                    }
                    window.lastContent = fullHtml;

                    const mediaResult = await this.resolvePreviewMedia('consultation_preview');
                    this.syncPreviewHtmlFromIframe();

                    this.addMessageToUI('ai', (!mediaResult || mediaResult.complete !== false)
                        ? '✨ Quick preview ready! Check the preview panel. For the full website with all pages, you can run the generation again.'
                        : '⌛ The quick preview layout is visible, but required media is still incomplete. The preview is not ready yet.');
                }
            } else {
                throw new Error(data.data?.message || 'Preview failed');
            }
        } catch (error) {
            console.error('Quick Preview Error:', error);
            this.addMessageToUI('ai', 'I apologize, but I couldn\'t generate the preview. Please try the "Generate Website" button in the toolbar.');
        }
    }
    
    // ✅ NEW: Populate Deliverables - Generate all business deliverables in chunks
    async populateDeliverables() {
        console.log('📊 Phase 8: Populating Deliverables...');
        
        const businessName = this.consultation_data?.extracted_information?.business_name || 'Your Business';
        
        // Show progress indicator
        this.addMessageToUI('ai', `📦 Preparing your complete business package for **${businessName}**...

I'm now generating:
• 📋 Business roadmap & milestones
• 📅 Daily task schedules

This will take a moment for thoroughness.`);
        
        try {
            const AX = this.getAjax();
            
            // ✅ CHUNKED DELIVERABLES: Generate in smaller chunks to prevent timeouts
            // Note: Legal docs and email templates are generated during full website generation (chunk 10)
            const deliverableChunks = [
                { action: 'vmo_generate_business_roadmap', label: '🗺️ Generating business roadmap...', key: 'roadmap' },
                { action: 'vmo_generate_daily_activities', label: '📅 Creating daily task plan...', key: 'daily_tasks' }
            ];
            
            const results = {};
            
            for (let i = 0; i < deliverableChunks.length; i++) {
                const chunk = deliverableChunks[i];
                
                // Update progress
                this.addMessageToUI('system', chunk.label);
                
                // ✅ BULLETPROOF: Retry logic with exponential backoff
                let success = false;
                let retries = 0;
                const MAX_RETRIES = 3;
                
                while (!success && retries < MAX_RETRIES) {
                    try {
                        const response = await fetch(AX.url, {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                            body: new URLSearchParams({
                                action: chunk.action,
                                security: AX.nonce,
                                session_id: this.sessionId,
                                consultation_data: JSON.stringify(this.consultation_data)
                            })
                        });
                        
                        if (!response.ok) {
                            throw new Error(`HTTP ${response.status}`);
                        }
                        
                        const data = await response.json();
                        if (data.success) {
                            results[chunk.key] = data.data;
                            console.log(`✅ Deliverable chunk ${i + 1}/${deliverableChunks.length} complete: ${chunk.key}`);
                            success = true;
                        } else {
                            throw new Error(data.data?.message || 'Request failed');
                        }
                    } catch (chunkError) {
                        retries++;
                        console.warn(`⚠️ Deliverable ${chunk.key} attempt ${retries}/${MAX_RETRIES} failed:`, chunkError);
                        
                        if (retries < MAX_RETRIES) {
                            // Exponential backoff: 2s, 4s, 8s
                            const backoffMs = 2000 * Math.pow(2, retries - 1);
                            await new Promise(resolve => setTimeout(resolve, backoffMs));
                        }
                    }
                }
                
                // Small delay between chunks to prevent rate limiting
                if (i < deliverableChunks.length - 1) {
                    await new Promise(resolve => setTimeout(resolve, 500));
                }
            }
            
            // ✅ Update state with generated deliverables
            if (results.roadmap) {
                this.state.roadmap_json = results.roadmap;
            }
            if (results.daily_tasks) {
                this.state.daily_tasks_json = results.daily_tasks;
            }
            
            // ✅ Mark deliverables as generated
            if (!this.state.approvals) this.state.approvals = {};
            this.state.approvals.artifacts_generated = true;
            
            // ✅ Refresh the right panel to show updated deliverables
            this.refreshPhaseUI();
            
            // Success message
            const successItems = Object.keys(results).length;
            if (successItems > 0) {
                this.addMessageToUI('ai', `✅ **Deliverables Generated!**

I've prepared ${successItems} business documents for you:
${results.roadmap ? '• ✅ Business Roadmap with milestones\n' : ''}${results.daily_tasks ? '• ✅ Daily execution plan\n' : ''}
You can access these in the **Deliverables** section on the right panel, or download them from your dashboard.

**Note:** Legal documents (Privacy Policy, Terms of Service) will be generated when you build your complete website.`);
            } else {
                this.addMessageToUI('ai', '⚠️ I couldn\'t generate the deliverables right now. You can generate them from your dashboard after the consultation.');
            }
            
        } catch (error) {
            console.error('Deliverables Population Error:', error);
            this.addMessageToUI('ai', '⚠️ Some deliverables couldn\'t be generated. You can generate them individually from your dashboard.');
        }
    }

    /**
     * ✅ Show Research Confirmation - Ask user before starting deep research
     * This gives the user a chance to add more information or confirm they're ready
     * VOICE SUPPORT: If user is in voice mode, speaks the confirmation and listens for voice confirmation
     */
    showResearchConfirmation() {
        const businessName = this.consultation_data?.extracted_information?.business_name || 'your business';
        const targetMarket = this.consultation_data?.extracted_information?.target_market || 'your target market';
        
        // ✅ Flag to track that we're awaiting research confirmation
        this._awaitingResearchConfirmation = true;
        
        // Request notification permission early
        if ('Notification' in window && Notification.permission === 'default') {
            Notification.requestPermission();
        }
        
        // Create confirmation message with action buttons
        const confirmationHtml = `
            <div class="vmo-research-confirmation">
                <h3>🔍 Ready to Begin COMPREHENSIVE Deep Research?</h3>
                <p>I've gathered the information about <strong>${businessName}</strong>. I'm now ready to conduct our <strong>Phase 3 Comprehensive Competitive Intelligence</strong> across 8 research phases:</p>
                <ul>
                    <li>🏢 Core Business Foundation & Target Audience</li>
                    <li>🔎 Competitor Discovery (10-12 key players)</li>
                    <li>📊 Individual Competitor Business Model Analysis</li>
                    <li>🌐 Competitor Website & UX Analysis</li>
                    <li>📈 Competitor SEO Benchmarking</li>
                    <li>💰 Market Trends & Pricing Strategy</li>
                    <li>⚙️ Technology, Legal & Compliance</li>
                    <li>🎯 Comparative Matrix & Strategic Advantages</li>
                </ul>
                <p style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 12px; border-radius: 8px; border-left: 4px solid #00d4ff; margin: 15px 0;">
                    <strong style="color: #ffd93d;">⏱️ Time Estimate:</strong> This comprehensive research takes <strong style="color: #00ff88;">15-45 minutes</strong> depending on your industry complexity. 
                    <br><br>
                    <strong>💡 Tip:</strong> You can leave this tab open and do other things. I'll send you a <strong>browser notification with a sound</strong> when the research is complete and your recommendations are ready!
                    ${this.isVoiceEnabled ? '<br><br><strong style="color: #00d4ff;">🎤 Voice Mode:</strong> Just say <em>"yes, start the research"</em> or <em>"go ahead"</em> to begin! Or <strong style="color: #ffd93d;">close the voice modal</strong> to click the button below.' : ''}
                </p>
                <div class="vmo-confirmation-actions">
                    <button type="button" class="vmo-btn-confirm-research vmo-btn-primary">
                        ✅ Yes, Start Comprehensive Research
                    </button>
                    <button type="button" class="vmo-btn-add-more-info vmo-btn-secondary">
                        ✏️ Wait, I Want to Add More Info
                    </button>
                </div>
            </div>
        `;
        
        this.addNodeMessageToUI('ai', confirmationHtml);
        this.saveMessageToDatabase('ai', `Ready to begin comprehensive deep research for ${businessName}. Waiting for user confirmation.`);
        
        // ✅ VOICE MODE: Speak the confirmation and listen for voice response
        // Also tell user they can close the modal to see the button
        if (this.isVoiceEnabled) {
            const voiceMessage = `I'm ready to begin the comprehensive deep research for ${businessName}. This will analyze your market, competitors, and opportunities. It takes about 15 to 45 minutes. You can say "yes, start the research" or "go ahead" to begin. You can also close this voice conversation window to see and click the start button in the chat.`;
            this.speak(voiceMessage);
        }
        
        // Attach event listeners after DOM update
        setTimeout(() => {
            const confirmBtn = document.querySelector('.vmo-btn-confirm-research');
            const addMoreBtn = document.querySelector('.vmo-btn-add-more-info');
            
            if (confirmBtn) {
                confirmBtn.addEventListener('click', async () => {
                    await this.confirmAndStartDeepResearch(confirmBtn, addMoreBtn);
                });
            }
            
            if (addMoreBtn) {
                addMoreBtn.addEventListener('click', () => {
                    this._awaitingResearchConfirmation = false;
                    this.addMessageToUI('ai', "📝 No problem! Please share any additional information you'd like me to consider before I start the research. You can also upload more documents if needed.");
                    this.saveMessageToDatabase('ai', 'User requested to add more information before research.');
                    
                    // Remove the confirmation buttons
                    const confirmSection = document.querySelector('.vmo-research-confirmation');
                    if (confirmSection) {
                        const actionsDiv = confirmSection.querySelector('.vmo-confirmation-actions');
                        if (actionsDiv) actionsDiv.remove();
                    }
                });
            }
        }, 100);
    }
    
    /**
     * ✅ Helper: Confirm and start deep research (used by both button and voice)
     */
    async confirmAndStartDeepResearch(confirmBtn = null, addMoreBtn = null) {
        // ✅ FIX: Immediately stop any currently playing speech (e.g. the long confirmation voice)
        // This prevents the confirmation voice from overlapping with the "Starting research" voice
        try { this.stopSpeaking(); } catch(_) {}
        try { this._progressiveTTSAbort(); } catch(_) {}
        try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_) {}

        // Clear the confirmation flag
        this._awaitingResearchConfirmation = false;

        // ✅ The user clicked "Start Research" — this IS the Phase 2 confirmation.
        // Mark phase2_confirmed immediately so we never re-ask.
        if (!this.consultation_data.phase_flags) {
            this.consultation_data.phase_flags = {};
        }
        this.consultation_data.phase_flags.phase2_confirmed = true;
        this.phase3ResearchRequested = true; // Set BEFORE ensurePhaseAtLeast to prevent duplicate confirmation UI

        // ✅ CRITICAL FIX: Save phase2_confirmed directly to the server via a NON-debounced fetch.
        // saveConsultationData() has a 300ms debounce, which causes a race condition:
        // triggerDeepResearch fires before the flag is persisted → backend 422 → double confirmation.
        try {
            const AX = this.getAjax();
            await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_update_consultation_context',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    session_id: this.sessionId,
                    context_action: 'save_consultation_data',
                    consultation_data: JSON.stringify({ phase_flags: this.consultation_data.phase_flags })
                })
            });
            console.log('✅ phase2_confirmed flag persisted to server');
        } catch (e) {
            console.warn('⚠️ phase2_confirmed direct save failed (will retry via debounce):', e?.message);
            // Fallback: queue through the debounced path so it eventually saves
            try { this.saveConsultationData({ phase_flags: this.consultation_data.phase_flags }); } catch (_) {}
        }

        // ✅ Ensure we're at Phase 3 (index 2) before starting research
        try { await this.ensurePhaseAtLeast(2); } catch (_) {}
        
        if (confirmBtn) {
            confirmBtn.disabled = true;
            confirmBtn.textContent = 'Starting comprehensive research...';
        }
        if (addMoreBtn) {
            addMoreBtn.style.display = 'none';
        }
        
        // Remove any confirmation buttons from the UI
        const confirmSection = document.querySelector('.vmo-research-confirmation');
        if (confirmSection) {
            const actionsDiv = confirmSection.querySelector('.vmo-confirmation-actions');
            if (actionsDiv) actionsDiv.remove();
        }
        
        this.addMessageToUI('ai', "✅ Perfect! Starting comprehensive Phase 3 research now. This will take 15-45 minutes for a thorough analysis of your market and 10-12 competitors. Feel free to leave this tab open—I'll notify you when it's done!");
        await this.saveMessageToDatabase('ai', 'Starting comprehensive Phase 3 deep research and competitor analysis.');
        
        // Voice feedback
        if (this.isVoiceEnabled) {
            this.speak("Starting deep research now. I'll notify you when it's complete!");
        }
        
        await this.triggerDeepResearch();
    }

    handleResearchGateViolation(message) {
        console.warn('Research gate violation received:', message);
        
        // If phase2_confirmed is already set locally, silently retry instead of re-showing confirmation
        const alreadyConfirmed = this.consultation_data?.phase_flags?.phase2_confirmed === true;
        
        if (alreadyConfirmed) {
            console.log('phase2_confirmed is set locally - silently retrying flag persistence + research trigger');
            const ajaxUrl = window.vmo_consultant_context_v1?.ajax_url;
            const nonce = window.vmo_consultant_context_v1?.nonce;
            const postId = window.vmo_consultant_context_v1?.post_id;
            if (ajaxUrl && nonce && postId) {
                fetch(ajaxUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({
                        action: 'vmo_save_consultation',
                        nonce: nonce,
                        post_id: postId,
                        consultation_data: JSON.stringify({
                            phase_flags: { ...this.consultation_data.phase_flags, phase2_confirmed: true }
                        })
                    })
                }).then(() => {
                    console.log('Flag re-persisted, retrying deep research...');
                    this._deepResearchInProgress = false;
                    this.phase3ResearchRequested = false;
                    setTimeout(() => this.triggerDeepResearch(), 300);
                }).catch(err => {
                    console.error('Flag re-persist failed, showing confirmation UI:', err);
                    this._showGateConfirmationFallback();
                });
            } else {
                this._showGateConfirmationFallback();
            }
            return;
        }
        
        // Not confirmed locally - show the confirmation UI
        this._showGateConfirmationFallback();
    }
    
    _showGateConfirmationFallback() {
        this.phase3ResearchRequested = false;
        this._deepResearchInProgress = false;
        this.isProcessing = false;
        this.setVoiceStatus('ready');
        this.refreshPhaseUI();
        this.refreshAllowedActions();
        setTimeout(() => this.showResearchConfirmation(), 500);
    }
    
    /**
     * ✅ Check if a message is a voice confirmation to start deep research
     */
    isResearchVoiceConfirmation(message) {
        if (!this._awaitingResearchConfirmation) return false;
        
        const lowerMessage = message.toLowerCase().trim();
        
        // ✅ FIX: Exclude messages that are clearly about something else (email, pages, etc.)
        const excludePatterns = [
            'email', '@', 'newsletter', 'support', 'info@', 'add', 'list', 'address',
            'page', 'feature', 'website', 'logo', 'color', 'design', 'product', 'service'
        ];
        
        for (const pattern of excludePatterns) {
            if (lowerMessage.includes(pattern)) {
                return false; // This message is about something else, not research confirmation
            }
        }
        
        // ✅ More specific affirmative phrases - must be more explicit to start research
        const startPhrases = [
            'yes start the research', 'start the research', 'begin the research', 'start research',
            'yes start', 'yes begin', 'yes go ahead', 'go ahead with research',
            'start it', 'begin it', 'let\'s do it', 'do the research',
            'i\'m ready', 'ready to start', 'proceed with research', 
            'yes please start', 'please start', 'please begin',
            'confirmed', 'confirm research', 'affirmative',
            'go ahead', 'go for it', 'sounds good', 'let\'s go', 'do it',
            'that sounds good', 'go ahead and start', 'yes go ahead and start'
        ];
        
        // ✅ Short confirmation words only if the message is VERY short (likely just a confirmation)
        const shortConfirmations = ['yes', 'yeah', 'yep', 'yup', 'sure', 'okay', 'ok', 'ready', 'proceed', 'continue', 'go ahead', 'do it', 'let\'s go', 'absolutely', 'definitely', 'for sure'];
        
        // If message is very short (less than 15 chars), accept short confirmations
        if (lowerMessage.length < 15) {
            for (const phrase of shortConfirmations) {
                if (lowerMessage === phrase || lowerMessage === phrase + '.' || lowerMessage === phrase + '!') {
                    return 'start';
                }
            }
        }
        
        // Check for longer, more specific phrases
        for (const phrase of startPhrases) {
            if (lowerMessage.includes(phrase)) {
                return 'start';
            }
        }
        
        // Phrases to add more info
        const waitPhrases = [
            'wait', 'hold on', 'not yet', 'more info', 'add more',
            'i want to add', 'let me add', 'one more thing',
            'before you start', 'before we start', 'hang on'
        ];
        
        for (const phrase of waitPhrases) {
            if (lowerMessage.includes(phrase)) {
                return 'wait';
            }
        }
        
        return false;
    }

    // ✅ FIX: Trigger Deep Research Phase 3 with COMPREHENSIVE 25-CHUNK Progress (1 API each)
    async triggerDeepResearch() {
        console.log('🔍 Phase 3: Deep Research - Starting COMPREHENSIVE 25-chunk market analysis...');
        this.phase3ResearchRequested = true;
        this._deepResearchInProgress = true; // ✅ Flag to prevent watchdog interruption
        this._researchStartTime = Date.now(); // ✅ Track start time for elapsed display
        this.isProcessing = true; // Set processing state
        this.setVoiceStatus('thinking');
        // Don't show modal phase thinking - let user see the progress
        // this.showModalPhase('thinking');
        const startingPhase = this.consultation_data?.consultation_flow?.current_index ?? 0;
        
        // ✅ Clear any existing watchdog - deep research manages its own progress
        if (this._processingWatchdog) clearTimeout(this._processingWatchdog);
        
        // ✅ Ensure wizard modal is visible so user can see progress
        this._ensureWizardModalVisible();
        
        // ✅ Remove any existing progress indicator (from retries) before creating new one
        const existingProgress = document.getElementById('vmo-research-progress');
        if (existingProgress) {
            const wrapper = existingProgress.closest('.vmo-ai-message');
            if (wrapper) wrapper.remove();
            else existingProgress.remove();
        }
        
        // 🎯 Show enhanced progress indicator for comprehensive research
        const progressHtml = this.createDeepResearchProgressIndicator();
        this.addNodeMessageToUI('system', progressHtml);
        
        // Give the DOM time to render the progress indicator
        await new Promise(resolve => setTimeout(resolve, 100));
        
        try {
            const AX = this.getAjax();
            this.addMessageToUI('system', '🔍 Starting COMPREHENSIVE Phase 3 research (26 micro-phases for reliability, ~30-50 minutes for thorough analysis)...');
            
            // ✅ PHASE 3 COMPLETE: 29-chunk comprehensive research (1 API call each to prevent nginx 504 timeouts)
            // MUST match PHP TOTAL_CHUNKS = 29 (chunks 27-29 extracted from old chunks 6, 24, 26)
            let isComplete = false;
            let currentChunk = 0;
            let finalResults = null;
            const totalChunks = 29;
            
            // Phase 3 chunk labels - 29 micro-chunks for maximum reliability (1 API call each)
            const chunkLabels = [
                '🎯 Analyzing business vision & intent...',
                '🏢 Evaluating business structure...',
                '👥 Researching target market intelligence...',
                '🎪 Profiling target audience...',
                '🔎 Discovering top competitors...',
                '📊 Analyzing industry insights...',
                '💼 Analyzing competitor business models (1/3)...',
                '💼 Analyzing competitor business models (2/3)...',
                '💼 Analyzing competitor business models (3/3)...',
                '🌐 Evaluating competitor websites (1/3)...',
                '🌐 Evaluating competitor websites (2/3)...',
                '🌐 Evaluating competitor websites (3/3)...',
                '📈 Benchmarking competitor SEO (1/3)...',
                '📈 Benchmarking competitor SEO (2/3)...',
                '📈 Benchmarking competitor SEO (3/3)...',
                '📉 Analyzing market trends...',
                '💰 Building pricing strategy...',
                '📣 Developing marketing strategy...',
                '⚙️ Planning operations & delivery...',
                '💻 Evaluating technology stack...',
                '⚖️ Checking legal & compliance...',
                '🔑 Researching keywords...',
                '🎨 Analyzing brand positioning...',
                '🧩 Synthesizing strategic insights...',
                '✅ Finalizing comprehensive research summary...',
                '⭐ Gathering competitor review intelligence (1/2)...',
                '📊 Mapping market positioning...',
                '📈 Building structured market intelligence...',
                '⭐ Gathering competitor review intelligence (2/2)...'
            ];
            
            while (!isComplete && currentChunk < totalChunks) {
                // Update progress indicator for current chunk
                const progressPercent = Math.round(((currentChunk + 1) / totalChunks) * 100);
                this.updateResearchProgress(currentChunk + 1, totalChunks, chunkLabels[currentChunk] || 'Processing...');
                console.log(`📦 Running research chunk ${currentChunk + 1}/${totalChunks} (${progressPercent}%)...`);
                
                // ✅ FIX: Maximum 5 retries per chunk, then skip or use fallback
                const MAX_RETRIES = 5;
                let retryCount = 0;
                let chunkSuccess = false;
                
                while (!chunkSuccess && retryCount < MAX_RETRIES) {
                    try {
                        // ✅ FIX: Pass ALL extracted information to deep research, not just a subset
                        const extractedInfo = this.consultation_data.extracted_information || {};
                        
                        // ✅ BOTTLENECK FIX: AbortController with 600s timeout per chunk
                        // Prevents browser from hanging indefinitely on a stalled API call
                        const chunkAbort = new AbortController();
                        const chunkTimeout = setTimeout(() => chunkAbort.abort(), 600000); // 10 minutes
                        
                        const response = await fetch(AX.url, {
                            method: 'POST',
                            signal: chunkAbort.signal,
                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                            body: new URLSearchParams({
                                action: 'vmo_trigger_deep_research',
                                security: AX.nonce,
                                nonce: AX.nonce,
                                _ajax_nonce: AX.nonce,
                                session_id: this.sessionId,
                                chunk: currentChunk + 1, // 1-based chunk number
                                consultation_data: JSON.stringify({
                                    // Core business info
                                    business_name: extractedInfo.businessName || extractedInfo.business_name,
                                    business_type: extractedInfo.businessType || extractedInfo.business_type,
                                    industry: extractedInfo.industry,
                                    
                                    // Target market - check multiple field names
                                    target_market: extractedInfo.targetAudience || extractedInfo.target_market || extractedInfo.target_audience,
                                    target_audience: extractedInfo.targetAudience || extractedInfo.target_audience,
                                    
                                    // Location and operations
                                    location: extractedInfo.location,
                                    operating_model: extractedInfo.operatingModel || extractedInfo.operating_model,
                                    
                                    // Offerings and pricing
                                    offerings: extractedInfo.offerings,
                                    pricing: extractedInfo.pricing,
                                    price_point: extractedInfo.pricePoint || extractedInfo.price_point,
                                    
                                    // Goals and vision
                                    goals: extractedInfo.businessGoals || extractedInfo.goals,
                                    business_goal: extractedInfo.businessGoals || extractedInfo.goals,
                                    business_vision: extractedInfo.businessVision || extractedInfo.business_vision,
                                    vision_timeframe: extractedInfo.visionTimeframe || extractedInfo.vision_timeframe,
                                    
                                    // Constraints
                                    budget: extractedInfo.budget,
                                    constraints: extractedInfo.constraints,
                                    
                                    // Custom pages and features (CRITICAL for recommendations)
                                    custom_pages: extractedInfo.customPages || extractedInfo.custom_pages || [],
                                    special_features: extractedInfo.specialFeatures || extractedInfo.special_features || [],
                                    page_descriptions: extractedInfo.pageDescriptions || extractedInfo.page_descriptions || {},
                                    
                                    // Style and branding
                                    website_style_preference: extractedInfo.preferredStyle || extractedInfo.website_style,
                                    brand_colors: extractedInfo.brandColors || extractedInfo.colors,
                                    
                                    // E-commerce
                                    needs_ecommerce: extractedInfo.needsEcommerce || extractedInfo.ecommerce_needed,
                                    
                                    // References
                                    references: extractedInfo.references || extractedInfo.inspirationSites,
                                    
                                    // Include the full extracted info as a backup
                                    _full_extracted_info: extractedInfo
                                })
                            })
                        });

                        if (!response.ok) {
                            clearTimeout(chunkTimeout); // Clear abort timer on response received
                            // ✅ Phase gate handling: stop retry loop on 422 (requires user confirmation/data)
                            if (response.status === 422) {
                                let gateMessage = '';
                                try {
                                    const body = await response.json();
                                    gateMessage = body?.data?.message || body?.message || '';
                                } catch (_) {
                                    try { gateMessage = await response.text(); } catch (_) {}
                                }
                                this.handleResearchGateViolation(gateMessage);
                                return;
                            }

                            const errorBody = await response.text();
                            throw new Error(`HTTP ${response.status}: ${errorBody || response.statusText}`);
                        }
                        
                        clearTimeout(chunkTimeout); // Clear abort timer — response received OK
                        let data;
                        try {
                            data = await response.json();
                        } catch (parseError) {
                            throw new Error('Invalid JSON received from deep research endpoint');
                        }
                        
                        // ✅ CHECK FOR INSUFFICIENT TOKENS - Show toast and pause for top-up
                        if (this.isInsufficientTokensResponse(data)) {
                            console.log('⚠️ Insufficient tokens detected during deep research');
                            // Save current progress so we can resume
                            this._pausedResearchChunk = currentChunk;
                            this._pausedResearchData = {
                                chunk: currentChunk,
                                totalChunks: totalChunks,
                                phase: 'deep_research'
                            };
                            await this.saveConsultationData({ paused_research: this._pausedResearchData });
                            
                            // Show the credit toast with resume capability
                            this.showInsufficientCreditsWithResume('deep_research');
                            
                            // Wait for user to top up - check every 10 seconds
                            await this.waitForCreditsAndResume();
                            
                            // After credits restored, continue the loop (don't throw)
                            console.log('✅ Credits restored, resuming deep research...');
                            continue; // Retry this chunk
                        }
                        
                        if (data?.success) {
                            console.log(`✅ Chunk ${currentChunk + 1}/${totalChunks} completed. Progress: ${data.data.progress_percent}%`);
                            isComplete = data.data.is_complete;
                            finalResults = data.data;
                            currentChunk++;
                            chunkSuccess = true;
                            retryCount = 0; // Reset retry count for next chunk
                            
                            // Clear any paused state on success
                            this._pausedResearchChunk = null;
                            this._pausedResearchData = null;
                            
                            // Update UI with progress
                            if (!isComplete) {
                                // Slightly longer delay for comprehensive research to prevent rate limiting
                                await new Promise(resolve => setTimeout(resolve, 800));
                            }
                        } else {
                            throw new Error(data?.data?.message || 'Research chunk failed');
                        }
                    } catch (chunkError) {
                        retryCount++;
                        
                        // ✅ FIX: If max retries reached, handle gracefully
                        if (retryCount >= MAX_RETRIES) {
                            console.error(`❌ Chunk ${currentChunk + 1} failed after ${MAX_RETRIES} attempts. Handling gracefully...`);
                            
                            // For the final chunk (25), we can still complete the research
                            // Other chunks have already succeeded with their data
                            if (currentChunk + 1 === totalChunks) {
                                console.log('⚠️ Final chunk failed but research is mostly complete. Proceeding with available data...');
                                isComplete = true;
                                chunkSuccess = true;
                                // Create minimal final results if we don't have them
                                if (!finalResults) {
                                    finalResults = {
                                        research_results: this.consultation_data.research_results || {},
                                        is_complete: true,
                                        current_chunk: totalChunks,
                                        total_chunks: totalChunks,
                                        progress_percent: 100
                                    };
                                }
                            } else {
                                // For non-final chunks, skip to next chunk after max retries
                                console.log(`⚠️ Skipping chunk ${currentChunk + 1} after ${MAX_RETRIES} failed attempts`);
                                currentChunk++;
                                chunkSuccess = true; // Exit inner loop, continue outer loop
                            }
                            continue;
                        }
                        
                        // Exponential backoff: 3s, 6s, 12s, 24s, 48s (cap at 48s)
                        const backoffMs = Math.min(3000 * Math.pow(2, Math.min(retryCount - 1, 4)), 48000);
                        console.warn(`⚠️ Chunk ${currentChunk + 1} attempt ${retryCount}/${MAX_RETRIES} failed. Retrying in ${backoffMs/1000}s...`, chunkError.message);
                        // Silent retry - NO user-facing error messages, just update progress bar label
                        this.updateResearchProgress(currentChunk + 1, totalChunks, `Working on analysis... (attempt ${retryCount + 1}/${MAX_RETRIES})`);
                        await new Promise(resolve => setTimeout(resolve, backoffMs));
                    }
                }
            }
            
            if (isComplete && finalResults) {
                console.log('✅ All research chunks completed successfully!');
                
                // ✅ Clear deep research flag and paused state
                this._deepResearchInProgress = false;
                this._pausedResearchChunk = null;
                this._pausedResearchData = null;
                
                // 🎯 Mark progress indicator as complete (but keep visible)
                this.hideResearchProgress();
                
                // ✅ FIX: Update main progress bar to show Phase 3 complete (43% = 3/7 phases)
                this.updateMainProgressBar(43, 'Deep research complete!');
                
                // ✅ NEW: Send browser notification to alert user
                await this.sendResearchCompleteNotification();
                
                const summary = this.formatResearchSummary(finalResults.research_results);
                this.addMessageToUI('ai', summary);
                await this.saveMessageToDatabase('ai', summary);
                
                if (finalResults.research_results) {
                    this.consultation_data.research_results = finalResults.research_results;
                    this.state.research_profile = finalResults.research_results;
                    this.syncLegacyConsultationDataFromState();
                }
                await this.saveConsultationData({
                    research_results: this.consultation_data.research_results || finalResults.research_results || {},
                    recommendation_checklist: this.consultation_data.recommendation_checklist || null
                });
                await this.updatePhaseFlag('phase3_completed', true);
                if (this.consultation_data.consultation_flow.current_index === 2) {
                    await this.advancePhase();
                }
                // ✅ FIX: Reset processing state before speaking
                this.isProcessing = false;
                this.setVoiceStatus('ready');
                
                // Speak the research summary if voice is enabled
                if (this.isVoiceEnabled) {
                    const shortSummary = `Deep research complete! I've analyzed your market, competitors, and opportunities. Ready to show you the recommendations checklist.`;
                    this.speak(shortSummary);
                }
                
                await this.presentPhase4Checklist(this.consultation_data.research_results);
                this.refreshPhaseUI();
                this.refreshAllowedActions();
            }
            // ✅ SAFETY NET: If loop exited without isComplete (all chunks sent but server
            // didn't flag complete), try one final explicit request for the last chunk,
            // then complete with whatever data we have. This guarantees chunk 29 (review
            // intelligence batch 2 + research_complete flag) always gets a chance to run.
            if (!isComplete && currentChunk >= totalChunks && finalResults) {
                console.warn('⚠️ All chunks sent but server did not flag is_complete. Retrying final chunk once...');
                try {
                    const extractedInfo = this.consultation_data.extracted_information || {};
                    const retryResp = await fetch(AX.url, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: new URLSearchParams({
                            action: 'vmo_trigger_deep_research',
                            security: AX.nonce,
                            nonce: AX.nonce,
                            _ajax_nonce: AX.nonce,
                            session_id: this.sessionId,
                            chunk: totalChunks, // Explicitly request the final chunk
                            consultation_data: JSON.stringify({
                                business_name: extractedInfo.businessName || extractedInfo.business_name,
                                business_type: extractedInfo.businessType || extractedInfo.business_type,
                                industry: extractedInfo.industry,
                                target_market: extractedInfo.targetAudience || extractedInfo.target_market || extractedInfo.target_audience,
                                _full_extracted_info: extractedInfo
                            })
                        })
                    });
                    if (retryResp.ok) {
                        const retryData = await retryResp.json();
                        if (retryData?.success) {
                            finalResults = retryData.data;
                            isComplete = retryData.data.is_complete;
                            console.log('✅ Final chunk retry succeeded. is_complete:', isComplete);
                        }
                    }
                } catch (retryErr) {
                    console.warn('⚠️ Final chunk retry failed:', retryErr.message);
                }
                
                // If STILL not complete after explicit retry, force completion
                // All chunks have been attempted — the data is as complete as it can be
                if (!isComplete) {
                    console.warn('⚠️ Forcing completion after final chunk retry. All chunks attempted.');
                    isComplete = true;
                }
                
                // Now run the normal completion flow
                this._deepResearchInProgress = false;
                this._pausedResearchChunk = null;
                this._pausedResearchData = null;
                
                this.hideResearchProgress();
                this.updateMainProgressBar(43, 'Deep research complete!');
                await this.sendResearchCompleteNotification();
                
                const safetyResults = finalResults.research_results || finalResults;
                const summary = this.formatResearchSummary(safetyResults);
                this.addMessageToUI('ai', summary);
                await this.saveMessageToDatabase('ai', summary);
                
                if (safetyResults) {
                    this.consultation_data.research_results = safetyResults;
                    this.state.research_profile = safetyResults;
                    this.syncLegacyConsultationDataFromState();
                }
                await this.saveConsultationData({
                    research_results: this.consultation_data.research_results || {},
                    recommendation_checklist: this.consultation_data.recommendation_checklist || null
                });
                await this.updatePhaseFlag('phase3_completed', true);
                if (this.consultation_data.consultation_flow.current_index === 2) {
                    await this.advancePhase();
                }
                this.isProcessing = false;
                this.setVoiceStatus('ready');
                
                if (this.isVoiceEnabled) {
                    this.speak('Deep research complete! Ready to show you the recommendations checklist.');
                }
                
                await this.presentPhase4Checklist(this.consultation_data.research_results);
                this.refreshPhaseUI();
                this.refreshAllowedActions();
            }
        } catch (error) {
            // ✅ SILENT ERROR HANDLING - No user messages, just log and continue retrying
            console.error('❌ Deep research outer error (will auto-retry):', error);
            
            // ✅ FIX: Don't restart if Phase 3 is already complete (research finished successfully)
            const phase3Complete = this.consultation_data?.phase_flags?.phase3_completed || 
                                   this.state?.phase_flags?.phase3_completed || 
                                   (this.consultation_data?.consultation_flow?.current_index > 2);
            
            if (phase3Complete) {
                console.log('🛑 Phase 3 already complete - not restarting deep research');
                this._deepResearchInProgress = false;
                // Try to continue to Phase 4 checklist if not presented
                if (!this.phase4ChecklistPresented && this.consultation_data.research_results) {
                    try {
                        await this.presentPhase4Checklist(this.consultation_data.research_results);
                    } catch(e) {
                        console.error('Error presenting Phase 4 checklist:', e);
                    }
                }
                return;
            }
            
            // If somehow we get here and research isn't complete, restart the entire research process silently
            this._deepResearchInProgress = false;
            // Wait 5 seconds then retry the entire research automatically
            console.log('🔄 Auto-restarting deep research in 5 seconds...');
            await new Promise(resolve => setTimeout(resolve, 5000));
            // Recursively retry - this ensures research NEVER fails to user
            return this.triggerDeepResearch();
        } finally {
            // ✅ Only clear the flag when truly complete (not on retry)
            // Flag is managed in the success path above
        }
    }
    
    // ✅ NEW: Update research progress indicator - PHASE 3 (26 chunks → 10 UI steps)
    updateResearchProgress(current, total, label) {
        // ✅ Ensure wizard modal stays visible during research updates
        this._ensureWizardModalVisible();
        
        const progressContainer = document.getElementById('vmo-research-progress');
        if (progressContainer) {
            const progressBar = progressContainer.querySelector('.vmo-progress-bar');
            const etaSpan = progressContainer.querySelector('#vmo-research-eta');
            
            const percent = Math.round((current / total) * 100);
            
            if (progressBar) {
                // Stop the animation and set manual width
                progressBar.style.animation = 'none';
                progressBar.style.width = `${percent}%`;
            }
            
            // ✅ FIX: Sync the MAIN wizard progress bar with research progress
            // Phase 3 range: 25% (start) → 43% (complete), so map research % into that range
            const mainPercent = 25 + Math.round((percent / 100) * 18); // 25-43%
            this.updateMainProgressBar(mainPercent, `Phase 3: Deep Research · ${percent}%`);
            
            // Update ETA with elapsed time and dynamic estimate
            if (etaSpan) {
                if (this._researchStartTime) {
                    const elapsedMs = Date.now() - this._researchStartTime;
                    const elapsedMin = Math.floor(elapsedMs / 60000);
                    const elapsedSec = Math.floor((elapsedMs % 60000) / 1000);
                    const elapsedStr = `${elapsedMin}m ${elapsedSec}s`;
                    
                    // Calculate dynamic ETA
                    let etaStr = '';
                    if (current > 0 && current < total) {
                        const msPerChunk = elapsedMs / current;
                        const remainingMs = msPerChunk * (total - current);
                        const remainingMin = Math.ceil(remainingMs / 60000);
                        etaStr = remainingMin > 0 ? ` (~${remainingMin}m remaining)` : ' (almost done!)';
                    }
                    
                    etaSpan.innerHTML = `⏱️ Elapsed: <strong>${elapsedStr}</strong> - comprehensive analysis in progress...${etaStr}`;
                } else {
                    etaSpan.textContent = label;
                }
            }
            
            // ✅ FIXED: Map 26 chunks to 10 UI steps
            // Chunks 1-4 = Step 1 (Core Foundation)
            // Chunks 5-6 = Step 2 (Competitor Discovery)
            // Chunks 7-9 = Step 3 (Business Model Analysis)
            // Chunks 10-12 = Step 4 (Website Analysis)
            // Chunks 13-15 = Step 5 (SEO Benchmarking)
            // Chunks 16-17 = Step 6 (Market & Pricing)
            // Chunks 18-19 = Step 7 (Tech & Compliance)
            // Chunks 20-21 = Step 8 (Comparative Matrix)
            // Chunks 22-23 = Step 9 (Strategic Advantages)
            // Chunks 24-26 = Step 10 (Final Recommendations + Review Intelligence)
            const chunkToStep = {
                1: 1, 2: 1, 3: 1, 4: 1,     // Core Foundation (chunks 1-4 → step 1)
                5: 2, 6: 2,                  // Competitor Discovery (chunks 5-6 → step 2)
                7: 3, 8: 3, 9: 3,            // Business Model (chunks 7-9 → step 3)
                10: 4, 11: 4, 12: 4,         // Website Analysis (chunks 10-12 → step 4)
                13: 5, 14: 5, 15: 5,         // SEO Benchmarking (chunks 13-15 → step 5)
                16: 6, 17: 6,                // Market & Pricing (chunks 16-17 → step 6)
                18: 7, 19: 7,                // Tech & Compliance (chunks 18-19 → step 7)
                20: 8, 21: 8,                // Comparative Matrix (chunks 20-21 → step 8)
                22: 9, 23: 9,                // Strategic Advantages (chunks 22-23 → step 9)
                24: 10, 25: 10, 26: 10       // Final Recommendations + Review Intelligence (chunks 24-26 → step 10)
            };
            
            // Calculate which step we're on based on current chunk
            const currentStep = chunkToStep[current] || 1;
            
            // Mark all steps BEFORE current step as complete
            for (let step = 1; step < currentStep; step++) {
                const stepEl = progressContainer.querySelector(`[data-step="${step}"] .vmo-check`);
                if (stepEl) {
                    stepEl.textContent = '✅';
                    stepEl.style.color = '#00ff88';
                }
            }
            
            // Mark current step as in-progress
            const currentStepEl = progressContainer.querySelector(`[data-step="${currentStep}"] .vmo-check`);
            if (currentStepEl && currentStepEl.textContent !== '✅') {
                currentStepEl.textContent = '⏳';
                currentStepEl.style.color = '#ffd93d';
            }
            
            // Mark all steps AFTER current step as pending
            for (let step = currentStep + 1; step <= 10; step++) {
                const stepEl = progressContainer.querySelector(`[data-step="${step}"] .vmo-check`);
                if (stepEl && stepEl.textContent !== '✅') {
                    stepEl.textContent = '○';
                    stepEl.style.color = '#666';
                }
            }
        }
    }

    async handleDeepResearchFailure(message) {
        // ✅ SILENT HANDLER: No user-facing messages, just log and auto-retry
        console.log('🔄 Deep research needs retry (no user message):', message || 'Continuing silently...');
        
        // ✅ DON'T hide progress - keep showing it while retrying
        // this.hideResearchProgress(); // REMOVED - keep progress visible
        
        // ✅ DON'T reset processing state - we're still processing via retry
        // this.isProcessing = false; // REMOVED
        // this.setVoiceStatus('ready'); // REMOVED
        
        // ✅ NO user messages - completely silent operation
        // this.addMessageToUI('ai', fallback); // REMOVED
        // await this.saveMessageToDatabase('ai', fallback); // REMOVED
        
        // ✅ NO voice announcements
        // if (this.isVoiceEnabled) { this.speak(fallback); } // REMOVED
        
        // ✅ Auto-retry the deep research after a brief pause
        console.log('🔄 Auto-retrying deep research in 3 seconds...');
        await new Promise(resolve => setTimeout(resolve, 3000));
        
        // Trigger retry - this ensures research ALWAYS completes eventually
        return this.triggerDeepResearch();
    }

    // ✅ CRITICAL: Ensure the wizard modal is visible so user can see research progress
    _ensureWizardModalVisible() {
        const modal = document.getElementById('vmo-business-wizard-modal');
        if (!modal) return;
        
        // Check if modal is hidden
        const isHidden = modal.style.display === 'none' || 
                         window.getComputedStyle(modal).display === 'none';
        
        if (isHidden) {
            console.log('🔄 Re-opening wizard modal for research progress visibility');
            modal.style.display = 'flex';
            modal.removeAttribute('inert');
            modal.setAttribute('aria-hidden', 'false');
            document.body.classList.add('vmo-wizard-active');
        }
        
        // Ensure conversation container is scrolled to bottom
        const chatContainer = document.getElementById('vmo-wizard-conversation');
        if (chatContainer) {
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }
    }
    
    // 🎯 Create visual progress indicator for PHASE 3 COMPREHENSIVE research (8 chunks)
    createDeepResearchProgressIndicator() {
        const container = document.createElement('div');
        container.className = 'vmo-deep-research-progress';
        container.id = 'vmo-research-progress';
        container.innerHTML = `
            <div style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); border-radius: 12px; padding: 20px; margin: 10px 0; border: 1px solid #0f3460;">
                <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;">
                    <div style="display: flex; align-items: center; gap: 12px;">
                        <div class="vmo-research-spinner" style="width: 24px; height: 24px; border: 3px solid #0f3460; border-top: 3px solid #00d4ff; border-radius: 50%; animation: vmo-spin 1s linear infinite;"></div>
                        <span style="color: #00d4ff; font-weight: 600; font-size: 16px;">🔬 Phase 3: Comprehensive Competitive Intelligence</span>
                    </div>
                    <span id="vmo-research-eta" style="color: #ffd93d; font-size: 13px;">⏱️ Starting comprehensive analysis...</span>
                </div>
                <p style="color: #a0a0a0; font-size: 13px; margin: 0 0 15px 0;">
                    <strong style="color: #00ff88;">Full Phase 3 Analysis (20-45 min):</strong> Analyzing 10-12 competitors individually, benchmarking websites, SEO strategies, and mapping strategic advantages. 
                    <br><span style="color: #ffd93d;">💡 You can leave this tab open - a notification will alert you when complete!</span>
                </p>
                <div style="background: #0f3460; border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 15px;">
                    <div class="vmo-progress-bar" style="height: 100%; width: 0%; background: linear-gradient(90deg, #00d4ff, #00ff88); transition: width 0.5s ease;"></div>
                </div>
                <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; font-size: 12px; color: #a0a0a0;">
                    <div class="vmo-research-item" data-step="1" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 1. Core Business Foundation
                    </div>
                    <div class="vmo-research-item" data-step="2" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 2. Competitor Discovery
                    </div>
                    <div class="vmo-research-item" data-step="3" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 3. Business Model Analysis
                    </div>
                    <div class="vmo-research-item" data-step="4" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 4. Website Analysis
                    </div>
                    <div class="vmo-research-item" data-step="5" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 5. SEO Benchmarking
                    </div>
                    <div class="vmo-research-item" data-step="6" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 6. Market & Pricing
                    </div>
                    <div class="vmo-research-item" data-step="7" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 7. Tech & Compliance
                    </div>
                    <div class="vmo-research-item" data-step="8" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 8. Comparative Matrix
                    </div>
                    <div class="vmo-research-item" data-step="9" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 9. Strategic Advantages
                    </div>
                    <div class="vmo-research-item" data-step="10" style="display: flex; align-items: center; gap: 6px;">
                        <span class="vmo-check" style="color: #666;">○</span> 10. Final Recommendations
                    </div>
                </div>
            </div>
            <style>
                @keyframes vmo-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
                @keyframes vmo-progress { 0% { width: 0%; } 100% { width: 100%; } }
            </style>
        `;
        
        // ✅ FIX: Do NOT auto-animate based on time - let actual chunk completion update steps
        // Only start the first step as "in progress"
        this.initResearchProgress(container);
        
        return container;
    }
    
    // ✅ NEW: Initialize research progress (no auto-animation)
    initResearchProgress(container) {
        const steps = container.querySelectorAll('.vmo-research-item');
        const etaEl = container.querySelector('#vmo-research-eta');
        const startTime = Date.now();
        
        // Only show first step as in-progress
        if (steps.length > 0) {
            const firstCheck = steps[0].querySelector('.vmo-check');
            if (firstCheck) {
                firstCheck.textContent = '⏳';
                firstCheck.style.color = '#00d4ff';
            }
        }
        
        // Update elapsed time every 30 seconds (no step changes)
        const interval = setInterval(() => {
            const elapsed = Math.floor((Date.now() - startTime) / 1000);
            const mins = Math.floor(elapsed / 60);
            const secs = elapsed % 60;
            
            // Only update the time display, NOT the steps
            if (etaEl && etaEl.textContent.indexOf('Complete') === -1) {
                etaEl.textContent = `⏱️ Elapsed: ${mins}m ${secs}s - comprehensive analysis in progress...`;
            }
        }, 30000);
        
        // Store interval so we can clear it when research completes
        this._researchProgressInterval = interval;
    }
    
    // ✅ DEPRECATED: Old auto-animation function - now replaced with chunk-driven updates
    animateResearchProgress_DISABLED(container) {
        const steps = container.querySelectorAll('.vmo-research-item');
        const etaEl = container.querySelector('#vmo-research-eta');
        let currentStep = 0;
        const stepDuration = 12000; // 12 seconds per step (10 steps = ~2 minutes)
        const startTime = Date.now();
        
        const interval = setInterval(() => {
            if (currentStep >= steps.length) {
                clearInterval(interval);
                if (etaEl) etaEl.textContent = 'Finalizing...';
                return;
            }
            const step = steps[currentStep];
            if (step) {
                step.querySelector('.vmo-check').textContent = '✅';
                step.querySelector('.vmo-check').style.color = '#00ff88';
                step.style.color = '#e0e0e0';
            }
            currentStep++;
            if (currentStep < steps.length) {
                const nextStep = steps[currentStep];
                if (nextStep) {
                    nextStep.querySelector('.vmo-check').textContent = '⏳';
                    nextStep.querySelector('.vmo-check').style.color = '#00d4ff';
                }
            }
            
            // Update ETA
            const elapsed = Math.floor((Date.now() - startTime) / 1000);
            const remaining = Math.max(0, ((steps.length - currentStep) * stepDuration / 1000));
            if (etaEl && remaining > 0) {
                const mins = Math.floor(remaining / 60);
                const secs = Math.floor(remaining % 60);
                etaEl.textContent = mins > 0 ? `~${mins}m ${secs}s remaining` : `~${secs}s remaining`;
            }
        }, stepDuration);
        
        // Store interval so we can clear it when research completes
        this._researchProgressInterval = interval;
    }
    
    hideResearchProgress() {
        if (this._researchProgressInterval) {
            clearInterval(this._researchProgressInterval);
            this._researchProgressInterval = null;
        }
        const progressEl = document.getElementById('vmo-research-progress');
        if (progressEl) {
            // Complete all steps visually
            const steps = progressEl.querySelectorAll('.vmo-research-item');
            steps.forEach(step => {
                const check = step.querySelector('.vmo-check');
                if (check) {
                    check.textContent = '✅';
                    check.style.color = '#00ff88';
                }
                step.style.color = '#e0e0e0';
            });
            // Update progress bar to 100%
            const progressBar = progressEl.querySelector('.vmo-progress-bar');
            if (progressBar) {
                progressBar.style.animation = 'none';
                progressBar.style.width = '100%';
            }
            // Update ETA text to show completion
            const etaSpan = progressEl.querySelector('#vmo-research-eta');
            if (etaSpan) {
                etaSpan.textContent = '✅ Research Complete!';
                etaSpan.style.color = '#00ff88';
            }
            // Update title to show complete
            const titleSpan = progressEl.querySelector('span[style*="font-weight: 600"]');
            if (titleSpan) {
                titleSpan.textContent = '✅ Phase 3: Research Complete';
            }
            // Stop spinner
            const spinner = progressEl.querySelector('.vmo-research-spinner');
            if (spinner) {
                spinner.style.animation = 'none';
                spinner.style.borderColor = '#00ff88';
                spinner.innerHTML = '✅';
                spinner.style.display = 'flex';
                spinner.style.alignItems = 'center';
                spinner.style.justifyContent = 'center';
                spinner.style.fontSize = '16px';
            }
            // ✅ KEEP the progress element visible - DO NOT REMOVE
            // This ensures user can see research history in the chat
        }
    }
    
    // ✅ NEW: Send browser notification when research completes
    async sendResearchCompleteNotification() {
        // Request notification permission if not already granted
        if ('Notification' in window) {
            if (Notification.permission === 'default') {
                await Notification.requestPermission();
            }
            
            if (Notification.permission === 'granted') {
                const iconUrl = this.getPluginAssetUrl('assets/vmo-icon.png');
                const notification = new Notification('VMO Deep Research Complete! 🎉', {
                    body: 'Your comprehensive market analysis is ready. Click to view recommendations.',
                    icon: iconUrl,
                    tag: 'vmo-research-complete',
                    requireInteraction: true
                });
                
                notification.onclick = () => {
                    window.focus();
                    notification.close();
                    // Open the wizard modal if closed
                    const modal = document.getElementById('vmoBusinessWizardModal');
                    if (modal) modal.style.display = 'block';
                };
                
                // Play notification sound
                this.playNotificationSound();
            }
        }
    }
    
    // ✅ NEW: Play notification sound
    playNotificationSound() {
        try {
            // Create a simple notification chime using Web Audio API
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();
            
            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);
            
            oscillator.frequency.setValueAtTime(880, audioContext.currentTime); // A5
            oscillator.frequency.setValueAtTime(1108.73, audioContext.currentTime + 0.1); // C#6
            oscillator.frequency.setValueAtTime(1318.51, audioContext.currentTime + 0.2); // E6
            
            gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
            
            oscillator.start(audioContext.currentTime);
            oscillator.stop(audioContext.currentTime + 0.5);
        } catch (e) {
            console.log('Could not play notification sound:', e);
        }
    }

    // ✅ NEW: Check if response indicates insufficient tokens/credits
    isInsufficientTokensResponse(data) {
        if (!data) return false;
        
        // Check various response formats used by VMO backend
        if (data.status === 'insufficient_tokens') return true;
        if (data.code === 'insufficient_tokens') return true;
        if (data.success === false && data.data) {
            if (data.data.status === 'insufficient_tokens') return true;
            if (data.data.code === 'insufficient_tokens') return true;
            if (data.data.message && data.data.message.toLowerCase().includes('insufficient')) return true;
        }
        if (data.error && data.error.code === 'insufficient_tokens') return true;
        
        return false;
    }
    
    // ✅ NEW: Show credit toast with resume capability for consultation
    showInsufficientCreditsWithResume(operationType = 'consultation') {
        console.log('💳 Showing insufficient credits toast with resume option');
        
        // Remove any existing toasts
        const existingToasts = document.querySelectorAll('.vmo-credit-toast, .vmo-toast');
        existingToasts.forEach(t => t.remove());
        
        // Get URLs from localized data
        const upgradeUrl = (window.vmo_ajax && window.vmo_ajax.upgradeUrl) || 
                           (window.vmoAjax && window.vmoAjax.upgradeUrl) || 
                           'https://jerremejosephson.com/vmo-upgrade/';
        const topupUrl = (window.vmo_ajax && window.vmo_ajax.topupUrl) || 
                         (window.vmoAjax && window.vmoAjax.topupUrl) || 
                         'https://jerremejosephson.com/vmo-topup/';
        
        // Custom message for ongoing consultation
        const operationName = operationType === 'deep_research' ? 'deep research' : 'consultation';
        const message = `Your ${operationName} is paused because your subscription credit has run out. 
                        <strong>Top up or upgrade to continue automatically from where you left off!</strong>`;
        
        // Create styled toast with pause indicator
        const toast = document.createElement('div');
        toast.className = 'vmo-credit-toast vmo-toast-warning vmo-credit-toast-paused';
        toast.innerHTML = `
            <div class="vmo-credit-toast-content">
                <div class="vmo-credit-toast-icon">⏸️</div>
                <div class="vmo-credit-toast-message">
                    <p>${message}</p>
                    <p style="font-size: 0.85em; color: #ffd93d; margin-top: 8px;">
                        ⏳ Checking for credits every 10 seconds...
                    </p>
                </div>
                <div class="vmo-credit-toast-actions">
                    <a href="${topupUrl}" target="_blank" class="vmo-credit-btn vmo-credit-btn-topup">
                        💳 Top Up Now
                    </a>
                    <a href="${upgradeUrl}" target="_blank" class="vmo-credit-btn vmo-credit-btn-upgrade">
                        🚀 Upgrade Plan
                    </a>
                </div>
            </div>
        `;
        
        // Add custom styles for the paused state
        toast.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 999999;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
            border: 2px solid #ffd93d;
            border-radius: 16px;
            padding: 24px;
            max-width: 450px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 40px rgba(255, 217, 61, 0.2);
            animation: vmo-pulse-border 2s infinite;
        `;
        
        // Add pulse animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes vmo-pulse-border {
                0%, 100% { border-color: #ffd93d; box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 40px rgba(255, 217, 61, 0.2); }
                50% { border-color: #ff9f43; box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 60px rgba(255, 159, 67, 0.3); }
            }
            .vmo-credit-toast-paused .vmo-credit-toast-content {
                text-align: center;
            }
            .vmo-credit-toast-paused .vmo-credit-toast-icon {
                font-size: 48px;
                margin-bottom: 16px;
            }
            .vmo-credit-toast-paused .vmo-credit-toast-message {
                color: #fff;
                font-size: 1.1em;
                line-height: 1.6;
                margin-bottom: 20px;
            }
            .vmo-credit-toast-paused .vmo-credit-toast-actions {
                display: flex;
                gap: 12px;
                justify-content: center;
            }
            .vmo-credit-toast-paused .vmo-credit-btn {
                padding: 12px 24px;
                border-radius: 8px;
                text-decoration: none;
                font-weight: 600;
                font-size: 1em;
                transition: all 0.2s;
            }
            .vmo-credit-toast-paused .vmo-credit-btn-topup {
                background: linear-gradient(135deg, #00b894 0%, #00cec9 100%);
                color: #fff;
            }
            .vmo-credit-toast-paused .vmo-credit-btn-topup:hover {
                transform: scale(1.05);
                box-shadow: 0 4px 20px rgba(0, 184, 148, 0.4);
            }
            .vmo-credit-toast-paused .vmo-credit-btn-upgrade {
                background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);
                color: #fff;
            }
            .vmo-credit-toast-paused .vmo-credit-btn-upgrade:hover {
                transform: scale(1.05);
                box-shadow: 0 4px 20px rgba(108, 92, 231, 0.4);
            }
        `;
        document.head.appendChild(style);
        document.body.appendChild(toast);
        
        // Store reference for removal
        this._creditToast = toast;
        this._creditToastStyle = style;
        
        // Optional voice feedback
        if (this.isVoiceEnabled) {
            this.speak('Your consultation is paused. Please top up or upgrade your subscription to continue automatically.');
        }
    }
    
    // ✅ NEW: Wait for credits to be restored, then auto-resume
    async waitForCreditsAndResume() {
        console.log('⏳ Waiting for credits to be restored...');
        
        const checkInterval = 10000; // Check every 10 seconds
        const maxWaitTime = 3600000; // Max wait 1 hour
        const startTime = Date.now();
        
        return new Promise((resolve) => {
            const checkCredits = async () => {
                // Check if we've been waiting too long
                if (Date.now() - startTime > maxWaitTime) {
                    console.log('⏰ Max wait time reached, user can manually resume later');
                    // Keep the toast visible - user needs to take action
                    return;
                }
                
                try {
                    // Make a lightweight API call to check credit status
                    const AX = this.getAjaxEndpoint();
                    const response = await fetch(AX.url, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: new URLSearchParams({
                            action: 'vmo_check_credit_status',
                            security: AX.nonce,
                            nonce: AX.nonce
                        })
                    });
                    
                    const data = await response.json();
                    
                    // If credits are available (not insufficient), resume!
                    if (data?.success && !this.isInsufficientTokensResponse(data)) {
                        console.log('✅ Credits restored! Resuming consultation...');
                        
                        // Remove the toast
                        if (this._creditToast) {
                            this._creditToast.remove();
                            this._creditToast = null;
                        }
                        if (this._creditToastStyle) {
                            this._creditToastStyle.remove();
                            this._creditToastStyle = null;
                        }
                        
                        // Clear the interval
                        if (this._creditCheckInterval) {
                            clearInterval(this._creditCheckInterval);
                            this._creditCheckInterval = null;
                        }
                        
                        // Show a happy toast
                        if (typeof window.showVmoToast === 'function') {
                            window.showVmoToast('Credits restored! Resuming your consultation...', 'success');
                        }
                        
                        // Voice announcement
                        if (this.isVoiceEnabled) {
                            this.speak('Credits restored! Resuming your consultation now.');
                        }
                        
                        resolve(); // This allows the deep research loop to continue
                        return;
                    }
                } catch (error) {
                    console.log('Credit check failed, will retry:', error.message);
                }
                
                // Schedule next check
                this._creditCheckInterval = setTimeout(checkCredits, checkInterval);
            };
            
            // Start checking
            checkCredits();
        });
    }

    formatResearchSummary(results = {}) {
        const info = this.consultation_data.extracted_information || {};
        // ✅ FIX: Check both camelCase and snake_case property names
        const bizName = info.businessName || info.business_name || 'your business';
        const industry = info.industry || 'your industry';
        const audience = info.targetAudience || info.target_audience || info.target_market || 'your ideal customers';
        const goals = info.businessGoals || info.goals || 'your launch and growth goals';
        
        // ✅ ENHANCED: Use backend-generated comprehensive summary if available
        if (results.research_summary && typeof results.research_summary === 'string' && results.research_summary.length > 200) {
            // Backend already generated a comprehensive summary with market data
            const leadsCount = results.leads_generated_count || 0;
            let summary = `✅ **Deep Research Complete for ${bizName}** (${industry})\n\n`;
            summary += results.research_summary;
            
            if (leadsCount > 0) {
                summary += `\n\n🎯 **Initial Leads Generated:** ${leadsCount} qualified leads have been identified and saved to your CRM dashboard.`;
            }
            
            // Note: Recommendations checklist is shown immediately after this summary
            return summary;
        }

        // ✅ ENHANCED: Extract actual competitor names from research
        const competitorsList = results.competitors_list || [];
        let competitorNames = [];
        for (const c of competitorsList.slice(0, 5)) {
            const name = (typeof c === 'object' && c.name) ? c.name : (typeof c === 'string' ? c : null);
            if (name) competitorNames.push(name);
        }
        
        // ✅ ENHANCED: Extract actual market insights
        const extractInsights = (data) => {
            if (!data) return [];
            if (typeof data === 'string') {
                // Parse bullet points from text
                const bullets = data.match(/[-•]\s*([^\n]+)/g) || [];
                return bullets.map(b => b.replace(/^[-•]\s*/, '').trim()).filter(Boolean).slice(0, 3);
            }
            if (Array.isArray(data)) return data.slice(0, 3);
            if (data.analysis && typeof data.analysis === 'string') {
                const bullets = data.analysis.match(/[-•]\s*([^\n]+)/g) || [];
                return bullets.map(b => b.replace(/^[-•]\s*/, '').trim()).filter(Boolean).slice(0, 3);
            }
            return [];
        };

        const formatBullets = (items = [], fallback) => {
            if (!Array.isArray(items) || items.length === 0) return `- ${fallback}`;
            return items.slice(0, 3).map(item => `- ${item}`).join('\n');
        };

        // Extract actual insights from research results
        const marketInsights = extractInsights(results.market_trends || results.target_market_intelligence);
        const opportunityInsights = extractInsights(results.market_gaps || results.opportunities_threats);
        const legalInsights = extractInsights(results.legal_compliance);

        const market = formatBullets(marketInsights, 'Market research data will be included in your strategy document.');
        
        // ✅ ENHANCED: Show actual competitor names
        const competitors = competitorNames.length > 0 
            ? `- **Key competitors analyzed:** ${competitorNames.join(', ')}\n- Identified gaps and opportunities for differentiation\n- Strategic positioning recommendations included`
            : formatBullets(results.competitor_highlights || [], 'Competitor analysis completed—see full report for details.');
        
        const opportunities = formatBullets(opportunityInsights, 'Strategic opportunities identified based on market gaps.');
        const legal = formatBullets(legalInsights, 'Ensure privacy policy, terms, and ADA-friendly design are in place.');
        
        // ✅ ENHANCED: Include leads count
        const leadsCount = results.leads_generated_count || 0;
        const leadsSection = leadsCount > 0 
            ? `\n🎯 **Initial Leads for Your CRM**\n- **${leadsCount} qualified leads** have been identified and saved to your dashboard\n- Ready for immediate outreach after setup\n`
            : '';

        return `✅ **Deep Research Complete for ${bizName}** (${industry})

📊 **Market Insights**
${market}

🏆 **Competitive Landscape**
${competitors}

💡 **Strategic Opportunities**
${opportunities}
${leadsSection}
⚖️ **Legal & Compliance Priorities**
${legal}`;
    }

    isExistingPhoneRecommendationItem(item = {}) {
        const itemId = String(item?.id || '').toLowerCase();
        if (itemId === 'communication_existing_phone_policy') {
            return false;
        }

        const haystack = [
            item?.label || '',
            item?.why || '',
            item?.recommendation || '',
            item?.research_found || ''
        ].join(' ').toLowerCase();

        if (!haystack.trim()) {
            return false;
        }

        return /(dedicated ai business phone|dedicated business phone|business phone number|business phone|ai phone attendant|twilio phone|provision(?:ing)?\s+(?:a\s+)?phone|new phone number|add(?:ing)?\s+another phone number)/.test(haystack);
    }

    applyPhoneRecommendationPolicy(profile = null) {
        if (!profile || typeof profile !== 'object') {
            return profile;
        }

        const phoneStatus = this.getPhoneProvisioningStatus();
        if (!phoneStatus || !phoneStatus.has_phone) {
            return profile;
        }

        const normalized = JSON.parse(JSON.stringify(profile));
        const policyId = 'communication_existing_phone_policy';
        const phoneDisplay = (phoneStatus.phone_formatted || phoneStatus.phone_number || '').trim();
        const policyMessage = (phoneStatus.policy_message || 'VMO policy allows 1 business phone number per user by default.').trim();
        const exceptionMessage = (phoneStatus.exception_message || 'Need more than one number? Contact VMO directly for an approved exception.').trim();

        const policyItem = {
            id: policyId,
            label: phoneDisplay
                ? `Business Phone Already Active (${phoneDisplay})`
                : 'Business Phone Already Active',
            why: `${phoneDisplay
                ? `Your dashboard already shows an active business number (${phoneDisplay}).`
                : 'Your dashboard already shows an active business number.'} ${policyMessage} ${exceptionMessage} I’m not recommending another business phone number in this consultation.`,
            user_wanted: 'Avoid duplicate phone provisioning',
            research_found: phoneDisplay
                ? `An active business phone is already provisioned in your dashboard (${phoneDisplay}).`
                : 'An active business phone is already provisioned in your dashboard.',
            recommendation: 'Keep your existing number and focus on optimizing routing, AI handling, and CRM follow-up workflows.',
            default_choice: 'recommended',
            impact: 'high'
        };

        const categories = Array.isArray(normalized.categories) ? normalized.categories : [];
        const nextCategories = categories
            .map(category => {
                const nextItems = (Array.isArray(category?.items) ? category.items : []).filter(item => {
                    if (item && item.id === policyId) {
                        return false;
                    }
                    return !this.isExistingPhoneRecommendationItem(item);
                });
                return { ...category, items: nextItems };
            })
            .filter(category => Array.isArray(category.items) && category.items.length > 0);

        const communicationCategoryIndex = nextCategories.findIndex(category => {
            const key = String(category?.key || '').toLowerCase();
            const title = String(category?.title || '').toLowerCase();
            return key.includes('communication') || title.includes('communication');
        });

        if (communicationCategoryIndex >= 0) {
            const communicationCategory = nextCategories[communicationCategoryIndex];
            const existingItems = Array.isArray(communicationCategory.items) ? communicationCategory.items : [];
            nextCategories[communicationCategoryIndex] = {
                ...communicationCategory,
                items: [policyItem, ...existingItems]
            };
        } else {
            nextCategories.push({
                key: 'communication',
                title: '📞 INTELLIGENT COMMUNICATION SYSTEM (Never Miss an Opportunity)',
                items: [policyItem]
            });
        }

        normalized.categories = nextCategories.map(category => ({
            ...category,
            items: (Array.isArray(category.items) ? category.items : []).map(item => ({
                ...item,
                category: item?.category || category.title || 'Recommendations'
            }))
        }));
        normalized.items = normalized.categories.flatMap(category => category.items || []);

        const summaryNotice = 'Business phone note: A dashboard-provisioned number is already active, so no additional business phone number is recommended unless VMO approves an exception.';
        if (typeof normalized.summary === 'string' && normalized.summary.trim()) {
            if (!normalized.summary.includes('dashboard-provisioned number is already active')) {
                normalized.summary = `${normalized.summary} ${summaryNotice}`.trim();
            }
        } else {
            normalized.summary = summaryNotice;
        }

        return normalized;
    }

    /**
     * ============================================================================
     * DYNAMIC AI-POWERED RECOMMENDATIONS
     * Fetches personalized recommendations by comparing user desires vs research
     * ============================================================================
     */
    async fetchDynamicRecommendations(researchResults = {}) {
        const info = this.consultation_data.extracted_information || {};
        const AX = this.getAjax();
        
        try {
            this.addMessageToUI('ai', '🔄 Generating personalized recommendations based on your preferences and our research findings...');

            const nonce = AX.nonce || '';
            if (!nonce) {
                console.warn('Dynamic recommendations request is missing nonce; backend may reject this request.');
            }

            const payload = new URLSearchParams({
                action: 'vmo_generate_dynamic_recommendations',
                security: nonce,
                nonce,
                _ajax_nonce: nonce,
                user_info: JSON.stringify(info),
                research_results: JSON.stringify(researchResults)
            });
            
            const response = await fetch(AX.url || '/wp-admin/admin-ajax.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                credentials: 'same-origin',
                body: payload
            });
            
            let data = null;
            try {
                data = await response.json();
            } catch (_) {
                return { success: false, error: `Unexpected server response (${response.status})` };
            }
            
            if (data.success && data.data && data.data.recommendations) {
                return {
                    success: true,
                    profile: this.transformAIRecommendationsToProfile(data.data.recommendations, info)
                };
            } else if (data.data && data.data.raw_content) {
                // Try to parse raw content
                console.warn('Recommendations parsing failed, using raw content');
                return {
                    success: false,
                    fallback: true,
                    error: data.data.parse_error
                };
            }
            
            return { success: false, error: (data && data.data && data.data.message) ? data.data.message : 'Failed to generate recommendations' };
        } catch (err) {
            console.error('Error fetching dynamic recommendations:', err);
            return { success: false, error: err.message };
        }
    }

    /**
     * Transform AI recommendations response into the profile format for UI rendering
     */
    transformAIRecommendationsToProfile(aiRecs, info) {
        const bizName = info.businessName || info.business_name || 'your business';
        const industry = info.industry || 'your industry';
        
        // Transform categories to include comparison data
        const categories = (aiRecs.categories || []).map(cat => ({
            key: cat.key || 'category',
            title: cat.title || 'Recommendations',
            items: (cat.items || []).map(item => ({
                id: item.id || `rec_${Math.random().toString(36).substr(2, 9)}`,
                label: item.label || 'Recommendation',
                user_wanted: item.user_wanted || '',
                research_found: item.research_found || '',
                recommendation: item.recommendation || '',
                why: item.why || '',
                options: item.options || [
                    { id: 'original', label: 'Keep Original Plan', description: 'Use your original idea' },
                    { id: 'recommended', label: 'Use AI Recommendation', description: 'Follow the AI suggestion' },
                    { id: 'hybrid', label: 'Combine Both', description: 'Merge both approaches' }
                ],
                default_choice: item.default_choice || 'recommended',
                impact: item.impact || 'medium'
            }))
        }));
        
        // Add custom pages as a category if present
        if (aiRecs.custom_pages && aiRecs.custom_pages.length > 0) {
            categories.push({
                key: 'custom_pages',
                title: '📄 CUSTOM PAGES (Your Specific Requests)',
                items: aiRecs.custom_pages.map((page, idx) => ({
                    id: `custom_page_${idx}`,
                    label: page.page_requested,
                    user_wanted: `You specifically requested: ${page.page_requested}`,
                    research_found: `Feasibility: ${page.feasibility?.toUpperCase() || 'HIGH'}`,
                    recommendation: page.recommendation,
                    why: page.why,
                    options: [
                        { id: 'original', label: 'Create As Requested', description: 'Build exactly as you described' },
                        { id: 'recommended', label: 'Use AI Recommendation', description: page.recommendation },
                        { id: 'skip', label: 'Skip This Page', description: 'Don\'t include this page' }
                    ],
                    default_choice: page.feasibility === 'not_recommended' ? 'recommended' : 'original',
                    impact: page.feasibility === 'high' ? 'high' : 'medium'
                }))
            });
        }
        
        // Add leads info if present
        if (aiRecs.leads_info && aiRecs.leads_info.count > 0) {
            categories.unshift({
                key: 'leads',
                title: `🎯 INITIAL LEADS PACKAGE (${aiRecs.leads_info.count} Leads Ready)`,
                items: [{
                    id: 'leads_package',
                    label: `${aiRecs.leads_info.count} Qualified Leads in Your CRM`,
                    user_wanted: 'You wanted to launch with potential customers ready',
                    research_found: `Our deep research identified ${aiRecs.leads_info.count} ideal leads for your business`,
                    recommendation: 'Access these leads in your CRM dashboard immediately',
                    why: aiRecs.leads_info.summary || 'These leads match your target audience and are ready for outreach',
                    options: [
                        { id: 'recommended', label: 'Access Leads Now', description: 'View and export leads from CRM' }
                    ],
                    default_choice: 'recommended',
                    impact: 'high'
                }]
            });
        }
        
        const profile = {
            generated_at: new Date().toISOString(),
            business_name: bizName,
            industry,
            summary: aiRecs.summary || 'Based on your consultation and our research, here are your personalized recommendations.',
            categories: categories.filter(c => c.items && c.items.length > 0),
            items: categories.flatMap(c => c.items || []),
            is_dynamic: true // Flag to indicate this is AI-generated
        };

        return this.applyPhoneRecommendationPolicy(profile);
    }

    buildRecommendationProfile(researchResults = {}) {
        const info = this.consultation_data.extracted_information || {};
        const bizName = info.businessName || info.business_name || 'your business';
        const industry = info.industry || 'your industry';
        const audience = info.targetAudience || info.target_audience || 'your ideal customers';
        const goals = info.businessGoals || info.goals || 'your launch and growth goals';
        const style = info.preferredStyle || info.style || 'modern and professional';
        const colors = info.colors || info.brandColors || 'professional brand colors';
        const references = info.references || info.inspirationSites || '';
        
        // ✅ NEW: Extract custom pages and features requested by user
        const customPages = info.customPages || [];
        const specialFeatures = info.specialFeatures || [];
        
        // ✅ DEBUG: Log what we're working with
        console.log('📊 buildRecommendationProfile - extracted_information:', {
            businessName: bizName,
            industry,
            customPages,
            specialFeatures,
            pageDescriptions: info.pageDescriptions,
            businessGoals: goals,
            targetAudience: audience
        });
        
        // Extract competitor insights
        const competitors = researchResults.competitors_list || [];
        const topCompetitors = competitors.slice(0, 3).map(c => c.name || c).filter(Boolean);
        const competitorText = topCompetitors.length > 0 ? topCompetitors.join(', ') : 'local competitors';
        
        // ✅ NEW: Get leads count from research
        const leadsCount = researchResults.leads_generated_count || 0;
        
        // Check if e-commerce is needed
        const needsEcommerce = info.ecommerce_needed || info.needsEcommerce || 
                              (info.offerings || '').toLowerCase().includes('product') ||
                              (info.offerings || '').toLowerCase().includes('sell');
        
        // ✅ NEW: Check for affiliate/referral program needs
        const needsAffiliate = customPages.some(p => /affiliate|referral|partner/i.test(p)) ||
                              specialFeatures.some(f => /affiliate|referral|partner/i.test(f)) ||
                              (info.businessGoals || '').toLowerCase().includes('affiliate') ||
                              (info.businessGoals || '').toLowerCase().includes('referral');
        
        const improvement = (researchResults.gaps && researchResults.gaps[0]) || 'Add proof-driven sections (social proof, guarantees, and benefit-focused storytelling) to outperform competitors.';

        const normalizeId = (categoryKey, label, index) => {
            const base = (label || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
            return `${categoryKey || 'rec'}_${index}_${base || 'item'}`;
        };
        
        // ✅ NEW: Build dynamic custom pages items based on user's requests
        const buildCustomPagesItems = () => {
            const items = [];
            
            // Add user-specified custom pages
            if (customPages.length > 0) {
                const pageList = customPages.slice(0, 5).map(p => {
                    // Convert slug to readable name
                    return p.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
                }).join(', ');
                
                items.push({
                    label: `Custom Pages: ${pageList}`,
                    why: `Based on your specific requirements during our consultation, I'll create these custom pages: ${pageList}. Each page will be designed with your goals in mind, incorporating conversion elements and content tailored to ${audience}.`
                });
            }
            
            // Add affiliate/referral page if requested
            if (needsAffiliate) {
                items.push({
                    label: 'Affiliate/Referral Partner Program Pages',
                    why: `You mentioned wanting a referral program. I'll create: (1) A compelling Affiliate Signup page showcasing benefits of becoming a referral partner, (2) An earnings calculator so potential affiliates can see their potential income based on different plans, (3) A Partner Dashboard where affiliates can track signups, commissions, and payouts.`
                });
            }
            
            // Add FAQ if not already in custom pages
            if (!customPages.some(p => /faq|question|answer/i.test(p))) {
                items.push({
                    label: 'FAQ & Knowledge Base',
                    why: `A comprehensive FAQ page addresses common questions before they become support tickets. I'll create AI-optimized FAQ content based on typical questions in ${industry}, reducing your support burden while building trust with ${audience}.`
                });
            }
            
            // Add special features
            if (specialFeatures.length > 0) {
                for (const feature of specialFeatures.slice(0, 3)) {
                    const featureName = feature.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
                    items.push({
                        label: `Special Feature: ${featureName}`,
                        why: `As you requested, I'll implement a ${featureName} to enhance your website's functionality and user experience.`
                    });
                }
            }
            
            return items;
        };
        
        // ✅ NEW: Build leads notification category if leads were generated
        const buildLeadsCategory = () => {
            if (leadsCount === 0) return null;
            
            return {
                key: 'leads',
                title: '🎯 INITIAL LEADS PACKAGE (Ready for Outreach)',
                items: [
                    {
                        label: `${leadsCount} Qualified Leads in Your CRM`,
                        why: `Based on deep market research for ${industry}, I've identified ${leadsCount} ideal target leads matching your customer profile. These leads are already saved in your CRM dashboard under the "Leads" section. Each lead includes: company/person name, industry, why they're a perfect fit, and recommended outreach approach. You can begin marketing to them immediately after setup.`
                    },
                    {
                        label: 'Lead Scoring & Prioritization',
                        why: `Each lead has been scored based on likelihood to convert. Hot leads (ready to buy now) are marked as high priority, while warm and cold leads are categorized for nurture sequences. Focus on high-priority leads first for fastest results.`
                    }
                ]
            };
        };

        // ✅ ENHANCED: More comprehensive, enthusiastic recommendations
        // Build the list of pages for the first item dynamically
        const allPagesForLabel = ['Homepage', 'About', 'Services/Products', 'Contact'];
        if (customPages.length > 0) {
            // Add custom pages to the list
            customPages.slice(0, 4).forEach(p => {
                const pageName = p.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
                if (!allPagesForLabel.some(existing => existing.toLowerCase() === pageName.toLowerCase())) {
                    allPagesForLabel.push(pageName);
                }
            });
        }
        const pagesListText = allPagesForLabel.join(', ');
        const totalPages = Math.max(5, allPagesForLabel.length);
        
        const categories = [
            {
                key: 'website',
                title: '🌐 CUSTOM WEBSITE EXPERIENCE (Tailored to Your Vision)',
                items: [
                    {
                        label: `Premium ${style} Website (${totalPages}+ Custom Pages)`,
                        why: `Based on your preferences for a ${style} design and your specific page requirements, I'll create a stunning website that immediately positions ${bizName} as the premium choice in ${industry}. Your site will include: ${pagesListText}${customPages.length > 4 ? ', and more' : ''}. Each page will be crafted to convert visitors into customers. Unlike ${competitorText} who use generic templates, yours will be custom-built to match your exact specifications.`
                    },
                    // ✅ ADD: Include user's custom pages in the website section
                    ...buildCustomPagesItems(),
                    {
                        label: 'Lightning-Fast Mobile Experience',
                        why: `Research shows ${competitorText} have slow mobile sites (3-5 second load times). Your site will load in under 2 seconds, giving you a massive competitive advantage since 70% of ${audience} browse on mobile. This alone can increase conversions by 25-40%.`
                    },
                    {
                        label: `Strategic Color Psychology (${colors || 'Custom Palette'})`,
                        why: `Your brand colors aren't just aesthetic—they're strategic. ${colors ? `The ${colors} palette you prefer` : 'Your custom color palette'} will be optimized using color psychology principles to evoke trust, professionalism, and action. Each color placement (CTAs, headers, accents) is designed to guide visitors toward conversion.`
                    },
                    {
                        label: 'Conversion-Optimized Copy & CTAs',
                        why: `Every word on your site will be crafted to move ${audience} toward action. Based on research of ${competitorText}, I've identified messaging gaps we'll exploit. Clear calls-to-action on every page, benefit-focused headlines, and trust-building content that addresses your audience's specific pain points.`
                    }
                ]
            },
            // ✅ ADD: Leads category if leads were generated
            buildLeadsCategory(),
            {
                key: 'ecommerce',
                title: '🛒 E-COMMERCE & PAYMENTS (Your Revenue Engine)',
                items: [
                    {
                        label: 'VMO Payments + Smart Checkout',
                        why: `Your customers expect seamless purchasing. VMO Payments provides instant, PCI-compliant payment processing with automatic invoicing, receipt generation, and dashboard tracking. Unlike third-party solutions, everything stays integrated—see every transaction, refund, and subscription in one place. Lower fees than competitors and faster checkout means more completed purchases.`
                    },
                    {
                        label: 'Product/Service Catalog Management',
                        why: `Easily manage your offerings with our intuitive product system. Add unlimited products/services, set pricing tiers, create bundles, and manage inventory. Each listing is SEO-optimized to attract organic traffic. ${needsEcommerce ? 'Essential for your e-commerce needs.' : 'Ready when you need it.'}`
                    },
                    {
                        label: 'Subscription & Recurring Revenue',
                        why: `Build predictable income with subscription options. Offer membership tiers, recurring services, or subscription boxes. Automatic billing, renewal reminders, and churn prevention built in. This is how modern businesses in ${industry} build sustainable revenue.`
                    }
                ]
            },
            {
                key: 'communication',
                title: '📞 INTELLIGENT COMMUNICATION SYSTEM (Never Miss an Opportunity)',
                items: [
                    {
                        label: 'Dedicated AI Business Phone',
                        why: `This is a game-changer for ${bizName}. You'll get a dedicated business phone number that lives in your dashboard—not your personal phone. Why this matters: Your AI phone attendant answers 24/7, qualifies leads, books appointments, and routes urgent calls to you. Every call is logged, optionally recorded for quality assurance, and summarized by AI. The system learns caller patterns and recommends follow-up actions. When you open a customer profile, you'll see every call, email, and chat in one timeline. The AI even suggests "retention actions" based on communication history—like reminding you to send a thank-you or follow up on a concern. This alone can increase customer retention by 30%.`
                    },
                    {
                        label: 'Intelligent CRM + Lead Capture',
                        why: `Every interaction with ${audience} is automatically captured and organized. When someone visits your site, fills a form, calls, emails, or chats—it all flows into one unified profile. The AI analyzes each contact's journey and recommends personalized actions: "This lead visited your pricing page 3 times—send the special offer." "This customer's last purchase was 60 days ago—send a re-engagement email." Your CRM becomes an active business partner, not just a database.`
                    },
                    {
                        label: 'Multi-Channel Chat (Web + WhatsApp + SMS)',
                        why: `Meet ${audience} wherever they are. Your website live chat, WhatsApp, and SMS all connect to one inbox. AI handles common questions instantly, qualifies leads, and escalates complex issues to you. After-hours inquiries are captured and prioritized for morning follow-up. ${competitorText} lose leads by forcing customers to wait—you won't.`
                    }
                ]
            },
            {
                key: 'email',
                title: '📧 EMAIL MARKETING & AUTOMATION (Your 24/7 Sales Team)',
                items: [
                    {
                        label: 'Professional Email Setup & Templates',
                        why: `Get a complete email system: branded templates for newsletters, promotions, and transactional emails. Your email domain will be properly configured (SPF, DKIM, DMARC) so emails actually reach inboxes—not spam folders. Pre-designed templates match your website for brand consistency.`
                    },
                    {
                        label: 'Automated Nurture Sequences',
                        why: `Turn cold leads into customers automatically. When someone joins your list, they enter a carefully crafted sequence: Welcome → Value → Proof → Offer → Follow-up. Each email is timed and personalized based on behavior. Research shows ${competitorText} don't do this—giving you a significant advantage in conversion rates.`
                    },
                    {
                        label: 'Abandoned Cart & Re-engagement Campaigns',
                        why: `Recover lost revenue automatically. When someone abandons checkout or goes quiet, trigger re-engagement emails with personalized offers. Industry data shows this can recover 15-25% of otherwise lost sales.`
                    }
                ]
            },
            {
                key: 'seo',
                title: '🔍 SEO & CONTENT STRATEGY (Dominate Search Results)',
                items: [
                    {
                        label: 'Comprehensive SEO Foundation',
                        why: `Your website will be built with SEO baked in from day one: optimized meta tags, structured data (Schema.org), fast loading speeds, mobile-first design, and clean URL structures. Based on keyword research, I've identified terms ${competitorText} are ranking for—and gaps where you can outrank them quickly.`
                    },
                    {
                        label: 'Local SEO Optimization',
                        why: `Dominate local search with Google Business Profile optimization, local citations, and location-specific landing pages. When ${audience} search for "${industry} near me", you'll be at the top. This is often the fastest path to new customers.`
                    },
                    {
                        label: 'Content Blueprint & Blog Strategy',
                        why: `Get a 90-day content plan targeting keywords your competitors miss. Each article positions you as the authority in ${industry}. Topics are selected based on search volume, competition level, and buyer intent. Content marketing compounds—start now and dominate later.`
                    }
                ]
            },
            {
                key: 'legal',
                title: '⚖️ LEGAL & COMPLIANCE (Protect Your Business)',
                items: [
                    {
                        label: 'Privacy Policy & Terms of Service',
                        why: `Get professionally generated legal documents customized for ${bizName}. Your Privacy Policy covers GDPR, CCPA, and general data protection. Terms of Service protect your business interests. These aren't generic templates—they're generated based on your specific business type and offerings.`
                    },
                    {
                        label: 'ADA Accessibility Compliance',
                        why: `Your website will meet WCAG 2.1 accessibility standards. Beyond being legally prudent (avoiding lawsuits), this expands your audience to include users with disabilities. Accessible sites also tend to rank better in search engines.`
                    },
                    {
                        label: 'Cookie Consent & Data Management',
                        why: `Proper cookie consent banners and data management ensure you're compliant with international regulations. Visitors see professional consent notices, and you have clear records of consent for legal protection.`
                    }
                ]
            },
            {
                key: 'analytics',
                title: '📊 ANALYTICS & INTELLIGENCE (Know What Works)',
                items: [
                    {
                        label: 'Real-Time KPI Dashboard',
                        why: `See everything that matters at a glance: website traffic, conversion rates, revenue, email performance, phone calls, appointments, and customer satisfaction. No more guessing—make decisions based on data. Weekly AI-generated insights highlight what works and what needs attention.`
                    },
                    {
                        label: 'Visitor Behavior Tracking',
                        why: `Understand exactly how ${audience} interact with your site. See heatmaps, scroll depth, click patterns, and conversion funnels. Identify where visitors drop off and optimize those points. This intelligence continuously improves your site's performance.`
                    },
                    {
                        label: 'Competitor Monitoring',
                        why: `Keep tabs on ${competitorText} automatically. Get alerts when they change pricing, launch new offerings, or update their websites. Stay one step ahead in your market.`
                    }
                ]
            }
        ].filter(cat => cat !== null).map(category => {
            const mappedItems = category.items.map((item, index) => ({
                id: normalizeId(category.key, item.label, index),
                label: item.label,
                why: item.why,
                category: category.title
            }));
            return { ...category, items: mappedItems };
        });

        const profile = {
            generated_at: new Date().toISOString(),
            business_name: bizName,
            industry,
            audience,
            goals,
            summary: improvement,
            categories,
            items: categories.flatMap(category => category.items)
        };

        return this.applyPhoneRecommendationPolicy(profile);
    }

    /**
     * ✅ NEW: Build comprehensive market intelligence introduction for recommendations
     * Includes: industry confirmation, global/local market values, top earners, target market, leads, and product pages
     */
    buildMarketIntelligenceIntro(researchData = {}, extractedInfo = {}) {
        const bizName = extractedInfo.businessName || 'Your Business';
        const userIndustry = extractedInfo.industry || 'your industry';
        const location = extractedInfo.location || 'your region';
        const isEcommerce = extractedInfo.needsEcommerce || extractedInfo.ecommerce_needed || false;
        const offerings = Array.isArray(extractedInfo.offerings) ? extractedInfo.offerings : [];
        const customPages = extractedInfo.customPages || [];
        
        const structuredIntel = this.getStructuredMarketIntel(researchData);

        const industryConfirmation = this.formatResearchValue(
            structuredIntel.industry_confirmation || structuredIntel.confirmed_industry || researchData.industry_confirmation || userIndustry
        ) || userIndustry;
        const marketSize = this.hasMeaningfulResearchValue(structuredIntel.global_market_size)
            ? structuredIntel.global_market_size
            : (this.hasMeaningfulResearchValue(researchData.market_size) ? researchData.market_size : null);
        const marketYear = structuredIntel.global_market_year || '';
        const localMarketSize = this.hasMeaningfulResearchValue(structuredIntel.local_market_size)
            ? structuredIntel.local_market_size
            : (this.hasMeaningfulResearchValue(researchData.local_market_size) ? researchData.local_market_size : null);
        const localMarketYear = structuredIntel.local_market_year || '';
        const marketGrowth = this.hasMeaningfulResearchValue(structuredIntel.market_growth_rate)
            ? structuredIntel.market_growth_rate
            : (this.hasMeaningfulResearchValue(researchData.market_growth_rate) ? researchData.market_growth_rate : null);
        const marketProjection = this.hasMeaningfulResearchValue(structuredIntel.market_projection)
            ? structuredIntel.market_projection
            : null;

        let leadsCount = Number(researchData.leads_generated_count || structuredIntel.leads_generated_count || 0);
        if (!Number.isFinite(leadsCount) || leadsCount < 0) {
            leadsCount = 0;
        }

        const competitorGroups = this.getResearchCompetitorGroups(researchData, structuredIntel);
        const marketShareHolders = competitorGroups.direct;
        const indirectHolders = competitorGroups.indirect;
        const marketPosition = structuredIntel.market_position || researchData.market_position || {};
        const positioningHighlights = this.extractResearchHighlights(
            researchData.market_positioning || researchData.comparative_matrix,
            3
        );
        const marketOpportunityHighlights = this.extractResearchHighlights(
            researchData.market_trends || researchData.market_gaps || researchData.opportunities_threats,
            3
        );

        const idealTargetFromIntel = structuredIntel.ideal_target_market || researchData.ideal_target_market || {};
        const targetMarketIntel = researchData.target_market_intelligence || {};
        const idealTarget = typeof idealTargetFromIntel === 'object' && Object.keys(idealTargetFromIntel).length > 0
            ? idealTargetFromIntel
            : (typeof targetMarketIntel === 'object' && targetMarketIntel.analysis
                ? targetMarketIntel.analysis
                : (typeof targetMarketIntel === 'string' ? targetMarketIntel : ''));
        const targetSegments = Array.isArray(structuredIntel.target_customer_segments)
            ? structuredIntel.target_customer_segments.slice(0, 3)
            : [];
        const validTargetSegments = targetSegments.filter(segment => (
            segment
            && typeof segment === 'object'
            && this.hasMeaningfulResearchValue(segment.segment || segment.name || segment.size || segment.acquisition_channel)
        ));

        const bulkBuyersFromIntel = structuredIntel.bulk_buyer_opportunities || [];
        const bulkBuyers = Array.isArray(bulkBuyersFromIntel) && bulkBuyersFromIntel.length > 0
            ? bulkBuyersFromIntel
            : (researchData.bulk_buyer_opportunities || researchData.b2b_opportunities || []);
        
        // Build intro HTML sections
        let introHtml = '';
        
        // 1. Market Intelligence Report Header - ✅ IMPROVED: Explains what VMO will do
        introHtml += `
            <div class="vmo-rec-intel-section">
                <div class="vmo-rec-intel-header">🚀 Your Business Success Blueprint</div>
                <div class="vmo-rec-intel-subheader">Based on our research, here's how <strong>VMO</strong> will position <strong>${this.escapeHtml(bizName)}</strong> to compete and win in your market</div>
                <div class="vmo-rec-intel-intro-text">The items below show what we discovered about your industry and competition. Each recommendation in the checklist is specifically designed to help you outperform these competitors and capture your share of this market.</div>
            </div>
        `;
        
        // 2. Industry Confirmation + Market Position Projections - wrapped in accordion
        let positionHtml = '';
        if (marketPosition && typeof marketPosition === 'object' && Object.keys(marketPosition).length > 0) {
            const mp = marketPosition;
            positionHtml += '<div class="vmo-rec-market-position">';
            if (this.hasMeaningfulResearchValue(mp.current_status)) {
                positionHtml += `<div class="vmo-rec-position-item"><span class="vmo-rec-position-badge vmo-pos-current">Now</span><div><strong>Current Position:</strong> ${this.escapeHtml(this.formatResearchValue(mp.current_status))}</div></div>`;
            }
            if (this.hasMeaningfulResearchValue(mp.potential_position)) {
                positionHtml += `<div class="vmo-rec-position-item"><span class="vmo-rec-position-badge vmo-pos-potential">Potential</span><div><strong>Where You Could Be:</strong> ${this.escapeHtml(this.formatResearchValue(mp.potential_position))}</div></div>`;
            }
            if (this.hasMeaningfulResearchValue(mp.short_term_6mo)) {
                positionHtml += `<div class="vmo-rec-position-item"><span class="vmo-rec-position-badge vmo-pos-short">6 Mo</span><div><strong>Short-Term (6 months):</strong> ${this.escapeHtml(this.formatResearchValue(mp.short_term_6mo))}</div></div>`;
            }
            if (this.hasMeaningfulResearchValue(mp.mid_term_2_3yr)) {
                positionHtml += `<div class="vmo-rec-position-item"><span class="vmo-rec-position-badge vmo-pos-mid">2-3 Yr</span><div><strong>Mid-Term (2-3 years):</strong> ${this.escapeHtml(this.formatResearchValue(mp.mid_term_2_3yr))}</div></div>`;
            }
            if (this.hasMeaningfulResearchValue(mp.long_term_5yr_plus)) {
                positionHtml += `<div class="vmo-rec-position-item"><span class="vmo-rec-position-badge vmo-pos-long">5+ Yr</span><div><strong>Long-Term (5+ years):</strong> ${this.escapeHtml(this.formatResearchValue(mp.long_term_5yr_plus))}</div></div>`;
            }
            positionHtml += '</div>';
        }

        if (positioningHighlights.length > 0) {
            positionHtml += `
                <div class="vmo-rec-intel-summary-list">
                    <div class="vmo-rec-intel-summary-title">Positioning signals pulled from the full research:</div>
                    ${positioningHighlights.map(item => `<div class="vmo-rec-intel-summary-item">${this.escapeHtml(item)}</div>`).join('')}
                </div>
            `;
        }

        introHtml += `
            <details class="vmo-rec-accordion" open>
                <summary class="vmo-rec-accordion-header">
                    <span class="vmo-rec-accordion-icon">🏢</span>
                    <span class="vmo-rec-accordion-title">Your Industry & Market Position</span>
                    <span class="vmo-rec-accordion-toggle">▼</span>
                </summary>
                <div class="vmo-rec-accordion-content">
                    <div class="vmo-rec-intel-card">
                        <div class="vmo-rec-intel-content">
                            <div class="vmo-rec-intel-label">Confirmed Industry</div>
                            <div class="vmo-rec-intel-value">${this.escapeHtml(industryConfirmation)}</div>
                            ${location && location !== 'Not specified' ? `<div class="vmo-rec-intel-sublabel">Operating in: ${this.escapeHtml(location)}</div>` : ''}
                            ${positionHtml}
                            <div class="vmo-rec-intel-why"><strong>Why this matters:</strong> Understanding your exact industry and position helps us target the right keywords, competitors, and customers for your website and marketing. The projections above show a realistic growth trajectory based on market data.</div>
                        </div>
                    </div>
                </div>
            </details>
        `;
        
        // 3. Global & Local Market Values with specific dollar amounts
        if (
            this.hasMeaningfulResearchValue(marketSize)
            || this.hasMeaningfulResearchValue(localMarketSize)
            || this.hasMeaningfulResearchValue(marketGrowth)
            || this.hasMeaningfulResearchValue(marketProjection)
            || marketOpportunityHighlights.length > 0
        ) {
            introHtml += `
            <details class="vmo-rec-accordion">
                <summary class="vmo-rec-accordion-header">
                    <span class="vmo-rec-accordion-icon">💰</span>
                    <span class="vmo-rec-accordion-title">Market Size & Opportunity (Click to expand)</span>
                    <span class="vmo-rec-accordion-toggle">▼</span>
                </summary>
                <div class="vmo-rec-accordion-content">
                    <div class="vmo-rec-intel-card vmo-rec-intel-market">
                        <div class="vmo-rec-intel-content">
                            <div class="vmo-rec-intel-label">How Big Is This Market?</div>
                            ${this.hasMeaningfulResearchValue(marketSize) ? `<div class="vmo-rec-intel-value-row"><span class="vmo-rec-intel-tag">🌍 Worldwide:</span> <strong class="vmo-rec-highlight-value">${this.escapeHtml(this.formatResearchValue(marketSize))}</strong>${marketYear ? ` (${this.escapeHtml(marketYear)})` : ''}</div>` : ''}
                            ${this.hasMeaningfulResearchValue(localMarketSize) ? `<div class="vmo-rec-intel-value-row"><span class="vmo-rec-intel-tag">📍 In ${this.escapeHtml(location)}:</span> <strong class="vmo-rec-highlight-value">${this.escapeHtml(this.formatResearchValue(localMarketSize))}</strong>${localMarketYear ? ` (${this.escapeHtml(localMarketYear)})` : ''}</div>` : ''}
                            ${this.hasMeaningfulResearchValue(marketGrowth) ? `<div class="vmo-rec-intel-value-row"><span class="vmo-rec-intel-tag">📈 Growing At:</span> <strong>${this.escapeHtml(this.formatResearchValue(marketGrowth))}</strong> per year</div>` : ''}
                            ${this.hasMeaningfulResearchValue(marketProjection) ? `<div class="vmo-rec-intel-value-row"><span class="vmo-rec-intel-tag">🔮 Future Projection:</span> ${this.escapeHtml(this.formatResearchValue(marketProjection))}</div>` : ''}
                            ${marketOpportunityHighlights.length > 0 ? `
                                <div class="vmo-rec-intel-summary-list">
                                    <div class="vmo-rec-intel-summary-title">Opportunity context from the research:</div>
                                    ${marketOpportunityHighlights.map(item => `<div class="vmo-rec-intel-summary-item">${this.escapeHtml(item)}</div>`).join('')}
                                </div>
                            ` : ''}
                            <div class="vmo-rec-intel-why"><strong>What this means for you:</strong> There's serious money in this industry! Even capturing just 0.1% of this market could be substantial. VMO will help you get your slice.</div>
                        </div>
                    </div>
                </div>
            </details>
            `;
        }
        
        // 4. Top Market Share Holders with Net Worth, Revenue, Weakness & How to Beat - ✅ ENHANCED with accordion
        if (marketShareHolders.length > 0) {
            const holdersHtml = marketShareHolders.map((holder, idx) => {
                const financials = [];
                if (this.hasMeaningfulResearchValue(holder.worth)) financials.push(`<div class="vmo-rec-intel-fin-item"><span class="vmo-rec-fin-label">💵 Valuation:</span> <span class="vmo-rec-fin-value">${this.escapeHtml(this.formatResearchValue(holder.worth))}</span></div>`);
                if (this.hasMeaningfulResearchValue(holder.revenue)) financials.push(`<div class="vmo-rec-intel-fin-item"><span class="vmo-rec-fin-label">📊 Yearly Income:</span> <span class="vmo-rec-fin-value">${this.escapeHtml(this.formatResearchValue(holder.revenue))}</span></div>`);
                if (this.hasMeaningfulResearchValue(holder.marketShare)) financials.push(`<div class="vmo-rec-intel-fin-item"><span class="vmo-rec-fin-label">🥧 Market Share:</span> <span class="vmo-rec-fin-value">${this.escapeHtml(this.formatResearchValue(holder.marketShare))}</span></div>`);
                const financialsStr = financials.length > 0 ? `<div class="vmo-rec-intel-holder-financials">${financials.join('')}</div>` : '<div class="vmo-rec-intel-no-data">(Financial data not publicly available)</div>';
                const descStr = holder.description ? `<div class="vmo-rec-intel-holder-desc"><em>${this.escapeHtml(holder.description)}</em></div>` : '';
                const typeBadge = holder.competitorType === 'indirect' 
                    ? '<span class="vmo-rec-comp-type-badge vmo-comp-indirect">Indirect</span>' 
                    : '<span class="vmo-rec-comp-type-badge vmo-comp-direct">Direct</span>';
                const weaknessStr = holder.weakness ? `<div class="vmo-rec-intel-weakness"><span class="vmo-rec-weakness-label">⚠️ Weakness:</span> ${this.escapeHtml(holder.weakness)}</div>` : '';
                const beatStr = holder.howToBeat ? `<div class="vmo-rec-intel-beat"><span class="vmo-rec-beat-label">🎯 How to Beat Them:</span> ${this.escapeHtml(holder.howToBeat)}</div>` : '';
                const websiteStr = holder.website ? `<div class="vmo-rec-intel-source-link"><span class="vmo-rec-source-link-label">🔗 Website:</span> <a href="${this.escapeHtmlAttr(holder.website)}" target="_blank" rel="noopener noreferrer">${this.escapeHtml(holder.website.replace(/^https?:\/\//i, ''))}</a></div>` : '';
                return `<div class="vmo-rec-intel-holder-card">
                    <div class="vmo-rec-intel-holder-header">
                        <span class="vmo-rec-intel-rank-badge">#${idx + 1}</span>
                        <span class="vmo-rec-intel-holder-name">${this.escapeHtml(holder.name)}</span>
                        ${typeBadge}
                    </div>
                    ${descStr}
                    ${websiteStr}
                    ${financialsStr}
                    ${weaknessStr}
                    ${beatStr}
                </div>`;
            }).join('');
            
            introHtml += `
            <details class="vmo-rec-accordion">
                <summary class="vmo-rec-accordion-header">
                    <span class="vmo-rec-accordion-icon">🏆</span>
                    <span class="vmo-rec-accordion-title">Your Top Competitors (Click to see who you're up against)</span>
                    <span class="vmo-rec-accordion-toggle">▼</span>
                </summary>
                <div class="vmo-rec-accordion-content">
                    <div class="vmo-rec-intel-intro-explain">
                        <p>🎯 <strong>Here's who dominates your industry right now.</strong> Don't be intimidated — every one of these started small. VMO's recommendations are designed to help you compete against (and eventually outpace) these players.</p>
                    </div>
                    <div class="vmo-rec-intel-holders-grid">${holdersHtml}</div>
                    <div class="vmo-rec-intel-why"><strong>How VMO helps:</strong> We'll analyze what these competitors do well (and poorly) to craft a strategy that differentiates your brand and captures customers they're missing.</div>
                </div>
            </details>
            `;
        }
        
        // 4b. Indirect/Secondary Competitors accordion - ✅ NEW
        if (indirectHolders.length > 0) {
            const indirectHtml = indirectHolders.map((holder) => {
                const threatColor = holder.threatLevel === 'high' ? '#ef4444' : holder.threatLevel === 'medium' ? '#f59e0b' : '#22c55e';
                const financials = [];
                if (this.hasMeaningfulResearchValue(holder.worth)) financials.push(`<div class="vmo-rec-intel-fin-item"><span class="vmo-rec-fin-label">💵 Valuation:</span> <span class="vmo-rec-fin-value">${this.escapeHtml(this.formatResearchValue(holder.worth))}</span></div>`);
                if (this.hasMeaningfulResearchValue(holder.revenue)) financials.push(`<div class="vmo-rec-intel-fin-item"><span class="vmo-rec-fin-label">📊 Revenue:</span> <span class="vmo-rec-fin-value">${this.escapeHtml(this.formatResearchValue(holder.revenue))}</span></div>`);
                const financialsStr = financials.length > 0 ? `<div class="vmo-rec-intel-holder-financials">${financials.join('')}</div>` : '';
                const overlapStr = holder.overlap ? `<div class="vmo-rec-intel-overlap"><span class="vmo-rec-overlap-label">🔄 Service Overlap:</span> ${this.escapeHtml(holder.overlap)}</div>` : '';
                const vulnStr = holder.vulnerability ? `<div class="vmo-rec-intel-weakness"><span class="vmo-rec-weakness-label">⚠️ Vulnerability:</span> ${this.escapeHtml(holder.vulnerability)}</div>` : '';
                const websiteStr = holder.website ? `<div class="vmo-rec-intel-source-link"><span class="vmo-rec-source-link-label">🔗 Website:</span> <a href="${this.escapeHtmlAttr(holder.website)}" target="_blank" rel="noopener noreferrer">${this.escapeHtml(holder.website.replace(/^https?:\/\//i, ''))}</a></div>` : '';
                return `<div class="vmo-rec-intel-holder-card vmo-indirect-card">
                    <div class="vmo-rec-intel-holder-header">
                        <span class="vmo-rec-comp-type-badge vmo-comp-indirect">Indirect</span>
                        <span class="vmo-rec-intel-holder-name">${this.escapeHtml(holder.name)}</span>
                        <span class="vmo-rec-threat-badge" style="color:${threatColor};border-color:${threatColor}">${this.escapeHtml(holder.threatLevel)} threat</span>
                    </div>
                    ${holder.description ? `<div class="vmo-rec-intel-holder-desc"><em>${this.escapeHtml(holder.description)}</em></div>` : ''}
                    ${websiteStr}
                    ${financialsStr}
                    ${overlapStr}
                    ${vulnStr}
                </div>`;
            }).join('');
            
            introHtml += `
            <details class="vmo-rec-accordion">
                <summary class="vmo-rec-accordion-header">
                    <span class="vmo-rec-accordion-icon">🔍</span>
                    <span class="vmo-rec-accordion-title">Indirect / Secondary Competitors (Click to expand)</span>
                    <span class="vmo-rec-accordion-toggle">▼</span>
                </summary>
                <div class="vmo-rec-accordion-content">
                    <div class="vmo-rec-intel-intro-explain">
                        <p>🔄 <strong>These aren't direct competitors, but they eat into your market.</strong> They offer overlapping services or products that could divert your potential customers. Understanding them is key to positioning yourself correctly.</p>
                    </div>
                    <div class="vmo-rec-intel-holders-grid">${indirectHtml}</div>
                    <div class="vmo-rec-intel-why"><strong>Strategic insight:</strong> While these companies aren't competing head-to-head with you, their presence affects your market share. Our recommendations account for their influence.</div>
                </div>
            </details>
            `;
        }
        
        // 5. Ideal Target Market with Bulk Buyer Opportunities
        let targetHtml = '';
        
        // Handle structured ideal target market object
        if (typeof idealTarget === 'object' && (idealTarget.primary || idealTarget.secondary || idealTarget.bulk_buyers)) {
            targetHtml += '<div class="vmo-rec-intel-target-grid">';
            if (idealTarget.primary) {
                targetHtml += `<div class="vmo-rec-intel-target-item"><strong>Primary Target:</strong> ${this.escapeHtml(idealTarget.primary)}</div>`;
            }
            if (idealTarget.secondary) {
                targetHtml += `<div class="vmo-rec-intel-target-item"><strong>Secondary Target:</strong> ${this.escapeHtml(idealTarget.secondary)}</div>`;
            }
            if (idealTarget.bulk_buyers) {
                targetHtml += `<div class="vmo-rec-intel-target-item"><strong>💼 Bulk/B2B Buyers:</strong> ${this.escapeHtml(idealTarget.bulk_buyers)}</div>`;
            }
            if (idealTarget.underserved_segments) {
                targetHtml += `<div class="vmo-rec-intel-target-item"><strong>Underserved Opportunity:</strong> ${this.escapeHtml(idealTarget.underserved_segments)}</div>`;
            }
            targetHtml += '</div>';
        } else if (typeof idealTarget === 'string' && idealTarget) {
            const displayTarget = idealTarget.length > 400 ? idealTarget.slice(0, 400) + '...' : idealTarget;
            targetHtml += `<div class="vmo-rec-intel-value-text">${this.escapeHtml(displayTarget)}</div>`;
        }

        if (validTargetSegments.length > 0) {
            targetHtml += '<div class="vmo-rec-intel-segment-grid"><div class="vmo-rec-intel-bulk-title">Audience segments identified in the research</div>';
            validTargetSegments.forEach(segment => {
                const meta = [
                    this.formatResearchValue(segment.size) ? `Size: ${this.formatResearchValue(segment.size)}` : '',
                    this.formatResearchValue(segment.avg_deal_value) ? `Avg deal value: ${this.formatResearchValue(segment.avg_deal_value)}` : '',
                    this.formatResearchValue(segment.acquisition_channel) ? `Best channel: ${this.formatResearchValue(segment.acquisition_channel)}` : ''
                ].filter(Boolean);
                targetHtml += `
                    <div class="vmo-rec-intel-segment-item">
                        <strong>${this.escapeHtml(this.formatResearchValue(segment.segment || 'Customer segment'))}</strong>
                        ${meta.length > 0 ? `<div class="vmo-rec-intel-segment-meta">${this.escapeHtml(meta.join(' • '))}</div>` : ''}
                    </div>
                `;
            });
            targetHtml += '</div>';
        }
        
        // Add bulk buyer opportunities if available as array
        if (Array.isArray(bulkBuyers) && bulkBuyers.length > 0) {
            targetHtml += '<div class="vmo-rec-intel-bulk-section"><div class="vmo-rec-intel-bulk-title">💼 High-Volume/B2B Opportunities:</div>';
            bulkBuyers.forEach(opp => {
                if (typeof opp === 'object') {
                    targetHtml += `<div class="vmo-rec-intel-bulk-item">
                        <strong>${this.escapeHtml(opp.segment || 'Opportunity')}:</strong> 
                        ${opp.potential ? this.escapeHtml(opp.potential) : ''} 
                        ${opp.approach ? `<span class="vmo-rec-intel-approach">→ ${this.escapeHtml(opp.approach)}</span>` : ''}
                    </div>`;
                } else if (opp) {
                    targetHtml += `<div class="vmo-rec-intel-bulk-item">${this.escapeHtml(String(opp))}</div>`;
                }
            });
            targetHtml += '</div>';
        }
        
        if (targetHtml) {
            introHtml += `
                <div class="vmo-rec-intel-card vmo-rec-intel-target">
                    <div class="vmo-rec-intel-icon">🎯</div>
                    <div class="vmo-rec-intel-content">
                        <div class="vmo-rec-intel-label">Ideal Target Market</div>
                        ${targetHtml}
                    </div>
                </div>
            `;
        }
        
        // 6. Leads Generated (CRM reference)
        if (leadsCount > 0) {
            introHtml += `
                <div class="vmo-rec-intel-card vmo-rec-intel-leads">
                    <div class="vmo-rec-intel-icon">📋</div>
                    <div class="vmo-rec-intel-content">
                        <div class="vmo-rec-intel-label">Leads Collected</div>
                        <div class="vmo-rec-intel-value-large">${leadsCount} Qualified Leads</div>
                        <div class="vmo-rec-intel-sublabel">Names, phone numbers, email addresses, and physical addresses have been collected and saved.</div>
                        <div class="vmo-rec-intel-highlight">📍 Find them in your <strong>CRM Dashboard</strong> after setup is complete.</div>
                    </div>
                </div>
            `;
        }
        
        // 7. Product/E-commerce Pages to be Generated
        if (isEcommerce || offerings.length > 0) {
            const productsToGenerate = offerings.slice(0, 5);
            const pagesHtml = [];
            
            if (isEcommerce) {
                pagesHtml.push('<div class="vmo-rec-intel-page-item">🛒 <strong>Shop/Store Page</strong> — Full e-commerce catalog with cart functionality</div>');
                pagesHtml.push('<div class="vmo-rec-intel-page-item">📦 <strong>Product Detail Pages</strong> — Individual pages for each product with images, pricing, and buy buttons</div>');
                pagesHtml.push('<div class="vmo-rec-intel-page-item">🏷️ <strong>Category Pages</strong> — Organized browsing by product categories</div>');
            }
            
            if (productsToGenerate.length > 0) {
                pagesHtml.push(`<div class="vmo-rec-intel-page-item">📝 <strong>Products/Services Listed:</strong> ${productsToGenerate.map(p => this.escapeHtml(String(p))).join(', ')}${offerings.length > 5 ? ` (+${offerings.length - 5} more)` : ''}</div>`);
            }
            
            // Check for custom pages related to products
            const productRelatedPages = customPages.filter(p => 
                /product|shop|store|pricing|catalog|portfolio|gallery/i.test(p)
            );
            if (productRelatedPages.length > 0) {
                pagesHtml.push(`<div class="vmo-rec-intel-page-item">📄 <strong>Custom Pages:</strong> ${productRelatedPages.join(', ')}</div>`);
            }
            
            if (pagesHtml.length > 0) {
                introHtml += `
                    <div class="vmo-rec-intel-card vmo-rec-intel-pages">
                        <div class="vmo-rec-intel-icon">🌐</div>
                        <div class="vmo-rec-intel-content">
                            <div class="vmo-rec-intel-label">Product/E-commerce Pages to Generate</div>
                            <div class="vmo-rec-intel-pages-list">${pagesHtml.join('')}</div>
                        </div>
                    </div>
                `;
            }
        }
        
        return introHtml;
    }
    
    /**
     * ✅ NEW: Inject styles for market intelligence intro panel
     */
    injectMarketIntelligenceStyles() {
        if (document.getElementById('vmo-market-intel-styles')) return;
        
        const style = document.createElement('style');
        style.id = 'vmo-market-intel-styles';
        style.textContent = `
            /* ✅ MARKET INTEL ACCORDIONS */
            .vmo-rec-market-intel .vmo-rec-accordion {
                background: rgba(255,255,255,0.03);
                border: 1px solid rgba(255,255,255,0.1);
                border-radius: 12px;
                margin-left: 0;
                margin-bottom: 12px;
                overflow: hidden;
            }
            .vmo-rec-market-intel .vmo-rec-accordion[open] {
                background: rgba(255,255,255,0.05);
            }
            .vmo-rec-market-intel .vmo-rec-accordion-header {
                display: flex;
                align-items: center;
                gap: 12px;
                padding: 16px 18px;
                cursor: pointer;
                background: linear-gradient(135deg, rgba(79,172,254,0.1) 0%, rgba(0,242,195,0.05) 100%);
                border-bottom: 1px solid rgba(255,255,255,0.1);
                transition: background 0.2s ease;
                list-style: none;
            }
            .vmo-rec-market-intel .vmo-rec-accordion-header::-webkit-details-marker {
                display: none;
            }
            .vmo-rec-market-intel .vmo-rec-accordion-header:hover {
                background: linear-gradient(135deg, rgba(79,172,254,0.15) 0%, rgba(0,242,195,0.1) 100%);
            }
            .vmo-rec-market-intel .vmo-rec-accordion-icon {
                font-size: 22px;
            }
            .vmo-rec-market-intel .vmo-rec-accordion-title {
                flex: 1;
                font-size: 15px;
                font-weight: 600;
                color: #fff;
            }
            .vmo-rec-market-intel .vmo-rec-accordion-toggle {
                font-size: 12px;
                color: rgba(255,255,255,0.5);
                transition: transform 0.3s ease;
            }
            .vmo-rec-market-intel .vmo-rec-accordion[open] .vmo-rec-accordion-toggle {
                transform: rotate(180deg);
            }
            .vmo-rec-market-intel .vmo-rec-accordion-content {
                padding: 16px 18px;
                overflow: visible;
            }
            
            /* ✅ WHY EXPLANATIONS - Novice friendly */
            .vmo-rec-intel-why {
                margin-top: 12px;
                padding: 12px 14px;
                background: rgba(251,191,36,0.1);
                border-left: 3px solid #fbbf24;
                border-radius: 0 8px 8px 0;
                font-size: 13px;
                color: rgba(255,255,255,0.9);
                line-height: 1.5;
            }
            .vmo-rec-intel-why strong {
                color: #fbbf24;
            }
            
            /* ✅ INTRO TEXT */
            .vmo-rec-intel-intro-text {
                font-size: 13px;
                color: rgba(255,255,255,0.75);
                margin-top: 10px;
                line-height: 1.5;
            }
            .vmo-rec-intel-intro-explain {
                padding: 14px;
                background: rgba(59, 130, 246, 0.08);
                border-radius: 10px;
                margin-bottom: 16px;
                border: 1px solid rgba(59, 130, 246, 0.2);
            }
            .vmo-rec-intel-intro-explain p {
                margin: 0;
                font-size: 14px;
                color: rgba(255,255,255,0.9);
                line-height: 1.5;
            }
            
            /* ✅ COMPETITOR CARDS GRID */
            .vmo-rec-intel-holders-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
                gap: 12px;
            }
            .vmo-rec-intel-holder-card {
                background: rgba(255,255,255,0.04);
                border: 1px solid rgba(255,255,255,0.1);
                border-radius: 10px;
                padding: 14px;
                transition: all 0.2s ease;
            }
            .vmo-rec-intel-holder-card:hover {
                background: rgba(255,255,255,0.06);
                border-color: rgba(79,172,254,0.3);
            }
            .vmo-rec-intel-holder-header {
                display: flex;
                align-items: center;
                gap: 10px;
                margin-bottom: 8px;
            }
            .vmo-rec-intel-rank-badge {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                width: 26px;
                height: 26px;
                background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                border-radius: 50%;
                font-size: 12px;
                font-weight: 700;
                color: #fff;
            }
            .vmo-rec-intel-holder-name {
                font-size: 15px;
                font-weight: 600;
                color: #fff;
            }
            .vmo-rec-intel-holder-desc {
                font-size: 12px;
                color: rgba(255,255,255,0.65);
                font-style: italic;
                margin-bottom: 10px;
                padding-left: 36px;
            }
            .vmo-rec-intel-source-link {
                font-size: 12px;
                color: rgba(255,255,255,0.78);
                margin-bottom: 10px;
                padding-left: 36px;
            }
            .vmo-rec-source-link-label {
                color: rgba(255,255,255,0.55);
                margin-right: 4px;
            }
            .vmo-rec-intel-source-link a {
                color: #93c5fd;
                text-decoration: none;
                word-break: break-all;
            }
            .vmo-rec-intel-source-link a:hover {
                text-decoration: underline;
            }
            .vmo-rec-intel-holder-financials {
                display: flex;
                flex-direction: column;
                gap: 6px;
                padding-left: 36px;
            }
            .vmo-rec-intel-fin-item {
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 12px;
            }
            .vmo-rec-fin-label {
                color: rgba(255,255,255,0.6);
            }
            .vmo-rec-fin-value {
                color: #00f2c3;
                font-weight: 600;
            }
            .vmo-rec-intel-no-data {
                font-size: 11px;
                color: rgba(255,255,255,0.4);
                font-style: italic;
                padding-left: 36px;
            }
            
            /* ✅ HIGHLIGHT VALUES */
            .vmo-rec-highlight-value {
                color: #00f2c3;
            }
            
            /* ✅ ORIGINAL STYLES */
            .vmo-rec-intel-section {
                background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
                border-radius: 12px;
                padding: 20px;
                margin-bottom: 16px;
                border: 1px solid rgba(79, 172, 254, 0.3);
            }
            .vmo-rec-intel-header {
                font-size: 18px;
                font-weight: 700;
                color: #4facfe;
                margin-bottom: 4px;
            }
            .vmo-rec-intel-subheader {
                font-size: 14px;
                color: rgba(255,255,255,0.8);
            }
            .vmo-rec-intel-card {
                display: flex;
                gap: 14px;
                background: rgba(255,255,255,0.03);
                border-radius: 10px;
                padding: 14px 16px;
                margin-bottom: 10px;
                border: 1px solid rgba(255,255,255,0.06);
                transition: background 0.2s;
            }
            .vmo-rec-intel-card:hover {
                background: rgba(255,255,255,0.05);
            }
            .vmo-rec-intel-icon {
                font-size: 24px;
                flex-shrink: 0;
            }
            .vmo-rec-intel-content {
                flex: 1;
            }
            .vmo-rec-intel-label {
                font-size: 12px;
                font-weight: 600;
                color: #4facfe;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                margin-bottom: 4px;
            }
            .vmo-rec-intel-value {
                font-size: 16px;
                font-weight: 600;
                color: #fff;
            }
            .vmo-rec-intel-value-large {
                font-size: 24px;
                font-weight: 700;
                color: #00f2c3;
            }
            .vmo-rec-intel-value-row {
                font-size: 14px;
                color: #fff;
                margin: 4px 0;
            }
            .vmo-rec-intel-value-text {
                font-size: 13px;
                color: rgba(255,255,255,0.85);
                line-height: 1.5;
            }
            .vmo-rec-intel-summary-list {
                display: flex;
                flex-direction: column;
                gap: 8px;
                margin-top: 14px;
                padding-top: 14px;
                border-top: 1px solid rgba(255,255,255,0.08);
            }
            .vmo-rec-intel-summary-title {
                font-size: 12px;
                font-weight: 700;
                color: #93c5fd;
                text-transform: uppercase;
                letter-spacing: 0.04em;
            }
            .vmo-rec-intel-summary-item {
                font-size: 13px;
                color: rgba(255,255,255,0.88);
                line-height: 1.5;
                padding: 8px 10px;
                background: rgba(255,255,255,0.03);
                border-radius: 8px;
            }
            .vmo-rec-intel-tag {
                color: rgba(255,255,255,0.6);
                margin-right: 4px;
            }
            .vmo-rec-intel-sublabel {
                font-size: 12px;
                color: rgba(255,255,255,0.6);
                margin-top: 4px;
            }
            .vmo-rec-intel-highlight {
                font-size: 12px;
                color: #00f2c3;
                margin-top: 8px;
                padding: 6px 10px;
                background: rgba(0,242,195,0.1);
                border-radius: 6px;
                display: inline-block;
            }
            .vmo-rec-intel-holders-list {
                margin-top: 6px;
            }
            .vmo-rec-intel-holder {
                font-size: 13px;
                color: rgba(255,255,255,0.9);
                padding: 10px 0;
                border-bottom: 1px solid rgba(255,255,255,0.08);
            }
            .vmo-rec-intel-holder:last-child {
                border-bottom: none;
            }
            .vmo-rec-intel-holder-main {
                display: flex;
                align-items: center;
                margin-bottom: 4px;
            }
            .vmo-rec-intel-holder-details {
                margin-left: 30px;
                margin-top: 6px;
            }
            .vmo-rec-intel-fin {
                display: inline-block;
                font-size: 11px;
                color: #00f2c3;
                background: rgba(0,242,195,0.1);
                padding: 3px 8px;
                border-radius: 4px;
                margin-right: 8px;
            }
            .vmo-rec-intel-rank {
                display: inline-block;
                width: 22px;
                height: 22px;
                line-height: 22px;
                text-align: center;
                background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                border-radius: 50%;
                font-size: 11px;
                font-weight: 700;
                color: #fff;
                margin-right: 8px;
            }
            .vmo-rec-intel-target-grid {
                display: flex;
                flex-direction: column;
                gap: 8px;
            }
            .vmo-rec-intel-segment-grid {
                margin-top: 12px;
                display: flex;
                flex-direction: column;
                gap: 8px;
            }
            .vmo-rec-intel-segment-item {
                padding: 10px 12px;
                border-radius: 8px;
                background: rgba(255,255,255,0.03);
                font-size: 13px;
                color: rgba(255,255,255,0.88);
            }
            .vmo-rec-intel-segment-meta {
                margin-top: 4px;
                font-size: 11px;
                color: rgba(255,255,255,0.58);
            }
            .vmo-rec-intel-target-item {
                font-size: 13px;
                color: rgba(255,255,255,0.85);
                padding: 8px 12px;
                background: rgba(255,255,255,0.03);
                border-radius: 6px;
            }
            .vmo-rec-intel-bulk-section {
                margin-top: 12px;
                padding-top: 12px;
                border-top: 1px solid rgba(255,255,255,0.1);
            }
            .vmo-rec-intel-bulk-title {
                font-size: 13px;
                font-weight: 600;
                color: #4facfe;
                margin-bottom: 8px;
            }
            .vmo-rec-intel-bulk-item {
                font-size: 12px;
                color: rgba(255,255,255,0.85);
                padding: 8px 12px;
                background: rgba(79,172,254,0.08);
                border-radius: 6px;
                margin-bottom: 6px;
                border-left: 3px solid #4facfe;
            }
            .vmo-rec-intel-bulk-item:last-child {
                margin-bottom: 0;
            }
            .vmo-rec-intel-approach {
                display: block;
                margin-top: 4px;
                font-size: 11px;
                color: rgba(255,255,255,0.6);
            }
            .vmo-rec-intel-pages-list {
                margin-top: 6px;
            }
            .vmo-rec-intel-page-item {
                font-size: 13px;
                color: rgba(255,255,255,0.85);
                padding: 6px 0;
                border-bottom: 1px solid rgba(255,255,255,0.05);
            }
            .vmo-rec-intel-page-item:last-child {
                border-bottom: none;
            }
            .vmo-rec-intel-leads {
                border: 1px solid rgba(0,242,195,0.3);
                background: rgba(0,242,195,0.05);
            }
            
            /* ✅ MARKET POSITION PROJECTIONS */
            .vmo-rec-market-position {
                margin-top: 14px;
                display: flex;
                flex-direction: column;
                gap: 8px;
                border-top: 1px solid rgba(255,255,255,0.08);
                padding-top: 14px;
            }
            .vmo-rec-position-item {
                display: flex;
                align-items: flex-start;
                gap: 10px;
                font-size: 13px;
                color: rgba(255,255,255,0.88);
                line-height: 1.5;
            }
            .vmo-rec-position-badge {
                flex-shrink: 0;
                display: inline-flex;
                align-items: center;
                justify-content: center;
                padding: 2px 8px;
                border-radius: 20px;
                font-size: 10px;
                font-weight: 700;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                min-width: 52px;
                text-align: center;
            }
            .vmo-pos-current { background: rgba(156,163,175,0.2); color: #9ca3af; border: 1px solid rgba(156,163,175,0.3); }
            .vmo-pos-potential { background: rgba(79,172,254,0.15); color: #4facfe; border: 1px solid rgba(79,172,254,0.3); }
            .vmo-pos-short { background: rgba(251,191,36,0.15); color: #fbbf24; border: 1px solid rgba(251,191,36,0.3); }
            .vmo-pos-mid { background: rgba(0,242,195,0.15); color: #00f2c3; border: 1px solid rgba(0,242,195,0.3); }
            .vmo-pos-long { background: rgba(168,85,247,0.15); color: #a855f7; border: 1px solid rgba(168,85,247,0.3); }
            
            /* ✅ COMPETITOR TYPE BADGES */
            .vmo-rec-comp-type-badge {
                display: inline-flex;
                align-items: center;
                padding: 1px 7px;
                border-radius: 10px;
                font-size: 10px;
                font-weight: 600;
                text-transform: uppercase;
                letter-spacing: 0.3px;
            }
            .vmo-comp-direct { background: rgba(239,68,68,0.15); color: #f87171; border: 1px solid rgba(239,68,68,0.3); }
            .vmo-comp-indirect { background: rgba(251,191,36,0.15); color: #fbbf24; border: 1px solid rgba(251,191,36,0.3); }
            
            /* ✅ WEAKNESS & HOW TO BEAT */
            .vmo-rec-intel-weakness {
                margin-top: 8px;
                padding: 8px 10px;
                padding-left: 36px;
                background: rgba(239,68,68,0.06);
                border-left: 2px solid rgba(239,68,68,0.4);
                border-radius: 0 6px 6px 0;
                font-size: 12px;
                color: rgba(255,255,255,0.85);
                line-height: 1.4;
            }
            .vmo-rec-weakness-label {
                font-weight: 600;
                color: #f87171;
            }
            .vmo-rec-intel-beat {
                margin-top: 4px;
                padding: 8px 10px;
                padding-left: 36px;
                background: rgba(0,242,195,0.06);
                border-left: 2px solid rgba(0,242,195,0.4);
                border-radius: 0 6px 6px 0;
                font-size: 12px;
                color: rgba(255,255,255,0.85);
                line-height: 1.4;
            }
            .vmo-rec-beat-label {
                font-weight: 600;
                color: #00f2c3;
            }
            .vmo-rec-intel-overlap {
                margin-top: 6px;
                padding-left: 8px;
                font-size: 12px;
                color: rgba(255,255,255,0.8);
            }
            .vmo-rec-overlap-label {
                font-weight: 600;
                color: #4facfe;
            }
            
            /* ✅ THREAT LEVEL BADGE */
            .vmo-rec-threat-badge {
                display: inline-flex;
                align-items: center;
                padding: 1px 7px;
                border-radius: 10px;
                font-size: 10px;
                font-weight: 600;
                text-transform: uppercase;
                letter-spacing: 0.3px;
                border: 1px solid;
                margin-left: auto;
            }
            .vmo-indirect-card {
                border-left: 3px solid rgba(251,191,36,0.4);
            }
        `;
        document.head.appendChild(style);
    }
    
    /**
     * Helper to escape HTML
     */
    escapeHtml(str) {
        if (!str) return '';
        return String(str).replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    escapeHtmlAttr(str) {
        if (!this.hasMeaningfulResearchValue(str)) return '';
        return String(str)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    hasMeaningfulResearchValue(value) {
        if (value === null || value === undefined) return false;
        if (Array.isArray(value)) return value.some(item => this.hasMeaningfulResearchValue(item));
        if (typeof value === 'object') return Object.keys(value).length > 0;

        const normalized = String(value).trim().toLowerCase();
        if (!normalized) return false;

        return !/^(data not available|not available|unknown|n\/a|see full research report|available in research dashboard)$/i.test(normalized);
    }

    formatResearchValue(value) {
        if (!this.hasMeaningfulResearchValue(value)) return '';
        if (typeof value === 'string' || typeof value === 'number') return String(value).trim();

        if (Array.isArray(value)) {
            return value.map(item => this.formatResearchValue(item)).filter(Boolean).join(', ');
        }

        if (typeof value === 'object') {
            const directValue = value.value || value.amount || value.size || value.summary || value.label || value.name || value.analysis;
            if (this.hasMeaningfulResearchValue(directValue)) {
                return String(directValue).trim();
            }

            try {
                return JSON.stringify(value);
            } catch (_) {
                return '';
            }
        }

        return String(value);
    }

    getStructuredMarketIntel(researchData = {}) {
        const candidates = [
            researchData?.structured_market_intel,
            researchData?.structured_market_intelligence,
            researchData?.market_intelligence,
            researchData?.research_results?.structured_market_intel,
            researchData?.research_results?.structured_market_intelligence,
            researchData?.research_results?.market_intelligence
        ];

        for (const candidate of candidates) {
            if (candidate && typeof candidate === 'object' && Object.keys(candidate).length > 0) {
                return candidate;
            }
        }

        return {};
    }

    extractResearchField(rawData = '', labels = []) {
        if (!rawData || typeof rawData !== 'string') return '';

        const labelList = Array.isArray(labels) ? labels : [labels];
        for (const label of labelList) {
            const escapedLabel = String(label)
                .trim()
                .split(/\s+/)
                .map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
                .join('\\s+');
            const pattern = new RegExp(`(?:^|\\n)\\s*[-*•]?\\s*${escapedLabel}\\s*:\\s*(.+)`, 'i');
            const match = rawData.match(pattern);
            if (match && match[1]) {
                return match[1].trim();
            }
        }

        return '';
    }

    extractResearchHighlights(source = null, maxItems = 3) {
        const text = typeof source === 'string'
            ? source
            : (typeof source?.analysis === 'string' ? source.analysis : (typeof source?.summary === 'string' ? source.summary : ''));

        if (!text || !text.trim()) return [];

        const bulletMatches = text.match(/(?:^|\n)\s*[-•*]\s+(.+)/g) || [];
        const bulletItems = bulletMatches
            .map(item => item.replace(/(?:^|\n)\s*[-•*]\s+/, '').trim())
            .filter(item => item && !/^[A-Z0-9 _-]+:$/.test(item));
        if (bulletItems.length > 0) {
            return bulletItems.slice(0, maxItems);
        }

        return (text.match(/[^.!?]+[.!?]?/g) || [])
            .map(item => item.replace(/\s+/g, ' ').trim())
            .filter(item => item.length > 20 && !/^#+\s*/.test(item))
            .slice(0, maxItems);
    }

    normalizeResearchUrl(value = '') {
        let url = String(value || '').trim();
        if (!url) return '';

        url = url.replace(/^[<[(]+/, '').replace(/[>\]),.;]+$/, '');
        if (/^\[?url\]?$/i.test(url)) return '';

        if (!/^https?:\/\//i.test(url) && /^[a-z0-9.-]+\.[a-z]{2,}(?:\/[^\s]*)?$/i.test(url)) {
            url = `https://${url}`;
        }

        return /^https?:\/\/[^\s]+$/i.test(url) ? url : '';
    }

    getResearchCompetitorGroups(researchData = {}, structuredIntel = this.getStructuredMarketIntel(researchData)) {
        let rawCompetitors = researchData?.competitors_list || [];
        if (typeof rawCompetitors === 'string') {
            try {
                const parsed = JSON.parse(rawCompetitors);
                rawCompetitors = Array.isArray(parsed) ? parsed : [];
            } catch (_) {
                rawCompetitors = rawCompetitors.split(/[\n,]+/).map(item => item.trim()).filter(Boolean);
            }
        }
        if (!Array.isArray(rawCompetitors)) {
            rawCompetitors = [];
        }

        const rawLookup = new Map();
        rawCompetitors.forEach(entry => {
            const name = typeof entry === 'object'
                ? this.formatResearchValue(entry.name || entry.company)
                : this.formatResearchValue(entry);
            if (!name) return;

            const rawData = typeof entry === 'object' ? String(entry.raw_data || '') : '';
            const competitorType = typeof entry === 'object' && /secondary|indirect/i.test(String(entry.competitor_type || ''))
                ? 'indirect'
                : 'direct';

            rawLookup.set(name.toLowerCase(), {
                name,
                description: this.extractResearchField(rawData, ['What They Offer', 'Description']) || '',
                targetCustomer: this.extractResearchField(rawData, ['Target Customer']) || '',
                whyPrimary: this.extractResearchField(rawData, ['Why Primary Competitor']) || '',
                whySecondary: this.extractResearchField(rawData, ['Why Secondary Competitor']) || '',
                overlap: this.extractResearchField(rawData, ['Competes With Feature', 'Service Overlap', 'Overlap']) || '',
                website: this.normalizeResearchUrl(
                    (typeof entry === 'object' ? (entry.website || entry.url) : '')
                    || this.extractResearchField(rawData, ['Website', 'Site', 'URL'])
                ),
                competitorType
            });
        });

        const mapEntry = (entry, defaultType = 'direct') => {
            if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
                const name = this.formatResearchValue(entry.name || entry.company) || 'Unknown';
                const fallback = rawLookup.get(name.toLowerCase()) || {};
                const competitorType = /secondary|indirect/i.test(String(entry.competitor_type || defaultType))
                    ? 'indirect'
                    : 'direct';

                return {
                    name,
                    description: this.formatResearchValue(entry.description || entry.key_strength) || fallback.description || fallback[competitorType === 'indirect' ? 'whySecondary' : 'whyPrimary'] || fallback.targetCustomer || null,
                    revenue: this.formatResearchValue(entry.annual_revenue || entry.revenue || entry.estimated_revenue) || null,
                    worth: this.formatResearchValue(entry.net_worth || entry.valuation || entry.market_cap) || null,
                    marketShare: this.formatResearchValue(entry.market_share) || null,
                    weakness: this.formatResearchValue(entry.weakness) || null,
                    howToBeat: this.formatResearchValue(entry.how_to_beat) || null,
                    threatLevel: this.formatResearchValue(entry.threat_level) || 'medium',
                    overlap: this.formatResearchValue(entry.overlap) || fallback.overlap || null,
                    vulnerability: this.formatResearchValue(entry.vulnerability) || null,
                    website: this.normalizeResearchUrl(entry.website || entry.url || fallback.website),
                    competitorType
                };
            }

            const name = this.formatResearchValue(entry);
            const fallback = rawLookup.get(String(name || '').toLowerCase()) || {};
            return {
                name: name || 'Unknown',
                description: fallback.description || fallback.whyPrimary || fallback.whySecondary || fallback.targetCustomer || null,
                revenue: null,
                worth: null,
                marketShare: null,
                weakness: null,
                howToBeat: null,
                threatLevel: fallback.competitorType === 'indirect' ? 'medium' : '',
                overlap: fallback.overlap || null,
                vulnerability: null,
                website: fallback.website || '',
                competitorType: fallback.competitorType || defaultType
            };
        };

        const uniqueByName = items => {
            const seen = new Set();
            return items.filter(item => {
                const key = String(item?.name || '').trim().toLowerCase();
                if (!key || seen.has(key)) return false;
                seen.add(key);
                return true;
            });
        };

        let direct = Array.isArray(structuredIntel.top_competitors) && structuredIntel.top_competitors.length > 0
            ? structuredIntel.top_competitors.map(entry => mapEntry(entry, 'direct'))
            : [];
        if (!direct.length && Array.isArray(researchData.top_earners) && researchData.top_earners.length > 0) {
            direct = researchData.top_earners.map(entry => mapEntry(entry, 'direct'));
        }
        if (!direct.length && Array.isArray(researchData.market_leaders) && researchData.market_leaders.length > 0) {
            direct = researchData.market_leaders.map(entry => mapEntry(entry, 'direct'));
        }
        if (!direct.length) {
            direct = rawCompetitors
                .filter(entry => !/secondary|indirect/i.test(String((entry && typeof entry === 'object' ? entry.competitor_type : '') || '')))
                .map(entry => mapEntry(entry, 'direct'));
        }

        let indirect = Array.isArray(structuredIntel.indirect_competitors) && structuredIntel.indirect_competitors.length > 0
            ? structuredIntel.indirect_competitors.map(entry => mapEntry(entry, 'indirect'))
            : [];
        if (!indirect.length) {
            indirect = rawCompetitors
                .filter(entry => /secondary|indirect/i.test(String((entry && typeof entry === 'object' ? entry.competitor_type : '') || '')))
                .map(entry => mapEntry(entry, 'indirect'));
        }

        return {
            direct: uniqueByName(direct).slice(0, 6),
            indirect: uniqueByName(indirect).slice(0, 4)
        };
    }

    getRecommendationResearchSources(researchData = {}, structuredIntel = this.getStructuredMarketIntel(researchData)) {
        const competitorGroups = this.getResearchCompetitorGroups(researchData, structuredIntel);
        const references = [];
        const seen = new Set();

        const pushReference = (label, url, note = '') => {
            const normalizedUrl = this.normalizeResearchUrl(url);
            if (!normalizedUrl) return;

            const key = normalizedUrl.toLowerCase();
            if (seen.has(key)) return;
            seen.add(key);

            references.push({ label, url: normalizedUrl, note });
        };

        [...competitorGroups.direct, ...competitorGroups.indirect].forEach(competitor => {
            pushReference(
                competitor.name,
                competitor.website,
                competitor.competitorType === 'indirect'
                    ? 'Indirect competitor website reviewed during Phase 3.'
                    : 'Direct competitor website reviewed during Phase 3.'
            );
        });

        const addTextUrls = (value, fallbackLabel) => {
            const text = typeof value === 'string'
                ? value
                : (typeof value?.analysis === 'string' ? value.analysis : '');
            if (!text) return;

            const matches = text.match(/\bhttps?:\/\/[^\s)]+/gi) || [];
            matches.forEach((url, index) => {
                pushReference(`${fallbackLabel} ${index + 1}`, url, 'Referenced in the stored research output.');
            });
        };

        addTextUrls(researchData.research_summary, 'Research source');
        addTextUrls(researchData.market_trends, 'Market source');

        const artifacts = [];
        if (
            this.hasMeaningfulResearchValue(structuredIntel.global_market_size)
            || this.hasMeaningfulResearchValue(structuredIntel.local_market_size)
            || this.hasMeaningfulResearchValue(structuredIntel.market_growth_rate)
            || this.hasMeaningfulResearchValue(structuredIntel.market_projection)
        ) {
            artifacts.push({
                label: 'Structured market intelligence',
                detail: 'Industry sizing, growth estimates, projections, and segment data used to populate the opportunity guidance.'
            });
        }
        if (this.hasMeaningfulResearchValue(researchData.market_positioning?.analysis || researchData.market_positioning)) {
            artifacts.push({
                label: 'Market positioning analysis',
                detail: 'Competitive positioning and whitespace analysis used to shape the strategic recommendation framing.'
            });
        }
        if (this.hasMeaningfulResearchValue(researchData.market_trends?.analysis || researchData.market_trends)) {
            artifacts.push({
                label: 'Market trends analysis',
                detail: 'Trend and demand signals used to enrich the opportunity summary in Phase 4.'
            });
        }
        if (this.hasMeaningfulResearchValue(researchData.target_market_intelligence?.analysis || researchData.target_market_intelligence)) {
            artifacts.push({
                label: 'Target market analysis',
                detail: 'Customer segments, buying behavior, and acquisition insights used to define the target market guidance.'
            });
        }
        if (competitorGroups.direct.length > 0 || competitorGroups.indirect.length > 0) {
            artifacts.push({
                label: 'Competitor intelligence set',
                detail: `${competitorGroups.direct.length} direct and ${competitorGroups.indirect.length} indirect competitors were profiled for this recommendation package.`
            });
        }

        return {
            references: references.slice(0, 10),
            artifacts
        };
    }

    buildRecommendationChecklist(researchResults = {}, dynamicProfile = null) {
        // Use dynamic AI-generated profile if available, otherwise fall back to static
        const baseProfile = dynamicProfile || this.buildRecommendationProfile(researchResults);
        const profile = this.applyPhoneRecommendationPolicy(baseProfile);
        
        const section = category => {
            // Check if this is a dynamic profile with comparison data
            const isDynamic = profile.is_dynamic;
            
            if (isDynamic) {
                // Render with comparison format: User wanted → Research found → Recommendation
                return `───────────────────────────────────────────────────────
${category.title}
───────────────────────────────────────────────────────
${category.items.map(item => {
    let output = `☑️ ${item.label}`;
    if (item.user_wanted) {
        output += `\n    📌 You Wanted: ${item.user_wanted}`;
    }
    if (item.research_found) {
        output += `\n    🔍 Research Found: ${item.research_found}`;
    }
    if (item.recommendation) {
        output += `\n    💡 Recommendation: ${item.recommendation}`;
    }
    output += `\n    ↳ Why: ${item.why}`;
    if (item.impact) {
        output += ` [Impact: ${item.impact.toUpperCase()}]`;
    }
    return output;
}).join('\n\n')}
`;
            }
            
            // Standard format (fallback)
            return `───────────────────────────────────────────────────────
${category.title}
───────────────────────────────────────────────────────
${category.items.map(item => `☑️ ${item.label}
    ↳ Why: ${item.why}`).join('\n\n')}
`;
        };

        const introText = profile.is_dynamic 
            ? `Based on your consultation and deep market research for ${profile.business_name} (${profile.industry}), here are personalized recommendations. For each item, I'm showing what you wanted, what research revealed, and my recommendation. YOU have final discretion on every choice.`
            : `Based on deep research for ${profile.business_name} (${profile.industry}), here is everything I recommend building right now. Everything is pre-checked so you don't miss critical foundations—feel free to uncheck anything you don't want.`;

        const text = `═══════════════════════════════════════════════════════
🎯 YOUR PERSONALIZED BUSINESS PACKAGE
═══════════════════════════════════════════════════════
${introText}

${profile.categories.map(section).join('\n')}

💡 **Summary**
${profile.summary}

Ready to move forward? Select your preferences for each item, then click "Approve & Continue". Next up is Phase 5: Website Architecture Plan.`;

        profile.preview_text = text;

        return { text, profile };
    }

    async presentPhase4Checklist(researchResults) {
        if (this.phase4ChecklistPresented) return;

        try {
            await this.refreshPhoneProvisioningStatus({ force: true, persist: true });
        } catch (e) {
            console.warn('Unable to refresh phone provisioning status before recommendations:', e);
        }
        
        // ✅ CRITICAL FIX: Ensure phase index is 3 (Phase 4) when presenting checklist
        const flow = this.consultation_data.consultation_flow;
        if (flow && flow.current_index < 3) {
            console.log(`📊 Phase 4 Checklist: Correcting phase from ${flow.current_index} to 3`);
            flow.current_index = 3;
            if (!this.state.consultation_flow) this.state.consultation_flow = {};
            this.state.consultation_flow.current_index = 3;
            this.state.consultation_flow.current_phase_index = 3;
            this.updatePhaseProgressBar();
            // ✅ FIX: Persist the corrected phase index so a page refresh doesn't revert it
            this.saveConsultationData({ consultation_flow: { current_index: 3, current_phase_index: 3 } });
        }
        
        let profile = null;
        let usedDynamic = false;
        
        // First, try to fetch AI-generated dynamic recommendations
        try {
            const dynamicResult = await this.fetchDynamicRecommendations(researchResults || this.consultation_data.research_results || {});
            
            if (dynamicResult.success && dynamicResult.profile) {
                profile = dynamicResult.profile;
                usedDynamic = true;
                console.log('Using AI-generated dynamic recommendations');
            } else {
                console.warn('Dynamic recommendations failed, using fallback:', dynamicResult.error);
            }
        } catch (err) {
            console.warn('Error fetching dynamic recommendations, using fallback:', err);
        }
        
        // Generate checklist (with dynamic profile if available)
        const { text: checklist, profile: finalProfile } = this.buildRecommendationChecklist(
            researchResults || this.consultation_data.research_results || {}, 
            profile
        );

        // Render an interactive checklist (pre-checked) with an Approve button.
        try {
            this.injectInsightsStyles && this.injectInsightsStyles();
        } catch(_) {}

        // ✅ Inject accordion styles for recommendations
        this.injectRecommendationAccordionStyles();

        // ✅ Get research data for sources
        const researchData = researchResults || this.consultation_data.research_results || {};
        const researchSources = this.getRecommendationResearchSources(researchData);

        const ui = document.createElement('section');
        ui.className = 'vmo-insight-card vmo-recommendations-panel';
        
        // Add summary if dynamic recommendations
        if (finalProfile.is_dynamic && finalProfile.summary) {
            ui.innerHTML = `<h5>🧭 Personalized Recommendations</h5>
                <p style="font-size:14px; opacity:0.9; margin-bottom:12px; line-height:1.5;">${String(finalProfile.summary).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</p>`;
        } else {
            ui.innerHTML = `<h5>🧭 Recommendations Checklist</h5>`;
        }

        // ✅ Add research sources panel if available.
        if (researchSources.references.length > 0 || researchSources.artifacts.length > 0) {
            const sourcesPanel = document.createElement('div');
            sourcesPanel.className = 'vmo-rec-sources-panel';
            sourcesPanel.innerHTML = `
                <div class="vmo-rec-accordion" data-accordion="sources">
                    <div class="vmo-rec-accordion-header" onclick="this.parentElement.classList.toggle('open')">
                        <span>📊 Research Sources & Data</span>
                        <span class="vmo-rec-accordion-icon">▶</span>
                    </div>
                    <div class="vmo-rec-accordion-content">
                        ${researchSources.references.length > 0 ? `
                            <div class="vmo-rec-source-section">
                                <div class="vmo-rec-source-section-title">References Consulted</div>
                                ${researchSources.references.map(source => `
                                    <div class="vmo-rec-source-item">
                                        <strong>${this.escapeHtml(source.label)}:</strong>
                                        <span>
                                            <a href="${this.escapeHtmlAttr(source.url)}" target="_blank" rel="noopener noreferrer">${this.escapeHtml(source.url.replace(/^https?:\/\//i, ''))}</a>
                                            ${source.note ? `<span class="vmo-rec-source-meta">${this.escapeHtml(source.note)}</span>` : ''}
                                        </span>
                                    </div>
                                `).join('')}
                            </div>
                        ` : ''}
                        ${researchSources.artifacts.length > 0 ? `
                            <div class="vmo-rec-source-section">
                                <div class="vmo-rec-source-section-title">Research Artifacts Used</div>
                                ${researchSources.artifacts.map(source => `
                                    <div class="vmo-rec-source-item">
                                        <strong>${this.escapeHtml(source.label)}:</strong>
                                        <span>${this.escapeHtml(source.detail)}</span>
                                    </div>
                                `).join('')}
                            </div>
                        ` : ''}
                    </div>
                </div>
            `;
            ui.appendChild(sourcesPanel);
        }

        // ✅ NEW: Add comprehensive Market Intelligence Introduction
        this.injectMarketIntelligenceStyles();
        const extractedInfo = this.consultation_data.extracted_information || {};
        const marketIntelHtml = this.buildMarketIntelligenceIntro(researchData, extractedInfo);
        
        if (marketIntelHtml) {
            const marketIntelPanel = document.createElement('div');
            marketIntelPanel.className = 'vmo-rec-market-intel';
            marketIntelPanel.innerHTML = marketIntelHtml;
            ui.appendChild(marketIntelPanel);
        }

        const categories = Array.isArray(finalProfile.categories) ? finalProfile.categories : [];
        categories.forEach(cat => {
            const block = document.createElement('div');
            block.style.cssText = 'margin-top:10px;';
            const title = document.createElement('div');
            title.style.cssText = 'font-weight:600; margin:8px 0 6px;';
            title.textContent = cat.title || 'Recommendations';
            block.appendChild(title);

            (cat.items || []).forEach(item => {
                const row = document.createElement('div');
                row.className = 'vmo-rec-item';
                row.style.cssText = 'padding:12px 0; border-top:1px solid rgba(255,255,255,0.06);';

                // Check if this is a dynamic item with comparison data
                const isDynamic = finalProfile.is_dynamic && (item.user_wanted || item.research_found || item.recommendation);
                const itemId = item.id || item.label || `rec_${Math.random().toString(36).substr(2, 9)}`;
                const impactBadge = item.impact ? `<span class="vmo-rec-impact vmo-rec-impact-${item.impact}">${item.impact.toUpperCase()}</span>` : '';

                if (isDynamic) {
                    // ✅ NEW: Accordion-style dynamic recommendation
                    row.innerHTML = `
                        <div class="vmo-rec-main">
                            <div class="vmo-rec-header">
                                <input type="checkbox" checked data-rec-id="${itemId}" class="vmo-rec-checkbox">
                                <div class="vmo-rec-title-wrap">
                                    <div class="vmo-rec-title" data-rec-title="1" contenteditable="true">${String(item.label || 'Recommendation').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</div>
                                    ${impactBadge}
                                    <button type="button" class="vmo-rec-refine-btn" data-refine-id="${itemId}" style="margin-left:8px;background:none;border:1px solid rgba(255,255,255,0.2);color:#c084fc;padding:3px 10px;border-radius:12px;font-size:11px;cursor:pointer;transition:all 0.2s ease;white-space:nowrap;" title="Describe changes to this recommendation">✏️ Refine</button>
                                </div>
                            </div>
                            
                            <!-- Inline Refinement Input (hidden by default) -->
                            <div class="vmo-rec-refine-panel" data-refine-panel="${itemId}" style="display:none;margin:8px 0 4px 28px;padding:10px;background:rgba(192,132,252,0.08);border:1px solid rgba(192,132,252,0.2);border-radius:8px;">
                                <label style="font-size:11px;color:#c084fc;display:block;margin-bottom:4px;">How would you like to change this?</label>
                                <textarea data-refine-input="${itemId}" rows="2" placeholder="e.g., 'I don't want a services section' or 'Change this to focus on testimonials instead'" style="width:100%;background:rgba(0,0,0,0.3);color:#fff;border:1px solid rgba(192,132,252,0.3);border-radius:6px;padding:8px;font-family:inherit;font-size:12px;box-sizing:border-box;resize:vertical;"></textarea>
                                <div style="display:flex;gap:8px;margin-top:6px;align-items:center;">
                                    <button type="button" class="vmo-rec-refine-apply" data-apply-id="${itemId}" style="background:#c084fc;color:#001a2e;border:none;padding:5px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;">Apply Refinement</button>
                                    <button type="button" class="vmo-rec-refine-cancel" data-cancel-id="${itemId}" style="background:none;border:1px solid rgba(255,255,255,0.15);color:#8899aa;padding:5px 14px;border-radius:6px;font-size:11px;cursor:pointer;">Cancel</button>
                                    <span class="vmo-rec-refine-status" data-refine-status="${itemId}" style="font-size:11px;color:#8899aa;"></span>
                                </div>
                            </div>
                            
                            <!-- Collapsible Details Accordion -->
                            <div class="vmo-rec-accordion" data-accordion="${itemId}">
                                <div class="vmo-rec-accordion-header" onclick="this.parentElement.classList.toggle('open')">
                                    <span>💡 Why this recommendation?</span>
                                    <span class="vmo-rec-accordion-icon">▶</span>
                                </div>
                                <div class="vmo-rec-accordion-content">
                                    ${item.user_wanted ? `
                                        <div class="vmo-rec-detail vmo-rec-detail-wanted">
                                            <span class="vmo-rec-detail-label">📌 You Wanted:</span>
                                            <span class="vmo-rec-detail-value">${String(item.user_wanted).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                        </div>
                                    ` : ''}
                                    
                                    ${item.research_found ? `
                                        <div class="vmo-rec-detail vmo-rec-detail-research">
                                            <span class="vmo-rec-detail-label">🔍 Research Found:</span>
                                            <span class="vmo-rec-detail-value">${String(item.research_found).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                        </div>
                                    ` : ''}
                                    
                                    ${item.recommendation ? `
                                        <div class="vmo-rec-detail vmo-rec-detail-ai">
                                            <span class="vmo-rec-detail-label">💡 AI Recommendation:</span>
                                            <span class="vmo-rec-detail-value">${String(item.recommendation).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                        </div>
                                    ` : ''}
                                    
                                    <div class="vmo-rec-detail vmo-rec-detail-why">
                                        <span class="vmo-rec-detail-label">↳ Reasoning:</span>
                                        <span class="vmo-rec-detail-value" data-rec-why="1" contenteditable="true">${String(item.why || 'Drives growth and conversion').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                    </div>
                                </div>
                            </div>
                            
                            ${item.options && item.options.length > 1 ? `
                            <div class="vmo-rec-options">
                                ${item.options.map(opt => `
                                    <label class="vmo-rec-option">
                                        <input type="radio" name="choice_${itemId}" value="${opt.id}" ${opt.id === item.default_choice ? 'checked' : ''} data-choice-for="${itemId}">
                                        <span>${String(opt.label).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                    </label>
                                `).join('')}
                            </div>
                            ` : ''}
                        </div>
                    `;
                } else {
                    // ✅ NEW: Accordion-style static recommendation (fallback)
                    row.innerHTML = `
                        <div class="vmo-rec-main">
                            <div class="vmo-rec-header">
                                <input type="checkbox" checked data-rec-id="${itemId}" class="vmo-rec-checkbox">
                                <div class="vmo-rec-title-wrap">
                                    <div class="vmo-rec-title" data-rec-title="1" contenteditable="true">${String(item.label || 'Recommendation').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</div>
                                    <button type="button" class="vmo-rec-refine-btn" data-refine-id="${itemId}" style="margin-left:8px;background:none;border:1px solid rgba(255,255,255,0.2);color:#c084fc;padding:3px 10px;border-radius:12px;font-size:11px;cursor:pointer;transition:all 0.2s ease;white-space:nowrap;" title="Describe changes to this recommendation">✏️ Refine</button>
                                </div>
                            </div>
                            
                            <!-- Inline Refinement Input (hidden by default) -->
                            <div class="vmo-rec-refine-panel" data-refine-panel="${itemId}" style="display:none;margin:8px 0 4px 28px;padding:10px;background:rgba(192,132,252,0.08);border:1px solid rgba(192,132,252,0.2);border-radius:8px;">
                                <label style="font-size:11px;color:#c084fc;display:block;margin-bottom:4px;">How would you like to change this?</label>
                                <textarea data-refine-input="${itemId}" rows="2" placeholder="e.g., 'I don't want this section' or 'Change to focus on something else'" style="width:100%;background:rgba(0,0,0,0.3);color:#fff;border:1px solid rgba(192,132,252,0.3);border-radius:6px;padding:8px;font-family:inherit;font-size:12px;box-sizing:border-box;resize:vertical;"></textarea>
                                <div style="display:flex;gap:8px;margin-top:6px;align-items:center;">
                                    <button type="button" class="vmo-rec-refine-apply" data-apply-id="${itemId}" style="background:#c084fc;color:#001a2e;border:none;padding:5px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;">Apply Refinement</button>
                                    <button type="button" class="vmo-rec-refine-cancel" data-cancel-id="${itemId}" style="background:none;border:1px solid rgba(255,255,255,0.15);color:#8899aa;padding:5px 14px;border-radius:6px;font-size:11px;cursor:pointer;">Cancel</button>
                                    <span class="vmo-rec-refine-status" data-refine-status="${itemId}" style="font-size:11px;color:#8899aa;"></span>
                                </div>
                            </div>
                            
                            <!-- Collapsible Why Accordion -->
                            <div class="vmo-rec-accordion" data-accordion="${itemId}">
                                <div class="vmo-rec-accordion-header" onclick="this.parentElement.classList.toggle('open')">
                                    <span>💡 Why this recommendation?</span>
                                    <span class="vmo-rec-accordion-icon">▶</span>
                                </div>
                                <div class="vmo-rec-accordion-content">
                                    <div class="vmo-rec-detail vmo-rec-detail-why">
                                        <span class="vmo-rec-detail-value" data-rec-why="1" contenteditable="true">${String(item.why || 'Drives growth and conversion').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    `;
                }
                block.appendChild(row);
            });

            ui.appendChild(block);
        });

        const actions = document.createElement('div');
        actions.style.cssText = 'margin-top:12px; display:flex; gap:10px; align-items:center; flex-wrap:wrap;';
        const approveBtn = document.createElement('button');
        approveBtn.type = 'button';
        approveBtn.className = 'vmo-wizard-btn vmo-wizard-btn-primary';
        approveBtn.textContent = '✅ Approve & Continue';
        const hint = document.createElement('div');
        hint.style.cssText = 'font-size:12px; opacity:0.85;';
        hint.textContent = finalProfile.is_dynamic 
            ? 'Select your preference for each item. Uncheck items you do not want. Use ✏️ Refine to request changes.' 
            : 'Everything is pre-checked. Uncheck anything you do not want. Use ✏️ Refine to request changes.';
        actions.appendChild(approveBtn);
        actions.appendChild(hint);
        ui.appendChild(actions);

        // ✅ Recommendation Refinement Handlers
        const self = this;
        ui.addEventListener('click', (e) => {
            // Toggle refine panel
            const refineBtn = e.target.closest('.vmo-rec-refine-btn');
            if (refineBtn) {
                const refId = refineBtn.getAttribute('data-refine-id');
                const panel = ui.querySelector(`[data-refine-panel="${refId}"]`);
                if (panel) panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
                return;
            }
            // Cancel refinement
            const cancelBtn = e.target.closest('.vmo-rec-refine-cancel');
            if (cancelBtn) {
                const cancelId = cancelBtn.getAttribute('data-cancel-id');
                const panel = ui.querySelector(`[data-refine-panel="${cancelId}"]`);
                if (panel) panel.style.display = 'none';
                return;
            }
            // Apply refinement via AI
            const applyBtn = e.target.closest('.vmo-rec-refine-apply');
            if (applyBtn) {
                const applyId = applyBtn.getAttribute('data-apply-id');
                const input = ui.querySelector(`[data-refine-input="${applyId}"]`);
                const status = ui.querySelector(`[data-refine-status="${applyId}"]`);
                const titleEl = applyBtn.closest('.vmo-rec-main')?.querySelector('[data-rec-title]');
                const whyEl = applyBtn.closest('.vmo-rec-main')?.querySelector('[data-rec-why]');
                const refinement = input ? input.value.trim() : '';
                if (!refinement) { if (status) status.textContent = 'Please describe your change.'; return; }
                
                // Check if user wants to remove this entirely
                const removalKeywords = ['remove', 'don\'t want', 'dont want', 'no need', 'skip', 'eliminate', 'delete', 'get rid'];
                const isRemoval = removalKeywords.some(kw => refinement.toLowerCase().includes(kw));
                
                if (isRemoval) {
                    // Simply uncheck the item
                    const checkbox = applyBtn.closest('.vmo-rec-item')?.querySelector('.vmo-rec-checkbox');
                    if (checkbox) checkbox.checked = false;
                    if (titleEl) titleEl.style.textDecoration = 'line-through';
                    if (titleEl) titleEl.style.opacity = '0.5';
                    if (status) { status.textContent = '✅ Removed from generation plan.'; status.style.color = '#00ff9a'; }
                    const panel = ui.querySelector(`[data-refine-panel="${applyId}"]`);
                    if (panel) setTimeout(() => { panel.style.display = 'none'; }, 1500);
                    
                    // Store the refinement note for AI context
                    const recItem = applyBtn.closest('.vmo-rec-item');
                    if (recItem) recItem.setAttribute('data-refinement-note', refinement);
                    return;
                }

                // For non-removal refinements: use AI to refine the title/description
                if (status) { status.textContent = '⏳ Refining with AI...'; status.style.color = '#c084fc'; }
                applyBtn.disabled = true;

                const currentTitle = titleEl ? titleEl.textContent.trim() : '';
                const currentWhy = whyEl ? whyEl.textContent.trim() : '';
                
                // Build a quick AI refinement request
                const refinementPrompt = `The user has a recommendation item in their website plan:\n`
                    + `Title: "${currentTitle}"\nReason: "${currentWhy}"\n\n`
                    + `The user wants this change: "${refinement}"\n\n`
                    + `Return ONLY a JSON object with two keys: "title" (the updated recommendation title, max 80 chars) and "why" (updated reasoning, max 120 chars). `
                    + `If the user wants it removed entirely, set title to "REMOVE". Be concise.`;

                jQuery.post(vmo_ajax?.ajaxurl || ajaxurl, {
                    action: 'vmo_handle_message',
                    security: vmo_ajax?.nonce || '',
                    message: refinementPrompt,
                    context: 'recommendation_refinement',
                    quick_mode: '1'
                }).done((res) => {
                    applyBtn.disabled = false;
                    try {
                        let content = res?.data?.reply || res?.data?.message || '';
                        // Try to parse JSON from response
                        const jsonMatch = content.match(/\{[\s\S]*\}/);
                        if (jsonMatch) {
                            const parsed = JSON.parse(jsonMatch[0]);
                            if (parsed.title === 'REMOVE') {
                                const checkbox = applyBtn.closest('.vmo-rec-item')?.querySelector('.vmo-rec-checkbox');
                                if (checkbox) checkbox.checked = false;
                                if (titleEl) { titleEl.style.textDecoration = 'line-through'; titleEl.style.opacity = '0.5'; }
                                if (status) { status.textContent = '✅ Removed from plan.'; status.style.color = '#00ff9a'; }
                            } else {
                                if (parsed.title && titleEl) titleEl.textContent = parsed.title;
                                if (parsed.why && whyEl) whyEl.textContent = parsed.why;
                                if (status) { status.textContent = '✅ Refined!'; status.style.color = '#00ff9a'; }
                            }
                        } else {
                            // Fallback: just update title with user's text
                            if (titleEl) titleEl.textContent = currentTitle + ' (' + refinement + ')';
                            if (status) { status.textContent = '✅ Note added.'; status.style.color = '#00ff9a'; }
                        }
                    } catch (parseErr) {
                        if (titleEl) titleEl.textContent = currentTitle + ' (' + refinement + ')';
                        if (status) { status.textContent = '✅ Note added.'; status.style.color = '#00ff9a'; }
                    }
                    const recItem = applyBtn.closest('.vmo-rec-item');
                    if (recItem) recItem.setAttribute('data-refinement-note', refinement);
                    const panel = ui.querySelector(`[data-refine-panel="${applyId}"]`);
                    if (panel) setTimeout(() => { panel.style.display = 'none'; }, 2000);
                }).fail(() => {
                    applyBtn.disabled = false;
                    // Fallback: just append user note to title
                    if (titleEl) titleEl.textContent = currentTitle + ' (' + refinement + ')';
                    if (status) { status.textContent = '✅ Note saved.'; status.style.color = '#00ff9a'; }
                    const panel = ui.querySelector(`[data-refine-panel="${applyId}"]`);
                    if (panel) setTimeout(() => { panel.style.display = 'none'; }, 2000);
                });
                return;
            }
        });

        // Use finalProfile for compatibility (profile was already declared with `let` earlier)
        approveBtn.addEventListener('click', async () => {
            approveBtn.disabled = true;
            approveBtn.textContent = 'Saving…';
            try {
                // Capture edits + selections + user choices
                const accepted = [];
                const rejected = [];
                const updatedItems = [];
                const userChoices = {};

                // Capture radio button choices for dynamic recommendations
                ui.querySelectorAll('input[type="radio"][data-choice-for]:checked').forEach(radio => {
                    const itemId = radio.getAttribute('data-choice-for');
                    userChoices[itemId] = radio.value;
                });

                ui.querySelectorAll('input[type="checkbox"][data-rec-id]').forEach(cb => {
                    const id = cb.getAttribute('data-rec-id') || '';
                    // Handle both dynamic (div container) and static (label container) layouts
                    const container = cb.closest('div') || cb.closest('label');
                    const recItem = cb.closest('.vmo-rec-item');
                    const titleEl = container?.querySelector('[data-rec-title]');
                    const whyEl = container?.querySelector('[data-rec-why]');
                    const label = (titleEl && titleEl.textContent) ? titleEl.textContent.trim() : '';
                    const why = (whyEl && whyEl.textContent) ? whyEl.textContent.trim() : '';
                    const choice = userChoices[id] || 'recommended';
                    const refinementNote = recItem ? (recItem.getAttribute('data-refinement-note') || '') : '';
                    const item = { id, label, why, user_choice: choice, refinement_note: refinementNote };
                    updatedItems.push(item);
                    if (cb.checked) accepted.push(id || label);
                    else rejected.push(id || label);
                });

                // Persist approvals with user choices
                const recommendationsApproval = { 
                    accepted: accepted.filter(Boolean), 
                    rejected: rejected.filter(Boolean),
                    user_choices: userChoices 
                };
                this.state.approvals = this.state.approvals || {};
                this.state.approvals.recommendations = recommendationsApproval;
                this.consultation_data.approvals = this.consultation_data.approvals || {};
                this.consultation_data.approvals.recommendations = recommendationsApproval;

                // Persist profile edits
                const nextProfile = { ...(finalProfile || {}) };
                nextProfile.items = updatedItems;
                this.state.recommendations_profile = nextProfile;
                this.consultation_data.recommendations_profile = nextProfile;

                if (window.vmo_consultant_context_v1) {
                    await this.saveConsultationData({
                        recommendation_checklist: checklist,
                        recommendations_profile: nextProfile,
                        approvals: { recommendations: recommendationsApproval }
                    });
                }

                this.addMessageToUI('ai', '✅ Perfect — your personalized recommendations locked in. Moving to the next step.');
                
                // ✅ Mark phase 4 as confirmed to prevent re-presentation
                // Don't await: ensure we can always advance even if saving stalls.
                try {
                    const p = this.updatePhaseFlag('phase4_confirmed', true);
                    if (p && typeof p.catch === 'function') {
                        p.catch(() => {});
                    }
                } catch (_) {}
                
                console.log('✅ Recommendations approved - calling advancePhase()...');
                try { 
                    await this.advancePhase(); 
                    console.log('✅ advancePhase() completed successfully after recommendations');
                } catch(advanceErr) {
                    console.error('❌ Error advancing phase after recommendations approval:', advanceErr);
                    this.addMessageToUI('system', '⚠️ There was an issue advancing to the next phase. Please type "continue" to proceed.');
                }
            } catch (e) {
                console.warn('Failed to approve recommendations:', e);
                approveBtn.disabled = false;
                approveBtn.textContent = '✅ Approve & Continue';
            }
        });

        this.addNodeMessageToUI('ai', ui);
        await this.saveMessageToDatabase('ai', checklist);
        this.consultation_data.recommendation_checklist = checklist;
        // ✅ FIX: Use finalProfile (from buildRecommendationChecklist) instead of profile which can be null
        this.state.recommendations_profile = finalProfile;
        this.consultation_data.recommendations_profile = finalProfile;

        // ✅ FIX: Use finalProfile instead of profile to avoid "Cannot read properties of null (reading 'items')" error
        const acceptedItems = ((finalProfile && finalProfile.items) || []).map(item => item.id || item.label).filter(Boolean);
        const recommendationsApproval = {
            accepted: acceptedItems,
            rejected: []
        };
        this.state.approvals = this.state.approvals || {};
        this.state.approvals.recommendations = recommendationsApproval;
        this.consultation_data.approvals = this.consultation_data.approvals || {};
        this.consultation_data.approvals.recommendations = recommendationsApproval;

        this.phase4ChecklistPresented = true;
        if (window.vmo_consultant_context_v1) {
            await this.saveConsultationData({
                recommendation_checklist: checklist,
                recommendations_profile: finalProfile,
                approvals: { recommendations: recommendationsApproval }
            });
        }
        await this.updatePhaseFlag('phase4_presented', true);
        this.refreshInsightsUI();
        
        // ✅ Update main progress bar to 55% after recommendations presented
        this.updateMainProgressBar(55, 'Recommendations ready for review');
        
        console.log('✅ Phase 4 checklist presented successfully. User can now approve to continue to Phase 5.');
        
        // ✅ VOICE MODE: Notify user to close modal to see and interact with the checklist
        if (this.isVoiceEnabled) {
            setTimeout(() => {
                this.notifyCloseModalToSee('your personalized recommendations checklist', 'You can review each recommendation, uncheck items you don\'t want, and click the Approve button to continue.');
            }, 500);
        }
    }

    getConfiguredEmailAccountCount() {
        const accounts = this.state?.email_roles_meta?.email_accounts;
        if (!accounts || typeof accounts !== 'object') {
            return 0;
        }
        return Object.values(accounts).filter(account => {
            const email = (account?.email || '').toString().trim();
            return !!email && email.includes('@');
        }).length;
    }

    hasPhase6SkipApproval() {
        return !!(this.state?.email_roles_meta?.phase6_skip_approved);
    }

    setPhase6SkipApproval(isApproved) {
        this.ensureProvisioningContainers();
        this.state.email_roles_meta.phase6_skip_approved = !!isApproved;
        this.consultation_data.email_roles_meta = {
            ...(this.consultation_data.email_roles_meta || {}),
            phase6_skip_approved: !!isApproved
        };
        this.scheduleStateSave({ immediate: true });
    }

    isPhase6ProvisioningModalOpen() {
        if (typeof document === 'undefined') {
            return false;
        }
        const isVisible = (el) => !!(el && el.parentElement && window.getComputedStyle(el).display !== 'none');
        const emailModal = document.getElementById('vmo-email-setup-modal');
        const bulkModal = document.getElementById('vmo-bulk-email-modal');
        return isVisible(emailModal) || isVisible(bulkModal);
    }

    getPhase6Readiness() {
        if (this.isPhase6ProvisioningModalOpen()) {
            return {
                ready: false,
                reason: 'Please finish or close the open email setup modal before continuing.'
            };
        }

        const configuredEmailAccounts = this.getConfiguredEmailAccountCount();
        if (configuredEmailAccounts > 0) {
            return { ready: true };
        }

        if (this.hasPhase6SkipApproval()) {
            return { ready: true };
        }

        return {
            ready: false,
            reason: 'Please set up at least one business email account, or click “Skip for Now” and confirm the skip first.'
        };
    }
    
    // ═══════════════════════════════════════════════════════════════════
    // ✅ Email Pre-Check Dialog — asks user about existing domain emails
    // Returns: 'has_emails' | 'setup_new' | 'skip'
    // ═══════════════════════════════════════════════════════════════════
    _showEmailPreCheckDialog(enforcedDomain, businessName) {
        return new Promise((resolve) => {
            const dialogHtml = `
                <div class="vmo-email-precheck" style="
                    background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%);
                    border: 1px solid rgba(59, 130, 246, 0.4);
                    border-radius: 16px; padding: 28px; margin: 16px 0;
                    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
                ">
                    <div style="text-align: center; margin-bottom: 20px;">
                        <div style="font-size: 40px; margin-bottom: 8px;">📧</div>
                        <h2 style="margin: 0; color: #f1f5f9; font-size: 20px; font-weight: 700;">
                            Business Email Setup
                        </h2>
                        <p style="margin: 8px 0 0; color: #94a3b8; font-size: 14px;">
                            Professional emails are essential for ${businessName || 'your business'}.
                        </p>
                    </div>

                    <!-- Enforced Domain Notice -->
                    <div style="
                        background: rgba(59, 130, 246, 0.1);
                        border: 1px solid rgba(59, 130, 246, 0.3);
                        border-radius: 10px; padding: 14px; margin-bottom: 20px;
                        text-align: center;
                    ">
                        <span style="color: #93c5fd; font-size: 13px;">
                            🌐 Your detected domain: <strong style="color: #60a5fa; font-size: 15px;">${enforcedDomain}</strong>
                        </span>
                        <br/>
                        <span style="color: #64748b; font-size: 11px;">
                            All business emails must use this domain for security &amp; brand consistency.
                        </span>
                    </div>

                    <!-- Question -->
                    <p style="color: #e2e8f0; font-size: 15px; text-align: center; margin-bottom: 24px; font-weight: 500;">
                        Do you already have email addresses on <strong style="color: #60a5fa;">@${enforcedDomain}</strong>?
                    </p>

                    <!-- Three Choices -->
                    <div style="display: flex; flex-direction: column; gap: 10px;">
                        <button id="vmo-precheck-have-emails" style="
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white; padding: 14px 20px; border-radius: 10px;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                            display: flex; align-items: center; gap: 12px;
                            text-align: left; transition: all 0.2s ease;
                        ">
                            <span style="font-size: 22px;">✅</span>
                            <div>
                                <div>Yes, I already have domain emails</div>
                                <div style="font-size: 11px; font-weight: 400; opacity: 0.85; margin-top: 2px;">
                                    I'll enter my existing @${enforcedDomain} addresses
                                </div>
                            </div>
                        </button>

                        <button id="vmo-precheck-setup-new" style="
                            background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%);
                            border: none; color: white; padding: 14px 20px; border-radius: 10px;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                            display: flex; align-items: center; gap: 12px;
                            text-align: left; transition: all 0.2s ease;
                        ">
                            <span style="font-size: 22px;">🆕</span>
                            <div>
                                <div>No, I need to create new email accounts</div>
                                <div style="font-size: 11px; font-weight: 400; opacity: 0.85; margin-top: 2px;">
                                    Set up new professional emails via Zoho Mail
                                </div>
                            </div>
                        </button>

                        <button id="vmo-precheck-skip" style="
                            background: transparent;
                            border: 1px solid rgba(148, 163, 184, 0.3);
                            color: #94a3b8; padding: 14px 20px; border-radius: 10px;
                            font-size: 14px; cursor: pointer;
                            display: flex; align-items: center; gap: 12px;
                            text-align: left; transition: all 0.2s ease;
                        ">
                            <span style="font-size: 22px;">⏭️</span>
                            <div>
                                <div>Skip email setup for now</div>
                                <div style="font-size: 11px; font-weight: 400; opacity: 0.7; margin-top: 2px;">
                                    I'll configure email later from the dashboard
                                </div>
                            </div>
                        </button>
                    </div>
                </div>
            `;

            const dialogContainer = this.addNodeMessageToUI('ai', dialogHtml);

            setTimeout(() => {
                const resolved = { done: false };
                const scope = dialogContainer && dialogContainer.querySelector ? dialogContainer : document;
                const findInScope = (selector) => {
                    if (scope && scope.querySelector) {
                        const scopedMatch = scope.querySelector(selector);
                        if (scopedMatch) {
                            return scopedMatch;
                        }
                    }

                    const matches = Array.from(document.querySelectorAll(selector));
                    return matches.length ? matches[matches.length - 1] : null;
                };
                const finish = (result) => {
                    if (resolved.done) return;
                    resolved.done = true;
                    // Disable all buttons to prevent re-click
                    ['vmo-precheck-have-emails', 'vmo-precheck-setup-new', 'vmo-precheck-skip'].forEach(id => {
                        const b = findInScope('#' + id);
                        if (b) { b.disabled = true; b.style.opacity = '0.5'; b.style.pointerEvents = 'none'; }
                    });
                    resolve(result);
                };

                const haveBtn = findInScope('#vmo-precheck-have-emails');
                const newBtn = findInScope('#vmo-precheck-setup-new');
                const skipBtn = findInScope('#vmo-precheck-skip');

                if (haveBtn) {
                    haveBtn.addEventListener('click', () => {
                        this._collectExistingEmails(enforcedDomain).then((result) => {
                            if (result === 'saved') {
                                finish('has_emails');
                                return;
                            }

                            if (result === 'back') {
                                finish('back');
                                return;
                            }

                            finish('skip');
                        });
                    });
                }
                if (newBtn) {
                    newBtn.addEventListener('click', () => finish('setup_new'));
                }
                if (skipBtn) {
                    skipBtn.addEventListener('click', () => finish('skip'));
                }
            }, 300);
        });
    }

    // ═══════════════════════════════════════════════════════════
    // ✅ Collect existing email addresses from the user
    // Shows input fields, validates domain match, records them.
    // ═══════════════════════════════════════════════════════════
    _collectExistingEmails(enforcedDomain) {
        return new Promise((resolve) => {
            const collectHtml = `
                <div id="vmo-existing-emails-form" style="
                    background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%);
                    border: 1px solid rgba(34, 197, 94, 0.3);
                    border-radius: 16px; padding: 24px; margin: 16px 0;
                ">
                    <h3 style="color: #f1f5f9; margin: 0 0 8px; font-size: 16px;">
                        📬 Enter Your Existing Email Addresses
                    </h3>
                    <p style="color: #94a3b8; font-size: 13px; margin: 0 0 16px;">
                        Enter your <strong style="color: #60a5fa;">@${enforcedDomain}</strong> email addresses below (one per line).
                        Only addresses ending in <strong>@${enforcedDomain}</strong> are accepted.
                    </p>
                    <textarea id="vmo-existing-emails-input" rows="5" placeholder="info@${enforcedDomain}&#10;support@${enforcedDomain}&#10;billing@${enforcedDomain}" style="
                        width: 100%; background: rgba(15, 23, 42, 0.8); border: 1px solid rgba(148, 163, 184, 0.3);
                        border-radius: 8px; color: #f1f5f9; padding: 12px; font-size: 14px;
                        font-family: monospace; resize: vertical; box-sizing: border-box;
                    "></textarea>
                    <div id="vmo-existing-emails-error" style="color: #ef4444; font-size: 12px; margin-top: 6px; display: none;"></div>
                    <div style="display: flex; gap: 10px; margin-top: 14px;">
                        <button id="vmo-existing-emails-save" style="
                            background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                            border: none; color: white; padding: 10px 24px; border-radius: 8px;
                            font-size: 14px; font-weight: 600; cursor: pointer;
                        ">
                            ✓ Save Email Addresses
                        </button>
                        <button id="vmo-existing-emails-back" style="
                            background: transparent; border: 1px solid rgba(148, 163, 184, 0.3);
                            color: #94a3b8; padding: 10px 20px; border-radius: 8px;
                            font-size: 14px; cursor: pointer;
                        ">
                            Back
                        </button>
                    </div>
                </div>
            `;

            const formContainer = this.addNodeMessageToUI('ai', collectHtml);

            setTimeout(() => {
                const scope = formContainer && formContainer.querySelector ? formContainer : document;
                const saveBtn = scope.querySelector('#vmo-existing-emails-save') || Array.from(document.querySelectorAll('#vmo-existing-emails-save')).pop() || null;
                const backBtn = scope.querySelector('#vmo-existing-emails-back') || Array.from(document.querySelectorAll('#vmo-existing-emails-back')).pop() || null;
                const input = scope.querySelector('#vmo-existing-emails-input') || Array.from(document.querySelectorAll('#vmo-existing-emails-input')).pop() || null;
                const errDiv = scope.querySelector('#vmo-existing-emails-error') || Array.from(document.querySelectorAll('#vmo-existing-emails-error')).pop() || null;

                if (saveBtn) {
                    saveBtn.addEventListener('click', () => {
                        const raw = (input?.value || '').trim();
                        if (!raw) {
                            if (errDiv) { errDiv.textContent = 'Please enter at least one email address.'; errDiv.style.display = 'block'; }
                            return;
                        }

                        const lines = raw.split(/[\n,;]+/).map(e => e.trim().toLowerCase()).filter(Boolean);
                        const valid = [];
                        const invalid = [];

                        lines.forEach(email => {
                            const domain = email.split('@')[1];
                            if (domain === enforcedDomain.toLowerCase()) {
                                valid.push(email);
                            } else {
                                invalid.push(email);
                            }
                        });

                        if (invalid.length > 0) {
                            if (errDiv) {
                                errDiv.innerHTML = '❌ These emails don\'t match <strong>@' + enforcedDomain + '</strong>:<br/>' +
                                    invalid.map(e => '<code style="color:#fca5a5;">' + e + '</code>').join(', ') +
                                    '<br/>Please correct them or remove non-matching entries.';
                                errDiv.style.display = 'block';
                            }
                            return;
                        }

                        if (valid.length === 0) {
                            if (errDiv) { errDiv.textContent = 'No valid email addresses found.'; errDiv.style.display = 'block'; }
                            return;
                        }

                        // Record the emails in state
                        const categories = ['general', 'support', 'transactions', 'customercare', 'bizdev', 'marketing', 'newsletter'];
                        valid.forEach((email, idx) => {
                            const cat = categories[idx] || 'general';
                            this.addEmailAccount(cat, {
                                email: email,
                                password: '',
                                display_name: email.split('@')[0],
                                provider_preset: 'existing',
                                imap_host: '',
                                imap_port: 993,
                                smtp_host: '',
                                smtp_port: 587
                            });
                        });

                        // Disable form
                        saveBtn.disabled = true; saveBtn.style.opacity = '0.5';
                        if (backBtn) { backBtn.disabled = true; backBtn.style.opacity = '0.5'; }
                        if (input) { input.disabled = true; }

                        this.addMessageToUI('ai', '✅ ' + valid.length + ' email address' + (valid.length > 1 ? 'es' : '') + ' recorded: ' + valid.join(', '));
                        resolve('saved');
                    });
                }

                if (backBtn) {
                    backBtn.addEventListener('click', () => {
                        // Re-show the precheck
                        this._phase6EmailPreCheckDone = false;
                        resolve('back');
                    });
                }
            }, 300);
        });
    }

    // ✅ NEW: Present Phase 6 Email Setup Wizard
    async presentPhase6EmailSetup() {
        console.log('📧 Presenting Phase 6 Email Setup Wizard...');
        
        // Get the email addresses confirmed in Phase 2
        const emailRoles = this.state.email_roles || {};
        const enforcedDomain = this.getEnforcedDomain() || 'yourdomain.com';
        const businessName = this.consultation_data?.extracted_information?.businessName || 
                            this.consultation_data?.business_name || 'Your Business';

        // ═══════════════════════════════════════════════════════
        // ✅ PRE-CHECK: Ask if user already has domain-based emails
        // Before launching the full wizard, give user 3 options:
        //   1) "I already have emails" → collect them → skip Zoho
        //   2) "Set up new emails" → show full Zoho wizard
        //   3) "Skip email setup" → fast-track to website generation
        // Domain is enforced from the WordPress installation URL.
        // ═══════════════════════════════════════════════════════
        if (!this._phase6EmailPreCheckDone) {
            while (!this._phase6EmailPreCheckDone) {
                const preCheckResult = await this._showEmailPreCheckDialog(enforcedDomain, businessName);

                if (preCheckResult === 'back') {
                    continue;
                }

                this._phase6EmailPreCheckDone = true;

                if (preCheckResult === 'has_emails') {
                    // User already has emails — they've been collected by the dialog.
                    // Set skip approval and proceed to generation.
                    this.setPhase6SkipApproval(true);
                    this.addMessageToUI('ai', '✅ Your existing email accounts have been recorded. Proceeding to website generation...');
                    try { await this.advancePhase(); } catch (_) {}
                    return;
                }

                if (preCheckResult === 'skip') {
                    // User wants to skip email entirely
                    this.setPhase6SkipApproval(true);
                    this.addMessageToUI('ai', '✅ Email setup skipped. You can configure business email accounts later from your dashboard. Proceeding to website generation...');
                    try { await this.advancePhase(); } catch (_) {}
                    return;
                }

                // preCheckResult === 'setup_new' → fall through to full wizard below
            }
        }
        
        // Default email categories — derived from canonical VMO_EMAIL_CATEGORIES
        const categoryExamples = {
            general: 'info', support: 'support', transactions: 'billing',
            customercare: 'care', bizdev: 'partnerships', marketing: 'marketing',
            newsletter: 'newsletter'
        };
        const defaultCategories = (typeof VMO_EMAIL_CATEGORIES !== 'undefined' ? VMO_EMAIL_CATEGORIES : [
            'general', 'support', 'transactions', 'customercare', 'bizdev', 'marketing', 'newsletter'
        ]).map(id => ({
            id,
            label: (typeof VMO_EMAIL_CATEGORY_LABELS !== 'undefined' && VMO_EMAIL_CATEGORY_LABELS[id])
                ? VMO_EMAIL_CATEGORY_LABELS[id]
                : id.charAt(0).toUpperCase() + id.slice(1).replace(/([A-Z])/g, ' $1'),
            example: categoryExamples[id] || id
        }));
        
        // Build the email setup checklist HTML
        let emailListHtml = '';
        defaultCategories.forEach(cat => {
            const configuredEmail = emailRoles[cat.id] || `${cat.example}@${enforcedDomain}`;
            const isConfigured = !!emailRoles[cat.id];
            const statusIcon = isConfigured ? '✅' : '⏳';
            const statusClass = isConfigured ? 'configured' : 'pending';
            
            emailListHtml += `
                <div class="vmo-email-item" data-category="${cat.id}" style="
                    background: rgba(30, 41, 59, 0.8);
                    border: 1px solid ${isConfigured ? 'rgba(34, 197, 94, 0.3)' : 'rgba(147, 51, 234, 0.3)'};
                    border-radius: 10px; padding: 14px; margin-bottom: 10px;
                    display: flex; align-items: center; justify-content: space-between;
                    transition: all 0.2s ease;
                ">
                    <div style="display: flex; align-items: center; gap: 12px;">
                        <span style="font-size: 18px;">${statusIcon}</span>
                        <div>
                            <div style="font-weight: 600; color: #f1f5f9; font-size: 14px;">${cat.label}</div>
                            <div style="color: #94a3b8; font-size: 12px;">${configuredEmail}</div>
                        </div>
                    </div>
                    <button class="vmo-setup-email-btn" data-category="${cat.id}" style="
                        background: ${isConfigured ? 'rgba(34, 197, 94, 0.2)' : 'linear-gradient(135deg, #7c3aed 0%, #a855f7 100%)'};
                        border: 1px solid ${isConfigured ? 'rgba(34, 197, 94, 0.4)' : 'rgba(147, 51, 234, 0.5)'};
                        color: ${isConfigured ? '#86efac' : 'white'};
                        padding: 8px 16px; border-radius: 6px;
                        font-size: 12px; font-weight: 500; cursor: pointer;
                        transition: all 0.2s ease;
                    ">
                        ${isConfigured ? 'Configured ✓' : 'Set Up →'}
                    </button>
                </div>
            `;
        });
        
        // Create the main Phase 6 setup interface
        const phase6Html = `
            <div class="vmo-phase6-email-wizard" style="
                background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%);
                border: 1px solid rgba(147, 51, 234, 0.3);
                border-radius: 16px; padding: 24px; margin: 16px 0;
                box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            ">
                <!-- Header -->
                <div style="text-align: center; margin-bottom: 24px;">
                    <div style="font-size: 48px; margin-bottom: 8px;">📧</div>
                    <h2 style="margin: 0; color: #f1f5f9; font-size: 22px; font-weight: 700;">
                        Email System Setup
                    </h2>
                    <p style="margin: 8px 0 0 0; color: #94a3b8; font-size: 14px;">
                        Configure your professional email accounts for ${businessName}
                    </p>
                </div>
                
                <!-- Info Box -->
                <div style="
                    background: rgba(59, 130, 246, 0.1);
                    border: 1px solid rgba(59, 130, 246, 0.3);
                    border-radius: 10px; padding: 14px; margin-bottom: 20px;
                ">
                    <div style="display: flex; gap: 10px; align-items: flex-start;">
                        <span style="font-size: 18px;">💡</span>
                        <div style="color: #93c5fd; font-size: 13px; line-height: 1.5;">
                            <strong>What happens here:</strong> We'll configure your email accounts so VMO can:
                            <ul style="margin: 8px 0 0 0; padding-left: 16px;">
                                <li>Send automated emails (order confirmations, welcome emails, etc.)</li>
                                <li>Receive and route customer inquiries</li>
                                <li>Power your email marketing campaigns</li>
                            </ul>
                        </div>
                    </div>
                </div>
                
                <!-- Email Accounts List -->
                <div style="margin-bottom: 20px;">
                    <h4 style="color: #e2e8f0; margin: 0 0 12px 0; font-size: 14px; font-weight: 600;">
                        📬 Your Email Accounts
                    </h4>
                    ${emailListHtml}
                </div>
                
                <!-- Bulk Email Provider Section -->
                <div style="
                    background: rgba(147, 51, 234, 0.1);
                    border: 1px solid rgba(147, 51, 234, 0.3);
                    border-radius: 10px; padding: 16px; margin-bottom: 20px;
                ">
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                        <div>
                            <h4 style="color: #e2e8f0; margin: 0; font-size: 14px; font-weight: 600;">
                                🚀 Bulk Email Marketing (Optional)
                            </h4>
                            <p style="color: #94a3b8; font-size: 12px; margin: 4px 0 0 0;">
                                Connect Brevo, MailerLite, or another provider for mass campaigns
                            </p>
                        </div>
                        <button id="vmo-setup-bulk-email-btn" style="
                            background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%);
                            border: 1px solid rgba(147, 51, 234, 0.5);
                            color: white; padding: 10px 20px; border-radius: 6px;
                            font-size: 12px; font-weight: 500; cursor: pointer;
                        ">
                            Connect Provider →
                        </button>
                    </div>
                </div>
                
                <!-- Action Buttons -->
                <div style="display: flex; gap: 12px; justify-content: center; margin-top: 24px;">
                    <button id="vmo-skip-email-setup" style="
                        background: transparent;
                        border: 1px solid rgba(148, 163, 184, 0.3);
                        color: #94a3b8; padding: 12px 24px; border-radius: 8px;
                        font-size: 14px; cursor: pointer;
                    ">
                        Skip for Now
                    </button>
                    <button id="vmo-confirm-email-setup" style="
                        background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
                        border: none; color: white;
                        padding: 12px 32px; border-radius: 8px;
                        font-size: 14px; font-weight: 600; cursor: pointer;
                        box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3);
                    ">
                        ✓ Continue to Website Generation
                    </button>
                </div>
            </div>
        `;
        
        // Add the wizard to the chat
        const wizardContainer = this.addNodeMessageToUI('ai', phase6Html);
        
        // Attach event listeners - use longer timeout to ensure DOM is ready
        setTimeout(() => {
            console.log('📧 Attaching email button event listeners...');
            const scope = wizardContainer && wizardContainer.querySelector ? wizardContainer : document;
            
            // Individual email setup buttons
            const emailBtns = scope.querySelectorAll('.vmo-setup-email-btn');
            console.log(`📧 Found ${emailBtns.length} email setup buttons`);
            
            emailBtns.forEach(btn => {
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    const button = e.currentTarget;
                    const category = button?.dataset?.category;
                    if (!category) {
                        console.warn('📧 Email setup click ignored: missing category on button', button);
                        return;
                    }
                    console.log(`📧 Email button clicked for category: ${category}`);
                    this.showEmailSetupModal(category, (result = null) => {
                        if (result && result.email) {
                            const smtp = result.smtp_settings || {};
                            this.addEmailAccount(category, {
                                email: result.email,
                                password: result.password || '',
                                display_name: result.display_name || category,
                                provider_preset: 'custom',
                                imap_host: smtp.imap_host || `mail.${enforcedDomain}`,
                                imap_port: smtp.imap_port || 993,
                                smtp_host: smtp.smtp_host || `mail.${enforcedDomain}`,
                                smtp_port: smtp.smtp_port || 587
                            });
                        }

                        // Refresh the UI after setup
                        button.textContent = 'Configured ✓';
                        button.style.background = 'rgba(34, 197, 94, 0.2)';
                        button.style.borderColor = 'rgba(34, 197, 94, 0.4)';
                        button.style.color = '#86efac';
                        
                        // Update parent border
                        const parent = button.closest('.vmo-email-item');
                        if (parent) {
                            parent.style.borderColor = 'rgba(34, 197, 94, 0.3)';
                            const icon = parent.querySelector('span');
                            if (icon) icon.textContent = '✅';
                        }
                    });
                });
            });
            
            // Bulk email provider button
            const bulkBtn = scope.querySelector('#vmo-setup-bulk-email-btn') || Array.from(document.querySelectorAll('#vmo-setup-bulk-email-btn')).pop() || null;
            if (bulkBtn) {
                bulkBtn.addEventListener('click', () => {
                    this.showBulkEmailSetupModal(() => {
                        bulkBtn.textContent = 'Connected ✓';
                        bulkBtn.style.background = 'rgba(34, 197, 94, 0.2)';
                        bulkBtn.style.borderColor = 'rgba(34, 197, 94, 0.4)';
                    });
                });
            }
            
            // Skip button
            const skipBtn = scope.querySelector('#vmo-skip-email-setup') || Array.from(document.querySelectorAll('#vmo-skip-email-setup')).pop() || null;
            if (skipBtn) {
                skipBtn.addEventListener('click', async () => {
                    const confirmed = window.confirm('Skip email setup for now? Your website can still be generated, but business inboxes will remain unconfigured until you complete setup later.');
                    if (!confirmed) {
                        return;
                    }

                    // ✅ FIX: Disable both buttons immediately to prevent double-click race condition
                    skipBtn.disabled = true;
                    skipBtn.style.opacity = '0.5';
                    skipBtn.style.pointerEvents = 'none';
                    const _confirmBtn = scope.querySelector('#vmo-confirm-email-setup') || Array.from(document.querySelectorAll('#vmo-confirm-email-setup')).pop() || null;
                    if (_confirmBtn) { _confirmBtn.disabled = true; _confirmBtn.style.opacity = '0.5'; _confirmBtn.style.pointerEvents = 'none'; }

                    this.setPhase6SkipApproval(true);
                    this.addMessageToUI('ai', '✅ Email setup skipped for now and recorded. You can configure accounts later from your dashboard. Proceeding to website generation...');
                    try {
                        await this.advancePhase();
                    } catch (err) {
                        console.error('❌ advancePhase failed after skip:', err);
                        skipBtn.disabled = false; skipBtn.style.opacity = '1'; skipBtn.style.pointerEvents = '';
                        const __confirmBtn = scope.querySelector('#vmo-confirm-email-setup') || Array.from(document.querySelectorAll('#vmo-confirm-email-setup')).pop() || null;
                        if (__confirmBtn) { __confirmBtn.disabled = false; __confirmBtn.style.opacity = '1'; __confirmBtn.style.pointerEvents = ''; }
                        this.addMessageToUI('system', '⚠️ Something went wrong advancing. Please try clicking Continue to Website Generation.');
                    }
                });
            }
            
            // Confirm button
            const confirmBtn = scope.querySelector('#vmo-confirm-email-setup') || Array.from(document.querySelectorAll('#vmo-confirm-email-setup')).pop() || null;
            if (confirmBtn) {
                confirmBtn.addEventListener('click', async () => {
                    const readiness = this.getPhase6Readiness();
                    if (!readiness.ready) {
                        this.addMessageToUI('system', `⚠️ ${readiness.reason}`);
                        return;
                    }

                    const configuredCount = this.getConfiguredEmailAccountCount();
                    if (configuredCount > 0) {
                        this.setPhase6SkipApproval(false);
                        this.addMessageToUI('ai', `✅ Excellent! ${configuredCount} email account(s) configured. Now let's generate your complete business package...`);
                    } else if (this.hasPhase6SkipApproval()) {
                        this.addMessageToUI('ai', '✅ Skip confirmed. Proceeding to website generation...');
                    } else {
                        this.addMessageToUI('system', '⚠️ Please set up at least one business email account, or use Skip for Now and confirm the skip.');
                        return;
                    }

                    // ✅ FIX: Disable both buttons immediately to prevent double-click race condition
                    confirmBtn.disabled = true;
                    confirmBtn.style.opacity = '0.5';
                    confirmBtn.style.pointerEvents = 'none';
                    const _skipBtn = scope.querySelector('#vmo-skip-email-setup') || Array.from(document.querySelectorAll('#vmo-skip-email-setup')).pop() || null;
                    if (_skipBtn) { _skipBtn.disabled = true; _skipBtn.style.opacity = '0.5'; _skipBtn.style.pointerEvents = 'none'; }

                    try {
                        await this.advancePhase();
                    } catch (err) {
                        console.error('❌ advancePhase failed after confirm:', err);
                        confirmBtn.disabled = false; confirmBtn.style.opacity = '1'; confirmBtn.style.pointerEvents = '';
                        if (_skipBtn) { _skipBtn.disabled = false; _skipBtn.style.opacity = '1'; _skipBtn.style.pointerEvents = ''; }
                        this.addMessageToUI('system', '⚠️ Something went wrong advancing. Please try again.');
                    }
                });
            }
            
            console.log('✅ Email setup event listeners attached successfully');
        }, 300); // Increased timeout for DOM stability
        
        console.log('✅ Phase 6 Email Setup Wizard presented successfully.');
        
        // ✅ VOICE MODE: Notify user to close modal to see and interact with the email setup form
        if (this.isVoiceEnabled) {
            setTimeout(() => {
                this.notifyCloseModalToSee('the email setup wizard', 'You can select your email provider and enter your credentials to connect your business email accounts.');
            }, 500);
        }
    }
    
    // Advance to next consultation phase
    async advancePhase() {
        // ✅ FIX: Deferred-queue reentrancy guard. If advancePhase is already running
        // (e.g., from onPhaseAdvanced → runPhase5ArchitectureAndAdvance), queue one
        // follow-up call instead of silently dropping it. This prevents the deadlock
        // where Phase 5 → 6 or Phase 7 → 8 transitions never fire.
        if (this._advancePhaseInProgress) {
            console.log('⏳ advancePhase() already in progress — queuing a deferred follow-up');
            this._advancePhasePending = true;
            return;
        }
        this._advancePhaseInProgress = true;
        this._advancePhasePending = false;

        try {
        // Prefer canonical flow stored in consultation_data, fallback to state.
        const flow = this.consultation_data.consultation_flow || this.state.consultation_flow;
        
        // ✅ Safety check: ensure flow and timestamps exist
        if (!flow) {
            console.error('❌ advancePhase: consultation_flow is undefined');
            return;
        }
        if (!flow.timestamps) {
            flow.timestamps = {};
        }
        if (!flow.phases || !Array.isArray(flow.phases)) {
            console.error('❌ advancePhase: phases array is invalid');
            return;
        }

        // Hard gate: cannot move from Phase 6 -> Phase 7 until email setup is complete
        // or skip has been explicitly approved.
        if (flow.current_index === 5) {
            const readiness = this.getPhase6Readiness();
            if (!readiness.ready) {
                this.addMessageToUI('system', `⚠️ ${readiness.reason}`);
                return;
            }
        }
        
        if (flow.current_index >= flow.phases.length - 1) {
            console.log('✅ Already at final phase');
            return;
        }
        
        const prevIdx = flow.current_index;
        
        // Phase advancement: always sequential (no phase skipping)
        // All 8 phases are required: Intake → Blueprint → Research → Recommendations → Website → Systems → Roadmap → Launch
        // Payments/e-commerce are handled within Phase 5 (Website Architecture) when applicable
        flow.current_index++;
        
        flow.timestamps[`phase_${prevIdx}_end`] = new Date().toISOString();
        flow.timestamps[`phase_${flow.current_index}_start`] = new Date().toISOString();
        
        // ✅ CRITICAL FIX: Sync BOTH state locations so progress bar reads correctly
        if (!this.state.consultation_flow) {
            this.state.consultation_flow = {};
        }
        this.state.consultation_flow.current_index = flow.current_index;
        this.state.consultation_flow.current_phase_index = flow.current_index;
        this.state.consultation_flow.phases = flow.phases;
        this.state.consultation_flow.timestamps = flow.timestamps;
        this.state.consultation_flow.status = flow.status || 'in_progress';
        
        console.log(`📈 Advanced: Phase ${prevIdx + 1} → Phase ${flow.current_index + 1}`);
        
        // ✅ FIX: Update progress bar immediately when phase changes
        this.updatePhaseProgressBar();
        
        // Persist updated flow back into consultation_data if we were using state fallback.
        if (!this.consultation_data.consultation_flow) {
            this.consultation_data.consultation_flow = flow;
        }

        await this.saveConsultationData(this.consultation_data);
        await this.onPhaseAdvanced(flow.current_index);

        // Enforce rules for the new phase
        this.enforcePhaseAdherence();
        this.refreshAllowedActions();

        } finally {
            // ✅ FIX: Always clear reentrancy guard, then execute any queued follow-up
            this._advancePhaseInProgress = false;
            if (this._advancePhasePending) {
                this._advancePhasePending = false;
                console.log('▶️ Executing deferred advancePhase() follow-up');
                // Use setTimeout(0) to avoid deep call stack and let current frame complete
                setTimeout(() => this.advancePhase(), 0);
            }
        }
    }
    
    // Orchestration hooks when phase advances
    async onPhaseAdvanced(phaseIndex) {
        console.log(`🎯 Phase ${phaseIndex + 1} orchestration triggered`);
        
        // Phase 3: Deep Industry & Market Research → trigger competitor scan
        if (phaseIndex === 2) {
            console.log('🔍 Phase 3 Reached: Triggering competitive research UI...');
            this.updateMainProgressBar(25, 'Phase 3: Deep Research · Ready');
            
            // ✅ CRITICAL FIX: Only show confirmation if research isn't already starting
            // (confirmAndStartDeepResearch sets phase3ResearchRequested before calling ensurePhaseAtLeast)
            if (!this.phase3ResearchRequested) {
                setTimeout(() => {
                    this.showResearchConfirmation();
                }, 1000);
            }
        }
        
        // Phase 5: Website & Experience Architecture → configure site, payments if e-commerce
        if (phaseIndex === 4) {
            console.log('🏗️ Phase 5: Website & Experience Architecture...');
            this.updateMainProgressBar(55, 'Designing website architecture...');

            // ✅ Rich progress interface for Phase 5
            const phase5ProgressHtml = this.createPhase5ProgressIndicator();
            this.addNodeMessageToUI('system', phase5ProgressHtml);
            await new Promise(r => setTimeout(r, 100)); // Let DOM render

            // Step 1: Analyzing research data
            this.updatePhase5Progress(1, 5, '📊 Analyzing research findings & approved recommendations...');
            await this.delay(800);

            // Step 2: Deriving architecture
            this.updatePhase5Progress(2, 5, '🏗️ Designing page structure & conversion flow...');
            await this.delay(600);

            await this.runPhase5ArchitectureAndAdvance();

            // Step 3-5 are updated inside runPhase5ArchitectureAndAdvance
            this.hidePhase5Progress();
        }
        
        // Phase 6: Systems, Automations & Compliance → email, CRM, phone, legal
        if (phaseIndex === 5) {
            console.log('⚙️ Phase 6: Setting up systems, automations & compliance...');
            this.updateMainProgressBar(70, 'Setting up business systems...');

            // ✅ Rich progress interface for Phase 6
            const phase6ProgressHtml = this.createPhase6ProgressIndicator();
            this.addNodeMessageToUI('system', phase6ProgressHtml);
            await new Promise(r => setTimeout(r, 100)); // Let DOM render

            // Step 1: E-commerce setup
            this.updatePhase6Progress(1, 5, '🛒 Checking e-commerce & store requirements...');
            try {
                await this.setupPhase6StoreAndCatalog();
            } catch (e) {
                console.warn('Phase 6 store/catalog setup failed (continuing):', e);
            }

            // Step 2: CRM & contact systems
            this.updatePhase6Progress(2, 5, '📇 Configuring CRM & contact management...');
            await this.delay(500);

            // Step 3: Compliance documents
            this.updatePhase6Progress(3, 5, '⚖️ Preparing compliance & legal framework...');
            await this.delay(500);

            // Step 4: Phone & communications
            this.updatePhase6Progress(4, 5, '📞 Setting up phone & communication channels...');
            await this.delay(400);

            // Step 5: Email setup wizard
            this.updatePhase6Progress(5, 5, '📧 Launching email setup wizard...');
            this.hidePhase6Progress();

            // Then present email setup wizard
            await this.presentPhase6EmailSetup();
        }
        
        // Phase 7: Roadmap & Daily Execution Model → generate business assets + roadmap
        if (phaseIndex === 6) {
            console.log('🏗️ Generating complete business package and roadmap...');
            this.addMessageToUI('system', '🏗️ Building your website, legal documents, email system, and roadmap...');
            this.updateMainProgressBar(85, 'Building your website...');
            
            await this.triggerFullBusinessGeneration();
        }
        
        // Phase 8: Launch Readiness & Deliverables → present dashboard and launch
        if (phaseIndex === 7) {
            console.log('🚀 Preparing Dashboard and Go-Live...');
            this.addMessageToUI('system', '🚀 Presenting your Business Dashboard and preparing Go-Live...');
            this.updateMainProgressBar(100, 'Ready for launch!');
            await this.runPhase8LaunchReadiness();
        }
    }

    async runPhase8LaunchReadiness() {
        if (this._phase8LaunchInProgress) {
            return;
        }
        this._phase8LaunchInProgress = true;

        try {
            await this.populateDeliverables();

            if (!this.state.approvals) this.state.approvals = {};
            if (!this.consultation_data.approvals) this.consultation_data.approvals = {};

            this.state.approvals.artifacts_generated = true;
            this.consultation_data.approvals.artifacts_generated = true;

            try { await this.updatePhaseFlag('phase8_completed', true); } catch (_) {}

            if (window.vmo_consultant_context_v1) {
                await this.saveConsultationData({
                    approvals: this.consultation_data.approvals
                });
            }
        } catch (error) {
            console.warn('Phase 8 launch readiness encountered an issue:', error);
        } finally {
            this._phase8LaunchInProgress = false;
        }
    }

    derivePhase5WebsitePlan() {
        const info = this.getDisplayExtractedPayload();
        const businessName = info.businessName || info.business_name || 'Your Business';
        const offerings = Array.isArray(info.offerings)
            ? info.offerings
            : (typeof info.offerings === 'string' && info.offerings.trim() ? [info.offerings.trim()] : []);

        const needsEcommerce = !!(info.ecommerce_needed || info.needsEcommerce || info.needs?.ecommerce || info.needs?.payments);
        const needsBooking = !!(info.needsBooking || info.needs?.booking);

        const rawCustomPages = Array.isArray(info.customPages) ? info.customPages : [];
        const pageDescriptions = this.isPlainObject(info.pageDescriptions) ? info.pageDescriptions : {};

        // ✅ Pull research data, recommendations, and competitor analysis for strategic planning
        const researchData = this.consultation_data?.deep_research_results || {};
        const structuredIntel = this.getStructuredMarketIntel(researchData);
        const topCompetitors = structuredIntel.top_competitors || structuredIntel.top_earners_shareholders || [];
        const indirectCompetitors = structuredIntel.indirect_competitors || [];
        const marketPosition = structuredIntel.market_position || {};
        const idealTarget = structuredIntel.ideal_target_market || {};
        const approvedRecs = this.consultation_data?.approved_items || [];
        const allRecommendations = this.consultation_data?.recommendation_items || this.consultation_data?.recommendations || [];
        const industryName = info.industry || structuredIntel.confirmed_industry || '';

        // ✅ Derive unique selling points from competitor weaknesses
        const competitorWeaknesses = topCompetitors
            .filter(c => c && c.weakness)
            .map(c => c.weakness)
            .slice(0, 3);

        // ✅ Derive recommended pages from approved recommendations
        const recommendedPages = [];
        (Array.isArray(approvedRecs) ? approvedRecs : []).forEach(rec => {
            const title = typeof rec === 'object' ? (rec.title || rec.name || '') : String(rec || '');
            if (/portfolio|gallery|case.?stud/i.test(title)) recommendedPages.push({ key: 'portfolio', title: 'Portfolio / Case Studies', goal: 'Showcase completed work to build credibility and drive conversions through visual proof.' });
            if (/testimonial|review/i.test(title)) recommendedPages.push({ key: 'testimonials', title: 'Testimonials & Reviews', goal: 'Leverage social proof from real customers to overcome objections and build trust.' });
            if (/blog|content|article/i.test(title)) recommendedPages.push({ key: 'blog', title: 'Blog / Resources', goal: 'Capture organic search traffic, establish industry authority, and nurture leads.' });
            if (/pric/i.test(title)) recommendedPages.push({ key: 'pricing', title: 'Pricing', goal: 'Remove friction from the buying decision with transparent, competitive pricing.' });
            if (/faq/i.test(title)) recommendedPages.push({ key: 'faq', title: 'FAQ', goal: 'Answer common objections and reduce support inquiries while boosting SEO.' });
            if (/land|funnel|lead/i.test(title)) recommendedPages.push({ key: 'landing', title: 'Landing Page / Lead Funnel', goal: 'Convert paid traffic and campaigns into qualified leads with focused messaging.' });
        });

        // ✅ Strategic page goals tied to research data
        const competitorNames = topCompetitors.slice(0, 3).map(c => c?.name).filter(Boolean).join(', ');
        const targetAudience = typeof idealTarget === 'object' ? (idealTarget.primary || '') : String(idealTarget || '');

        const defaultPages = [
            {
                key: 'home',
                title: 'Home',
                primary_goal: targetAudience
                    ? `Immediately communicate value to ${targetAudience} and differentiate from ${competitorNames || 'competitors'} within 3 seconds.`
                    : 'Communicate value proposition and direct visitors to the primary call-to-action.',
                sections: ['Hero with Clear Value Proposition', 'Problem/Solution Statement', 'Key Offerings Grid', 'Social Proof / Testimonials', 'Trust Signals & Certifications', 'Primary CTA'],
                strategic_notes: competitorWeaknesses.length > 0
                    ? `Address competitor weakness: ${competitorWeaknesses[0]}. Lead with what they lack.`
                    : 'Lead with unique differentiators from research findings.'
            },
            {
                key: 'about',
                title: 'About',
                primary_goal: 'Build trust through authentic story, proven expertise, and industry credibility that competitors lack.',
                sections: ['Brand Story & Origin', 'Mission & Vision', 'Industry Credentials', 'Team / Founder Spotlight', 'Trust Badges & Certifications'],
                strategic_notes: 'Position founder/team as the human advantage over larger, faceless competitors.'
            },
            {
                key: 'services',
                title: needsEcommerce ? 'Products / Services' : 'Services',
                primary_goal: `Present each offering with clear benefits, outcomes, and pricing — structured to convert ${targetAudience || 'target audience'}.`,
                sections: ['Offerings Overview', 'Individual Service/Product Detail', 'Benefits & Outcomes', 'Pricing/Packages', 'Comparison Table', 'CTA Blocks'],
                strategic_notes: offerings.length > 0
                    ? `Feature: ${offerings.slice(0, 3).join(', ')}. Emphasize outcomes over features.`
                    : 'Emphasize outcomes and benefits over feature lists.'
            },
            {
                key: 'contact',
                title: 'Contact',
                primary_goal: 'Capture high-intent leads with friction-free forms and multiple contact options.',
                sections: ['Smart Contact Form', 'Business Location & Map', 'Phone/Email/Chat Options', 'Response Time Promise', 'FAQ Snippets'],
                strategic_notes: 'Include a response-time guarantee — something most competitors skip.'
            }
        ];

        if (needsEcommerce) {
            defaultPages.push({
                key: 'shop',
                title: 'Shop',
                primary_goal: 'Enable direct transactions with a streamlined checkout that minimizes cart abandonment.',
                sections: ['Product Catalog', 'Smart Filters & Search', 'Product Cards with Quick-Add', 'Trust Signals at Checkout', 'Related Products'],
                strategic_notes: 'Optimize for mobile checkout — most competitor ecommerce sites lose customers here.'
            });
        }

        if (needsBooking) {
            defaultPages.push({
                key: 'book',
                title: 'Book Appointment',
                primary_goal: 'Convert visitors into booked consultations with zero friction.',
                sections: ['Booking Widget', 'Available Services', 'Calendar Integration', 'Instant Confirmation', 'Preparation Guide'],
                strategic_notes: 'Show real-time availability — competitors often hide this behind contact forms.'
            });
        }

        const normalizedCustomPages = rawCustomPages
            .map(page => String(page || '').trim())
            .filter(Boolean)
            .map(page => ({
                key: page.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''),
                title: this.toSentenceCase(page.replace(/[-_]+/g, ' ')),
                primary_goal: pageDescriptions[page] || pageDescriptions[page.toLowerCase()] || 'Support business goals with targeted content and conversion intent.',
                sections: ['Purpose & Value', 'Content Blocks', 'Proof Elements', 'CTA'],
                strategic_notes: ''
            }));

        const describedOnlyPages = Object.keys(pageDescriptions)
            .filter(key => !normalizedCustomPages.some(page => page.key === key.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')))
            .map(key => ({
                key: key.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''),
                title: this.toSentenceCase(key.replace(/[-_]+/g, ' ')),
                primary_goal: pageDescriptions[key] || 'Support business goals with targeted content and conversion intent.',
                sections: ['Purpose & Value', 'Content Blocks', 'Proof Elements', 'CTA'],
                strategic_notes: ''
            }));

        const pageMap = new Map();
        [...defaultPages, ...recommendedPages.map(rp => ({
            key: rp.key,
            title: rp.title,
            primary_goal: rp.goal,
            sections: ['Purpose & Value', 'Content Blocks', 'Proof Elements', 'CTA'],
            strategic_notes: 'Added based on approved Phase 4 recommendations.'
        })), ...normalizedCustomPages, ...describedOnlyPages].forEach(page => {
            if (!page || !page.key) return;
            if (!pageMap.has(page.key)) {
                pageMap.set(page.key, page);
                return;
            }
            const existing = pageMap.get(page.key);
            if ((!existing.primary_goal || existing.primary_goal.length < 8) && page.primary_goal) {
                existing.primary_goal = page.primary_goal;
            }
            if ((!existing.sections || !existing.sections.length) && Array.isArray(page.sections)) {
                existing.sections = page.sections;
            }
            pageMap.set(page.key, existing);
        });

        const pages = Array.from(pageMap.values());

        return {
            generated_at: new Date().toISOString(),
            business_name: businessName,
            industry: industryName,
            audience: targetAudience || info.targetAudience || info.target_audience || '',
            tone: info.preferredStyle || info.websitePreferences || 'Professional, clear, and conversion-focused',
            offerings,
            pages,
            // ✅ NEW: Strategic context for the architecture summary
            strategic_context: {
                market_position: marketPosition,
                top_competitors: topCompetitors.slice(0, 5).map(c => ({
                    name: c?.name || 'Unknown',
                    weakness: c?.weakness || null,
                    how_to_beat: c?.how_to_beat || null
                })),
                indirect_competitors: indirectCompetitors.slice(0, 3).map(c => ({
                    name: c?.name || 'Unknown',
                    vulnerability: c?.vulnerability || null
                })),
                approved_recommendations_count: approvedRecs.length,
                leads_collected: this.consultation_data?.deep_research_results?.leads_generated_count || 0,
                competitor_weaknesses: competitorWeaknesses
            }
        };
    }

    buildPhase5ArchitectureSummary(plan = {}) {
        const businessName = plan.business_name || 'Your Business';
        const ctx = plan.strategic_context || {};
        const mp = ctx.market_position || {};

        // ✅ Strategic executive summary
        const execSummary = [];
        if (mp.current_status) execSummary.push(`**Current Position:** ${mp.current_status}`);
        if (mp.short_term_6mo) execSummary.push(`**6-Month Target:** ${mp.short_term_6mo}`);
        const execBlock = execSummary.length > 0
            ? `\n### Strategic Context\n${execSummary.join('\n')}\n`
            : '';

        // ✅ Competitive advantage section
        const weaknesses = ctx.competitor_weaknesses || [];
        const competitors = ctx.top_competitors || [];
        let compAdvantage = '';
        if (weaknesses.length > 0 || competitors.length > 0) {
            const compLines = [];
            competitors.filter(c => c.weakness).slice(0, 3).forEach(c => {
                compLines.push(`- **${c.name}** weakness: ${c.weakness}${c.how_to_beat ? ` → *Our strategy: ${c.how_to_beat}*` : ''}`);
            });
            if (compLines.length > 0) {
                compAdvantage = `\n### Competitive Advantages Built Into This Architecture\n${compLines.join('\n')}\n`;
            }
        }

        // ✅ Strategic page structure with notes
        const pageLines = (Array.isArray(plan.pages) ? plan.pages : []).map((page, idx) => {
            const sections = Array.isArray(page.sections) ? page.sections.slice(0, 6).join(', ') : 'Core sections tailored to this page';
            let line = `${idx + 1}. **${page.title || 'Page'}**\n   - **Goal:** ${page.primary_goal || 'Support conversion and clarity.'}\n   - **Sections:** ${sections}`;
            if (page.strategic_notes) {
                line += `\n   - **Strategy:** _${page.strategic_notes}_`;
            }
            return line;
        }).join('\n\n');

        const offeringsLine = Array.isArray(plan.offerings) && plan.offerings.length
            ? plan.offerings.join(', ')
            : 'Not explicitly listed yet — architecture is still optimized for conversion.';

        // ✅ Build the full strategic document
        const sections = [
            `## Website Architecture Blueprint`,
            `### ${businessName}`,
            '---',
            `**Industry:** ${plan.industry || 'Identified during research'}`,
            `**Target Audience:** ${plan.audience || 'Defined from research findings'}`,
            `**Tone & UX Direction:** ${plan.tone || 'Professional and conversion-focused'}`,
            `**Core Offerings:** ${offeringsLine}`,
            `**Total Pages:** ${(plan.pages || []).length}`,
            ctx.approved_recommendations_count > 0 ? `**Approved Recommendations Applied:** ${ctx.approved_recommendations_count}` : '',
            ctx.leads_collected > 0 ? `**Leads Collected:** ${ctx.leads_collected} qualified leads saved to CRM` : '',
            execBlock,
            compAdvantage,
            '### Proposed Page Structure',
            pageLines || '- Core pages will be finalized from your approved recommendations.',
            '',
            '---',
            '> Every page is designed to convert visitors into customers based on your specific market research, competitor analysis, and approved recommendations. Each section is refineable after launch through conversation with me.',
        ].filter(Boolean);

        return sections.join('\n');
    }

    async runPhase5ArchitectureAndAdvance() {
        if (this._phase5ArchitectureInProgress) {
            return;
        }
        this._phase5ArchitectureInProgress = true;

        try {
            // Step 3: Building website plan
            this.updatePhase5Progress(3, 5, '📐 Building strategic page plan from approved recommendations...');

            const plan = this.derivePhase5WebsitePlan();
            this.state.website_plan = plan;
            this.consultation_data.website_plan = plan;
            this.syncLegacyConsultationDataFromState();

            // Step 4: Generating architecture summary
            this.updatePhase5Progress(4, 5, '📝 Generating architecture blueprint document...');
            await this.delay(400);

            const architectureMessage = this.buildPhase5ArchitectureSummary(plan);
            this.addMessageToUI('ai', architectureMessage);
            await this.saveMessageToDatabase('ai', architectureMessage);

            if (window.vmo_consultant_context_v1) {
                await this.saveConsultationData({
                    website_plan: plan,
                    consultation_flow: {
                        current_index: this.consultation_data?.consultation_flow?.current_index || 4,
                        current_phase_index: this.consultation_data?.consultation_flow?.current_index || 4
                    }
                });
            }

            // Step 5: Finalizing
            this.updatePhase5Progress(5, 5, '✅ Architecture finalized — preparing systems setup...');
            await this.delay(800);

            try { await this.updatePhaseFlag('phase5_completed', true); } catch (_) {}

            const currentIdx = this.consultation_data?.consultation_flow?.current_index ?? this.state?.consultation_flow?.current_index ?? 0;
            if (currentIdx === 4) {
                await this.advancePhase();
            }
        } catch (error) {
            console.error('Phase 5 architecture flow failed:', error);
            this.addMessageToUI('system', '⚠️ Website architecture planning hit an issue, but I’ll continue to systems setup so your workflow does not stall.');
            const currentIdx = this.consultation_data?.consultation_flow?.current_index ?? this.state?.consultation_flow?.current_index ?? 0;
            if (currentIdx === 4) {
                await this.advancePhase();
            }
        } finally {
            this._phase5ArchitectureInProgress = false;
        }
    }

    // Phase 6: set up store catalog (products + buy URLs) from consultation data
    // ✅ ENHANCED: Now includes conversational variant/tax collection before catalog creation
    async setupPhase6StoreAndCatalog() {
        const AX = this.getAjax();
        const info = this.consultation_data?.extracted_information || {};

        // Only run if e-commerce intent is present
        const needsEcommerce = !!(info.ecommerce_needed || info.needsEcommerce || info.needs?.ecommerce || info.needs?.payments);
        if (!needsEcommerce) {
            this.addMessageToUI('ai', '✅ No payments/products detected — skipping store catalog setup.');
            return { success: true, ecommerce: false };
        }

        // Progress is now shown by the Phase 6 progress indicator (no static message needed)

        const resp = await fetch(AX.url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action: 'vmo_phase6_setup_store_and_catalog',
                security: AX.nonce,
                nonce: AX.nonce,
                _ajax_nonce: AX.nonce,
                session_id: this.sessionId,
            })
        });

        const data = await resp.json();
        if (!data?.success) {
            const msg = data?.data?.message || data?.message || 'Store setup failed.';
            this.addMessageToUI('system', `⚠️ Store setup encountered an issue. Don't worry — we'll continue with the rest of your business setup and you can add products later from your dashboard.`);
            return { success: false, error: msg };
        }

        const payload = data.data || {};
        const products = Array.isArray(payload.products) ? payload.products : [];
        if (products.length) {
            // ✅ CLEAN DISPLAY: No raw HTML, no code, no technical jargon
            let productSummary = `✅ Great news! I've set up your online store with ${products.length} product${products.length > 1 ? 's' : ''}:\n\n`;
            products.slice(0, 10).forEach((p, i) => {
                const name = p.name || 'Product';
                const price = p.price_cents ? `$${(p.price_cents / 100).toFixed(2)}` : '';
                productSummary += `${i + 1}. **${name}**${price ? ` — ${price}` : ''}\n`;
            });
            if (products.length > 10) {
                productSummary += `...and ${products.length - 10} more.\n`;
            }
            productSummary += `\nEach product now has its own checkout page ready for your customers. You can manage your products, add images, update prices, and create variations from your Business Dashboard at any time.`;

            // ✅ Check if any products might need variants and prompt the user
            const potentialVariantProducts = products.filter(p => {
                const name = (p.name || '').toLowerCase();
                return /shirt|shoe|cloth|apparel|dress|jacket|hat|bag|phone case|poster|print|mug|candle/i.test(name);
            });

            if (potentialVariantProducts.length > 0) {
                productSummary += `\n\n💡 I noticed some of your products might come in different **sizes, colors, or styles** (like "${potentialVariantProducts[0].name}"). Would you like me to help you set up variations for any of your products? For example:\n\n`;
                productSummary += `• Different **colors** (each showing a visual preview when selected)\n`;
                productSummary += `• Different **sizes** (S, M, L, XL, etc.)\n`;
                productSummary += `• **Add-ons** (like custom logos, engraving, gift wrapping)\n`;
                productSummary += `• **Custom pricing** per variation\n\n`;
                productSummary += `Just tell me which products have variations and I'll set everything up. Or you can say **"skip"** and add them later from your dashboard.`;

                // ✅ Flag to track that we're waiting for variant input
                this._awaitingVariantResponse = true;
                this._variantProducts = products;
            }

            // ✅ Tax handling question
            if (!this.consultation_data?.extracted_information?.tax_handling) {
                productSummary += `\n\n🧾 One more thing about pricing — would you like your displayed prices to **include tax** (what customers see is what they pay), or should **tax be added separately** at checkout? Just let me know your preference.`;
                this._awaitingTaxResponse = true;
            }

            this.addMessageToUI('ai', productSummary);
        } else {
            this.addMessageToUI('ai', '✅ Your online store framework is ready! You can add products anytime from your Business Dashboard — or just tell me about your products right here and I\'ll set them up for you.');
        }

        return { success: true, ecommerce: !!payload.ecommerce, store_url: payload.store_url || '' };
    }
    
    // ✅ NEW: Trigger full business generation (website, legal docs, email, roadmap)
    // Uses CHUNKED approach like deep research to prevent timeouts (12 chunks, ~30-60 min)
    async triggerFullBusinessGeneration() {
        console.log('🏗️ triggerFullBusinessGeneration() - Starting CHUNKED generation...');
        
        // ✅ Flag to prevent watchdog interruption during generation
        this._businessGenerationInProgress = true;
        if (this._processingWatchdog) clearTimeout(this._processingWatchdog);
        
        // Show enhanced progress indicator for generation
        const progressHtml = this.createGenerationProgressIndicator();
        this.addNodeMessageToUI('system', progressHtml);
        
        // Give the DOM time to render the progress indicator
        await new Promise(resolve => setTimeout(resolve, 100));
        
        try {
            const AX = this.getAjax();
            this.addMessageToUI('system', '🎨 Starting COMPREHENSIVE business generation (12 phases for reliability, ~30-60 minutes for complete business package)...');
            
            // ✅ CHUNKED GENERATION: 12 chunks (like deep research) to prevent nginx 504 timeouts
            let isComplete = false;
            let currentChunk = 0;
            let finalResults = null;
            const totalChunks = 12;
            
            // Generation chunk labels
            const chunkLabels = [
                '📋 Processing consultation data...',
                '🖼️ Processing media assets...',
                '📊 Generating business strategy...',
                '💳 Setting up e-commerce & payments...',
                '🏗️ Designing website architecture...',
                '🏠 Creating core website pages...',
                '👤 Creating additional pages...',
                '🛍️ Creating services and sales pages...',
                '📞 Creating contact and conversion pages...',
                '🧩 Completing remaining pages...',
                '⚖️ Generating legal docs & integrating AI...',
                '🚀 Publishing website & sending notifications...'
            ];
            
            while (!isComplete && currentChunk < totalChunks) {
                // Update progress indicator for current chunk
                const progressPercent = Math.round(((currentChunk + 1) / totalChunks) * 100);
                this.updateGenerationProgress(currentChunk + 1, totalChunks, chunkLabels[currentChunk] || 'Processing...');
                this.updateMainProgressBar(60 + Math.round((currentChunk / totalChunks) * 35), chunkLabels[currentChunk]);
                console.log(`📦 Running generation chunk ${currentChunk + 1}/${totalChunks} (${progressPercent}%)...`);
                
                // ✅ BULLETPROOF: Maximum 5 retries per chunk with exponential backoff
                const MAX_RETRIES = 5;
                let retryCount = 0;
                let chunkSuccess = false;
                
                while (!chunkSuccess && retryCount < MAX_RETRIES) {
                    try {
                        // ✅ BOTTLENECK FIX v9.1: AbortController prevents infinite hangs.
                        // Each chunk can generate pages + images (up to 5 images × 90s = 7.5min per page, 3 pages/chunk).
                        // 600s (10 min) timeout per chunk gives generous headroom; browser default (~300s) was too tight.
                        const chunkController = new AbortController();
                        const chunkTimeout = setTimeout(() => chunkController.abort(), 600000); // 10 minutes per chunk

                        const response = await fetch(AX.url, {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                            signal: chunkController.signal,
                            body: new URLSearchParams({
                                action: 'vmo_trigger_business_generation',
                                security: AX.nonce,
                                nonce: AX.nonce,
                                _ajax_nonce: AX.nonce,
                                session_id: this.sessionId,
                                chunk: currentChunk + 1 // 1-based chunk number
                            })
                        });

                        clearTimeout(chunkTimeout); // ✅ Clear timeout on successful response

                        if (!response.ok) {
                            const errorBody = await response.text();
                            throw new Error(`HTTP ${response.status}: ${errorBody || response.statusText}`);
                        }
                        
                        let data;
                        try {
                            data = await response.json();
                        } catch (parseError) {
                            throw new Error('Invalid JSON received from generation endpoint');
                        }
                        
                        if (data?.success) {
                            console.log(`✅ Generation chunk ${currentChunk + 1}/${totalChunks} completed. Progress: ${data.data.progress_percent}%`);
                            isComplete = data.data.is_complete;
                            finalResults = data.data;
                            currentChunk++;
                            chunkSuccess = true;
                            retryCount = 0;
                            
                            // Update UI with progress
                            if (!isComplete) {
                                // Delay between chunks to prevent rate limiting
                                await new Promise(resolve => setTimeout(resolve, 500));
                            }
                        } else {
                            throw new Error(data?.data?.message || 'Generation chunk failed');
                        }
                    } catch (chunkError) {
                        retryCount++;
                        
                        // ✅ If max retries reached, skip to next chunk
                        if (retryCount >= MAX_RETRIES) {
                            console.error(`❌ Chunk ${currentChunk + 1} failed after ${MAX_RETRIES} attempts`);
                            
                            // ✅ FIX: Show user-visible warning when a chunk fails
                            this.addMessageToUI('system', `⚠️ Generation step ${currentChunk + 1}/${totalChunks} (${chunkLabels[currentChunk] || 'Processing'}) encountered issues after ${MAX_RETRIES} attempts. Continuing with remaining steps...`);
                            
                            // For the final chunk, mark as complete
                            if (currentChunk + 1 === totalChunks) {
                                console.log('⚠️ Final generation chunk failed but proceeding with available data...');
                                isComplete = true;
                                chunkSuccess = true;
                                if (!finalResults) {
                                    finalResults = {
                                        is_complete: true,
                                        website_url: '',
                                        total_time: 0,
                                        progress_percent: 100
                                    };
                                }
                            } else {
                                console.log(`⚠️ Skipping chunk ${currentChunk + 1}, continuing...`);
                                currentChunk++;
                                chunkSuccess = true;
                            }
                            continue;
                        }
                        
                        // Exponential backoff: 3s, 6s, 12s, 24s, 48s
                        const backoffMs = Math.min(3000 * Math.pow(2, Math.min(retryCount - 1, 4)), 48000);
                        console.warn(`⚠️ Chunk ${currentChunk + 1} attempt ${retryCount}/${MAX_RETRIES} failed. Retrying in ${backoffMs/1000}s...`, chunkError.message);
                        this.updateGenerationProgress(currentChunk + 1, totalChunks, `Working on generation... (attempt ${retryCount + 1}/${MAX_RETRIES})`);
                        await new Promise(resolve => setTimeout(resolve, backoffMs));
                    }
                }
            }
            
            if (isComplete && finalResults) {
                console.log('✅ All generation chunks completed successfully!');
                
                // Extract results - website is now saved as DRAFT for refinement
                let homepageHtml = finalResults.homepage_html || '';
                const previewUrl = finalResults.preview_url || '';
                const postId = finalResults.post_id || null;
                const businessName = finalResults.business_name || 'Your Business';
                const projectTitle = finalResults.project_title || `${businessName} - Website`;
                const builderState = finalResults.builder_state && typeof finalResults.builder_state === 'object'
                    ? finalResults.builder_state
                    : null;
                const pagesMap = finalResults.pages_map && typeof finalResults.pages_map === 'object'
                    ? finalResults.pages_map
                    : null;
                const totalTime = finalResults.total_time ? Math.round(finalResults.total_time / 60) : 0;

                // ✅ GrapesJS: Validate homepage HTML through adapter if available
                if (homepageHtml && window.vmoGrapesAdapter && typeof window.vmoGrapesAdapter.isReady === 'function' && window.vmoGrapesAdapter.isReady()) {
                    try {
                        window.vmoGrapesAdapter.runPipeline([{ id: 'homepage', html: homepageHtml }]);
                        console.log('[VMO] GrapesJS validation pass completed for homepage');
                    } catch (e) { console.warn('[VMO] GrapesJS validation skipped:', e); }
                }
                
                // Store draft info for later publishing
                this._draftWebsite = {
                    postId: postId,
                    previewUrl: previewUrl,
                    homepageHtml: homepageHtml,
                    businessName: businessName,
                    projectTitle: projectTitle,
                    builderState: builderState,
                    pagesMap: pagesMap
                };

                if (!this.state.approvals) this.state.approvals = {};
                if (!this.consultation_data.approvals) this.consultation_data.approvals = {};
                this.state.approvals.artifacts_generated = true;
                this.consultation_data.approvals.artifacts_generated = true;

                if (window.vmo_consultant_context_v1) {
                    await this.saveConsultationData({
                        approvals: this.consultation_data.approvals
                    });
                }

                // Deterministic handoff: once Phase 7 generation succeeds, always advance to Phase 8.
                const currentIdx = this.consultation_data?.consultation_flow?.current_index ?? this.state?.consultation_flow?.current_index ?? 0;
                if (currentIdx === 6) {
                    try { await this.updatePhaseFlag('phase7_completed', true); } catch (_) {}
                    await this.delay(400);
                    await this.advancePhase();
                }

                this.updateMainProgressBar(88, 'Preparing the preview and generating required media...');

                // ✅ Load the generated website into the preview iframe and wait for required media completion.
                const mediaResult = await this.loadWebsiteIntoPreview(previewUrl, homepageHtml, postId);
                const websiteReady = !mediaResult || mediaResult.complete !== false;

                // Hide progress indicator
                this.hideGenerationProgress();
                this.updateMainProgressBar(
                    websiteReady ? 100 : 96,
                    websiteReady ? 'Ready for preview!' : 'Media still incomplete'
                );

                this.addMessageToUI('ai', websiteReady
                    ? `✅ **Your website is ready for preview!**
                
🎨 **${businessName}** has been generated and is now showing in the **Live Preview** on the left.
⏱️ **Generation time:** ${totalTime} minutes

**What you can do now:**
• 📝 **Refine** any section by clicking on it in the preview
• 🖼️ **Replace media** with your own images and videos
• 🎨 **Adjust colors, fonts, and layout** as needed
• ✅ When satisfied, click **"Publish"** in the toolbar to go live!

Your website is saved as a draft and won't be public until you publish it.`
                    : `⌛ **${businessName}** is still finishing required media.

The layout is generated, but the website is not ready yet because one or more required media assets are still incomplete. VMO will keep the preview in progress until those assets are resolved.`);

                if (websiteReady) {
                    // ✅ Send browser notification when generation completes
                    await this.sendGenerationCompleteNotification();
                }
                
                // ✅ Refresh the dashboard KPIs/stats
                this.refreshDashboardAfterGeneration();

                if (websiteReady) {
                    // ✅ Close the wizard AFTER a delay so user sees the message
                    // Keep preview visible for refinement (publish is a manual action).
                    setTimeout(() => {
                        this.showRefinementMode(postId, businessName);
                    }, 2000);
                }
            } else {
                throw new Error('Generation did not complete after all chunks');
            }
            
        } catch (error) {
            console.error('❌ Error in full business generation:', error);
            this.hideGenerationProgress();
            this.addMessageToUI('system', `⚠️ There was an issue during generation: ${error.message}.`);
            throw error;
        } finally {
            // ✅ Always clear the generation flag
            this._businessGenerationInProgress = false;
        }
    }
    
    // ✅ Phase 5: Architecture progress indicator
    createPhase5ProgressIndicator() {
        this._phase5ProgressStart = Date.now();
        return `
            <div id="vmo-phase5-progress" style="background: linear-gradient(135deg, #1a1f2e 0%, #2d3548 100%); border-radius: 12px; padding: 20px; margin: 15px 0; border: 1px solid rgba(14, 165, 233, 0.3);">
                <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 15px;">
                    <div style="width: 24px; height: 24px; border: 3px solid rgba(14, 165, 233, 0.3); border-top-color: #0ea5e9; border-radius: 50%; animation: vmo-spin 1s linear infinite;"></div>
                    <span style="color: #7dd3fc; font-size: 14px; font-weight: 600;">🏗️ Phase 5: Designing Website Architecture</span>
                </div>
                <div style="background: rgba(0,0,0,0.3); border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 10px;">
                    <div id="vmo-phase5-bar" style="height: 100%; background: linear-gradient(90deg, #0ea5e9, #38bdf8); width: 0%; transition: width 0.5s ease;"></div>
                </div>
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <span id="vmo-phase5-label" style="color: #94a3b8; font-size: 13px;">Initializing...</span>
                    <span id="vmo-phase5-percent" style="color: #0ea5e9; font-weight: 600;">0%</span>
                </div>
                <div style="margin-top: 12px; padding: 10px; background: rgba(14, 165, 233, 0.08); border-radius: 8px; border: 1px solid rgba(14, 165, 233, 0.15);">
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                        <span style="color: #7dd3fc; font-size: 12px;">⏱️ Elapsed: <span id="vmo-phase5-elapsed">0:00</span></span>
                        <span style="color: #64748b; font-size: 11px;">~1-2 minutes</span>
                    </div>
                </div>
            </div>
        `;
    }

    updatePhase5Progress(current, total, label) {
        const bar = document.getElementById('vmo-phase5-bar');
        const labelEl = document.getElementById('vmo-phase5-label');
        const pctEl = document.getElementById('vmo-phase5-percent');
        const elapsedEl = document.getElementById('vmo-phase5-elapsed');
        const pct = Math.round((current / total) * 100);
        if (bar) bar.style.width = pct + '%';
        if (labelEl) labelEl.textContent = label;
        if (pctEl) pctEl.textContent = pct + '%';
        if (elapsedEl && this._phase5ProgressStart) {
            const ms = Date.now() - this._phase5ProgressStart;
            const m = Math.floor(ms / 60000);
            const s = Math.floor((ms % 60000) / 1000);
            elapsedEl.textContent = `${m}:${s.toString().padStart(2, '0')}`;
        }
        this.updateMainProgressBar(55 + Math.round((current / total) * 15), label);
    }

    hidePhase5Progress() {
        const el = document.getElementById('vmo-phase5-progress');
        if (el) {
            let elapsed = '';
            if (this._phase5ProgressStart) {
                const ms = Date.now() - this._phase5ProgressStart;
                const m = Math.floor(ms / 60000);
                const s = Math.floor((ms % 60000) / 1000);
                elapsed = ` in ${m}:${s.toString().padStart(2, '0')}`;
            }
            el.innerHTML = `
                <div style="display: flex; align-items: center; gap: 12px; padding: 15px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; border: 1px solid rgba(34, 197, 94, 0.3);">
                    <span style="font-size: 24px;">✅</span>
                    <div>
                        <span style="color: #22c55e; font-weight: 500;">Website architecture designed successfully!</span>
                        <span style="color: #86efac; font-size: 12px; margin-left: 8px;">Completed${elapsed}</span>
                    </div>
                </div>`;
        }
    }

    // ✅ Phase 6: Systems Setup progress indicator
    createPhase6ProgressIndicator() {
        this._phase6ProgressStart = Date.now();
        return `
            <div id="vmo-phase6-progress" style="background: linear-gradient(135deg, #1a1f2e 0%, #2d3548 100%); border-radius: 12px; padding: 20px; margin: 15px 0; border: 1px solid rgba(168, 85, 247, 0.3);">
                <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 15px;">
                    <div style="width: 24px; height: 24px; border: 3px solid rgba(168, 85, 247, 0.3); border-top-color: #a855f7; border-radius: 50%; animation: vmo-spin 1s linear infinite;"></div>
                    <span style="color: #d8b4fe; font-size: 14px; font-weight: 600;">⚙️ Phase 6: Setting Up Business Systems</span>
                </div>
                <div style="background: rgba(0,0,0,0.3); border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 10px;">
                    <div id="vmo-phase6-bar" style="height: 100%; background: linear-gradient(90deg, #a855f7, #c084fc); width: 0%; transition: width 0.5s ease;"></div>
                </div>
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <span id="vmo-phase6-label" style="color: #94a3b8; font-size: 13px;">Initializing systems...</span>
                    <span id="vmo-phase6-percent" style="color: #a855f7; font-weight: 600;">0%</span>
                </div>
                <div id="vmo-phase6-steps" style="margin-top: 15px; display: flex; flex-direction: column; gap: 6px;">
                    <div class="vmo-p6-step" data-step="1" style="display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; background: rgba(168, 85, 247, 0.05); border: 1px solid rgba(168, 85, 247, 0.1);">
                        <span style="font-size: 14px;">🛒</span>
                        <span style="color: #94a3b8; font-size: 12px;">E-commerce & Store Setup</span>
                        <span class="vmo-p6-status" style="margin-left: auto; font-size: 11px; color: #64748b;">Pending</span>
                    </div>
                    <div class="vmo-p6-step" data-step="2" style="display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; background: rgba(168, 85, 247, 0.05); border: 1px solid rgba(168, 85, 247, 0.1);">
                        <span style="font-size: 14px;">📇</span>
                        <span style="color: #94a3b8; font-size: 12px;">CRM & Contact Management</span>
                        <span class="vmo-p6-status" style="margin-left: auto; font-size: 11px; color: #64748b;">Pending</span>
                    </div>
                    <div class="vmo-p6-step" data-step="3" style="display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; background: rgba(168, 85, 247, 0.05); border: 1px solid rgba(168, 85, 247, 0.1);">
                        <span style="font-size: 14px;">⚖️</span>
                        <span style="color: #94a3b8; font-size: 12px;">Compliance & Legal Framework</span>
                        <span class="vmo-p6-status" style="margin-left: auto; font-size: 11px; color: #64748b;">Pending</span>
                    </div>
                    <div class="vmo-p6-step" data-step="4" style="display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; background: rgba(168, 85, 247, 0.05); border: 1px solid rgba(168, 85, 247, 0.1);">
                        <span style="font-size: 14px;">📞</span>
                        <span style="color: #94a3b8; font-size: 12px;">Phone & Communications</span>
                        <span class="vmo-p6-status" style="margin-left: auto; font-size: 11px; color: #64748b;">Pending</span>
                    </div>
                    <div class="vmo-p6-step" data-step="5" style="display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 6px; background: rgba(168, 85, 247, 0.05); border: 1px solid rgba(168, 85, 247, 0.1);">
                        <span style="font-size: 14px;">📧</span>
                        <span style="color: #94a3b8; font-size: 12px;">Email System Configuration</span>
                        <span class="vmo-p6-status" style="margin-left: auto; font-size: 11px; color: #64748b;">Pending</span>
                    </div>
                </div>
                <div style="margin-top: 12px; padding: 10px; background: rgba(168, 85, 247, 0.08); border-radius: 8px; border: 1px solid rgba(168, 85, 247, 0.15);">
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                        <span style="color: #d8b4fe; font-size: 12px;">⏱️ Elapsed: <span id="vmo-phase6-elapsed">0:00</span></span>
                        <span style="color: #64748b; font-size: 11px;">~1-3 minutes</span>
                    </div>
                </div>
            </div>
        `;
    }

    updatePhase6Progress(current, total, label) {
        const bar = document.getElementById('vmo-phase6-bar');
        const labelEl = document.getElementById('vmo-phase6-label');
        const pctEl = document.getElementById('vmo-phase6-percent');
        const elapsedEl = document.getElementById('vmo-phase6-elapsed');
        const pct = Math.round((current / total) * 100);
        if (bar) bar.style.width = pct + '%';
        if (labelEl) labelEl.textContent = label;
        if (pctEl) pctEl.textContent = pct + '%';
        if (elapsedEl && this._phase6ProgressStart) {
            const ms = Date.now() - this._phase6ProgressStart;
            const m = Math.floor(ms / 60000);
            const s = Math.floor((ms % 60000) / 1000);
            elapsedEl.textContent = `${m}:${s.toString().padStart(2, '0')}`;
        }
        // Update individual step statuses
        const steps = document.querySelectorAll('#vmo-phase6-steps .vmo-p6-step');
        steps.forEach(step => {
            const stepNum = parseInt(step.getAttribute('data-step'));
            const statusEl = step.querySelector('.vmo-p6-status');
            if (stepNum < current) {
                if (statusEl) { statusEl.textContent = '✅ Done'; statusEl.style.color = '#22c55e'; }
                step.style.background = 'rgba(34, 197, 94, 0.08)';
                step.style.borderColor = 'rgba(34, 197, 94, 0.2)';
            } else if (stepNum === current) {
                if (statusEl) { statusEl.textContent = '⏳ In progress...'; statusEl.style.color = '#a855f7'; }
                step.style.background = 'rgba(168, 85, 247, 0.12)';
                step.style.borderColor = 'rgba(168, 85, 247, 0.3)';
            }
        });
        this.updateMainProgressBar(70 + Math.round((current / total) * 12), label);
    }

    hidePhase6Progress() {
        const el = document.getElementById('vmo-phase6-progress');
        if (el) {
            // Mark all steps as done
            const steps = el.querySelectorAll('.vmo-p6-step');
            steps.forEach(step => {
                const statusEl = step.querySelector('.vmo-p6-status');
                if (statusEl) { statusEl.textContent = '✅ Done'; statusEl.style.color = '#22c55e'; }
                step.style.background = 'rgba(34, 197, 94, 0.08)';
                step.style.borderColor = 'rgba(34, 197, 94, 0.2)';
            });
            // Update bar to 100%
            const bar = document.getElementById('vmo-phase6-bar');
            const pctEl = document.getElementById('vmo-phase6-percent');
            if (bar) bar.style.width = '100%';
            if (pctEl) pctEl.textContent = '100%';
            const labelEl = document.getElementById('vmo-phase6-label');
            if (labelEl) labelEl.textContent = '✅ All systems configured — launching email setup wizard...';
        }
    }

    // ✅ NEW: Create progress indicator HTML for generation (like research)
    createGenerationProgressIndicator() {
        // Store start time for dynamic estimates
        this._generationStartTime = Date.now();
        
        return `
            <div id="vmo-generation-progress" class="vmo-research-progress" style="background: linear-gradient(135deg, #1a1f2e 0%, #2d3548 100%); border-radius: 12px; padding: 20px; margin: 15px 0; border: 1px solid rgba(99, 102, 241, 0.3);">
                <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 15px;">
                    <div class="vmo-generation-spinner" style="width: 24px; height: 24px; border: 3px solid rgba(99, 102, 241, 0.3); border-top-color: #6366f1; border-radius: 50%; animation: vmo-spin 1s linear infinite;"></div>
                    <span style="color: #a5b4fc; font-size: 14px; font-weight: 500;">Building Your Business Package</span>
                </div>
                <div style="background: rgba(0,0,0,0.3); border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 10px;">
                    <div id="vmo-generation-bar" style="height: 100%; background: linear-gradient(90deg, #6366f1, #8b5cf6); width: 0%; transition: width 0.5s ease;"></div>
                </div>
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <span id="vmo-generation-label" style="color: #94a3b8; font-size: 13px;">Initializing...</span>
                    <span id="vmo-generation-percent" style="color: #6366f1; font-weight: 600;">0%</span>
                </div>
                <div id="vmo-generation-time-info" style="margin-top: 15px; padding: 12px; background: rgba(99, 102, 241, 0.1); border-radius: 8px; border: 1px solid rgba(99, 102, 241, 0.2);">
                    <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
                        <span style="color: #a5b4fc; font-size: 12px;">⏱️ <strong>Estimated time:</strong> <span id="vmo-generation-eta">30-60 minutes</span></span>
                        <span style="color: #94a3b8; font-size: 12px;">Elapsed: <span id="vmo-generation-elapsed">0:00</span></span>
                    </div>
                    <div style="margin-top: 8px; color: #64748b; font-size: 11px;">
                        💡 You'll receive a browser notification when complete. Safe to leave this tab open.
                    </div>
                </div>
            </div>
            <style>
                @keyframes vmo-spin { to { transform: rotate(360deg); } }
            </style>
        `;
    }
    
    // ✅ NEW: Update generation progress indicator with dynamic time estimate
    updateGenerationProgress(current, total, label) {
        const progressBar = document.getElementById('vmo-generation-bar');
        const progressLabel = document.getElementById('vmo-generation-label');
        const progressPercent = document.getElementById('vmo-generation-percent');
        const etaEl = document.getElementById('vmo-generation-eta');
        const elapsedEl = document.getElementById('vmo-generation-elapsed');
        
        if (progressBar && progressLabel && progressPercent) {
            const percent = Math.round((current / total) * 100);
            progressBar.style.width = percent + '%';
            progressLabel.textContent = label;
            progressPercent.textContent = percent + '%';
        }
        
        // Calculate elapsed time
        if (elapsedEl && this._generationStartTime) {
            const elapsedMs = Date.now() - this._generationStartTime;
            const elapsedMin = Math.floor(elapsedMs / 60000);
            const elapsedSec = Math.floor((elapsedMs % 60000) / 1000);
            elapsedEl.textContent = `${elapsedMin}:${elapsedSec.toString().padStart(2, '0')}`;
        }
        
        // Calculate dynamic ETA based on actual chunk times
        if (etaEl && this._generationStartTime && current > 0) {
            const elapsedMs = Date.now() - this._generationStartTime;
            const msPerChunk = elapsedMs / current;
            const remainingChunks = total - current;
            const remainingMs = msPerChunk * remainingChunks;
            const remainingMin = Math.ceil(remainingMs / 60000);
            
            if (remainingMin <= 0) {
                etaEl.textContent = 'Almost done!';
            } else if (remainingMin === 1) {
                etaEl.textContent = '~1 minute remaining';
            } else if (remainingMin < 60) {
                etaEl.textContent = `~${remainingMin} minutes remaining`;
            } else {
                const hours = Math.floor(remainingMin / 60);
                const mins = remainingMin % 60;
                etaEl.textContent = `~${hours}h ${mins}m remaining`;
            }
        }
    }
    
    // ✅ NEW: Hide generation progress indicator
    hideGenerationProgress() {
        // Calculate final elapsed time
        let elapsedText = '';
        if (this._generationStartTime) {
            const elapsedMs = Date.now() - this._generationStartTime;
            const elapsedMin = Math.floor(elapsedMs / 60000);
            const elapsedSec = Math.floor((elapsedMs % 60000) / 1000);
            elapsedText = ` in ${elapsedMin}:${elapsedSec.toString().padStart(2, '0')}`;
        }
        
        const progressContainer = document.getElementById('vmo-generation-progress');
        if (progressContainer) {
            progressContainer.innerHTML = `
                <div style="display: flex; align-items: center; gap: 12px; padding: 15px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; border: 1px solid rgba(34, 197, 94, 0.3);">
                    <span style="font-size: 24px;">✅</span>
                    <div>
                        <span style="color: #22c55e; font-weight: 500;">Business package generated successfully!</span>
                        <span style="color: #86efac; font-size: 12px; margin-left: 8px;">Completed${elapsedText}</span>
                    </div>
                </div>
            `;
        }
    }
    
    // ✅ NEW: Send browser notification when generation completes
    async sendGenerationCompleteNotification() {
        if ('Notification' in window) {
            if (Notification.permission === 'default') {
                await Notification.requestPermission();
            }
            if (Notification.permission === 'granted') {
                const iconUrl = this.getPluginAssetUrl('assets/vmo-icon.svg');
                const notification = new Notification('🎉 Website Ready!', {
                    body: 'Your custom website has been generated. Click to view.',
                    icon: iconUrl,
                    requireInteraction: true
                });
                
                notification.onclick = () => {
                    window.focus();
                    notification.close();
                };
                
                // Also play a completion sound
                try {
                    const audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2teleQkAYL3KnYRQGBSR4eyaaTQDAKfm0JSNZDcaK5/17pFYJAAA');
                    audio.volume = 0.4;
                    audio.play().catch(() => {});
                } catch(_) {}
            }
        }
    }

    syncPreviewHtmlFromIframe() {
        try {
            const iframe = document.getElementById('vmo-preview-iframe') || 
                           document.getElementById('vmo-preview-frame') || 
                           document.getElementById('vmo-live-preview') ||
                           document.querySelector('.vmo-preview-iframe') ||
                           document.querySelector('iframe[name="vmo-preview"]');

            if (!iframe) {
                return window.lastContent || '';
            }

            const html = typeof window.vmoSerializePreviewDocument === 'function'
                ? window.vmoSerializePreviewDocument(iframe)
                : String(iframe.getAttribute('srcdoc') || iframe.srcdoc || '');

            if (html && html.trim()) {
                window.lastContent = html;
            }

            return html || window.lastContent || '';
        } catch (error) {
            console.warn('[VMO] Could not sync preview HTML from iframe', error);
            return window.lastContent || '';
        }
    }

    async resolvePreviewMedia(source = 'consultation_generation') {
        if (typeof window.vmoMediaGenerator === 'undefined' || typeof window.vmoMediaGenerator.resolvePendingMedia !== 'function') {
            return { complete: true, total: 0, completed: 0, failed: [] };
        }

        try {
            return await window.vmoMediaGenerator.resolvePendingMedia({
                source,
                requireComplete: true,
                onProgress: (progress) => {
                    if (typeof this.updateMainProgressBar !== 'function') {
                        return;
                    }

                    const ratio = progress.total
                        ? ` ${progress.completed}/${progress.total}`
                        : '';
                    const nextPercent = progress.total
                        ? Math.min(99, 88 + Math.round((Math.max(progress.completed, 0) / Math.max(progress.total, 1)) * 10))
                        : 92;

                    this.updateMainProgressBar(nextPercent, `Generating required media${ratio}: ${progress.message || 'working…'}`);
                }
            });
        } catch (error) {
            console.error('[VMO] Consultation preview media completion failed:', error);
            return {
                complete: false,
                total: 0,
                completed: 0,
                failed: [{ message: error?.message || 'Required media generation failed.' }]
            };
        }
    }

    async queuePreviewMedia(postId, source = 'consultation_generation') {
        if (!postId || typeof window.vmoMediaGenerator === 'undefined' || typeof window.vmoMediaGenerator.queueProjectMedia !== 'function') {
            return this.resolvePreviewMedia(source);
        }

        const targets = typeof window.vmoMediaGenerator.collectPendingMediaTargets === 'function'
            ? window.vmoMediaGenerator.collectPendingMediaTargets()
            : [];

        if (!targets.length) {
            return { complete: true, total: 0, completed: 0, failed: [] };
        }

        return window.vmoMediaGenerator.queueProjectMedia({
            source,
            targets,
            projectId: postId,
            title: window.currentProjectTitle || 'Consultation Website',
            state: window.vmoState,
            html: window.lastContent,
            waitForCompletion: true,
            onUpdate: (queue) => {
                if (typeof this.updateMainProgressBar !== 'function') {
                    return;
                }

                const counts = queue && queue.counts ? queue.counts : {};
                const total = counts.total || 0;
                const completed = (counts.complete || 0) + (counts.failed || 0);
                const nextPercent = total
                    ? Math.min(99, 90 + Math.round((completed / Math.max(total, 1)) * 8))
                    : 94;
                this.updateMainProgressBar(nextPercent, total
                    ? `Generating required media ${completed}/${total}...`
                    : 'Generating required media...');
            }
        });
    }

    // ✅ FIX: Load generated website into the preview iframe
    async loadWebsiteIntoPreview(websiteUrl, homepageHtml = '', postId = null) {
        console.log('📺 Loading generated website into preview:', websiteUrl || '(using HTML)');
        let mediaResult = { complete: true, total: 0, completed: 0, failed: [] };
        
        try {
            // Try multiple iframe selectors
            const iframe = document.getElementById('vmo-preview-iframe') || 
                           document.getElementById('vmo-preview-frame') || 
                           document.getElementById('vmo-live-preview') ||
                           document.querySelector('.vmo-preview-iframe') ||
                           document.querySelector('iframe[name="vmo-preview"]');
            
            if (iframe) {
                // Prefer HTML when available so required media can be resolved before the site is treated as ready.
                if (homepageHtml) {
                    // Write HTML directly to iframe using srcdoc for better compatibility
                    window.vmoPreviewIsLive = false;
                    const wrappedHtml = window.wrapIframeContent ? window.wrapIframeContent(homepageHtml) : homepageHtml;
                    const draftTitle = (this._draftWebsite && this._draftWebsite.projectTitle) || window.currentProjectTitle || 'Consultation Website';
                    const draftState = this._draftWebsite && this._draftWebsite.builderState && typeof this._draftWebsite.builderState === 'object'
                        ? JSON.parse(JSON.stringify(this._draftWebsite.builderState))
                        : null;
                    const draftPagesMap = this._draftWebsite && this._draftWebsite.pagesMap && typeof this._draftWebsite.pagesMap === 'object'
                        ? this._draftWebsite.pagesMap
                        : null;
                    const prepareEditablePreview = () => {
                        if (typeof window.vmoInjectReplaceButtonsInIframe === 'function') {
                            window.vmoInjectReplaceButtonsInIframe();
                        }
                        if (typeof window.makeIframeTextEditable === 'function') {
                            window.makeIframeTextEditable(iframe);
                        }
                    };

                    iframe.addEventListener('load', function handlePreviewLoad() {
                        iframe.removeEventListener('load', handlePreviewLoad);
                        setTimeout(prepareEditablePreview, 120);
                    });
                    iframe.srcdoc = wrappedHtml;
                    console.log('✅ Preview iframe updated with generated HTML');

                    // ✅ CRITICAL: Sync window.lastContent for "View Full Page" and "Publish" buttons
                    window.lastContent = wrappedHtml;
                    window.currentProjectId = postId || window.currentProjectId || null;
                    window.currentProjectTitle = draftTitle;
                    if (draftState && Array.isArray(draftState.blocks) && draftState.blocks.length) {
                        window.vmoState = draftState;
                    } else {
                        if (!window.vmoState) window.vmoState = { blocks: [] };
                        window.vmoState.blocks = [{
                            id: window.getUniqueBlockId ? window.getUniqueBlockId() : Date.now().toString(),
                            html: homepageHtml,
                            type: 'consultation_generated'
                        }];
                    }
                    if (draftPagesMap && !window.vmoState.pagesMap) {
                        window.vmoState.pagesMap = draftPagesMap;
                    }
                    if (!window.vmoState.currentPage) {
                        window.vmoState.currentPage = 'home';
                    }
                    window.vmoState.generatedFromConsultation = true;
                    mediaResult = await this.queuePreviewMedia(postId, 'consultation_generation');
                    this.syncPreviewHtmlFromIframe();
                } else if (websiteUrl) {
                    // Prevent other components from clobbering iframe.src with srcdoc
                    window.vmoPreviewIsLive = true;
                    iframe.src = websiteUrl;
                    console.log('✅ Preview iframe updated with website URL');

                    // Ensure preview panel is visible/active when available
                    if (typeof window.vmoOpenPreview === 'function') {
                        try { window.vmoOpenPreview('modernized'); } catch (_) {}
                    }
                }
                
                // ✅ CRITICAL: Show and activate the Update Live button
                this.showUpdateLiveButton(websiteUrl);
                
                // Also dispatch a custom event for any listeners
                document.dispatchEvent(new CustomEvent('vmo:website-loaded', { 
                    detail: { url: websiteUrl, hasHtml: !!homepageHtml }
                }));
            } else {
                console.warn('⚠️ Preview iframe not found, will open website in new tab');
                // Store URL for later use
                this._generatedWebsiteUrl = websiteUrl;
                
                // Still sync lastContent if we have HTML
                if (homepageHtml) {
                    const wrappedHtml = window.wrapIframeContent ? window.wrapIframeContent(homepageHtml) : homepageHtml;
                    window.lastContent = wrappedHtml;
                }
            }
        } catch (e) {
            console.error('Error loading website preview:', e);
        }

        return mediaResult;
    }
    
    // ✅ NEW: Show and configure the Update Live button after generation
    showUpdateLiveButton(websiteUrl) {
        try {
            const updateBtn = document.getElementById('vmo-update-live-btn');
            const publishBtn = document.getElementById('vmo-publish-btn');
            
            if (updateBtn) {
                updateBtn.style.display = 'inline-flex';
                updateBtn.disabled = false;
                console.log('✅ Update Live button shown');
            }
            
            if (publishBtn) {
                publishBtn.style.display = 'inline-flex';
                publishBtn.disabled = false;
                // Highlight to draw attention
                publishBtn.style.animation = 'vmo-pulse-publish 1.5s ease-in-out 3';
                console.log('✅ Publish button highlighted');
            }
            
            // Store the live page URL for Update Live functionality
            if (websiteUrl) {
                window.vmoLivePermalink = websiteUrl;
            }
        } catch (e) {
            console.warn('Could not show Update Live button:', e);
        }
    }

    // ✅ FIX: Refresh dashboard KPIs and stats after generation - ENHANCED
    refreshDashboardAfterGeneration() {
        console.log('📊 Refreshing dashboard after generation...');
        
        try {
            // Method 1: Dispatch custom event for dashboard listeners
            document.dispatchEvent(new Event('vmo:refresh-kpis'));
            document.dispatchEvent(new Event('vmo:refresh-dashboard'));
            
            // Method 2: Call global dashboard refresh functions if they exist
            if (typeof window.vmoReloadDashboardStats === 'function') {
                window.vmoReloadDashboardStats();
            }
            
            if (typeof window.loadDashboardKPIs === 'function') {
                window.loadDashboardKPIs();
            }
            
            // Method 3: Look for VMOBusinessDashboard instance
            if (window.VMOBusinessDashboard && typeof window.VMOBusinessDashboard.loadKPIs === 'function') {
                window.VMOBusinessDashboard.loadKPIs();
            }
            
            // Method 4: Trigger via jQuery if available
            if (typeof jQuery !== 'undefined') {
                jQuery(document).trigger('vmo:dashboard:refresh');
            }
            
            // ✅ NEW: Call individual tab loaders if they exist (strategic dashboard)
            if (typeof window.loadEmailPanel === 'function') {
                window.loadEmailPanel();
            }
            if (typeof window.loadPaymentsPanel === 'function') {
                window.loadPaymentsPanel();
            }
            if (typeof window.loadSEOPanel === 'function') {
                window.loadSEOPanel();
            }
            if (typeof window.loadCRMPanel === 'function') {
                window.loadCRMPanel();
            }
            if (typeof window.loadAnalyticsPanel === 'function') {
                window.loadAnalyticsPanel();
            }
            if (typeof window.loadDocumentsPanel === 'function') {
                window.loadDocumentsPanel();
            }
            
            // ✅ NEW: Update footer with correct last scan date
            this.updateDashboardFooter();
            
            console.log('✅ Dashboard refresh signals sent');
        } catch (e) {
            console.warn('Dashboard refresh error (non-critical):', e);
        }
    }
    
    // ✅ NEW: Update dashboard footer with correct scan dates
    updateDashboardFooter() {
        try {
            const lastUpdateEl = document.getElementById('vmo-last-update');
            const nextScanEl = document.getElementById('vmo-next-scan');
            
            if (lastUpdateEl) {
                // Use actual deep research date if available, otherwise current time
                const lastResearch = this.consultation_data?.research_timestamp || 
                                     this.consultation_data?.last_research_date ||
                                     new Date().toISOString();
                const date = new Date(lastResearch);
                lastUpdateEl.textContent = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
            }
            
            if (nextScanEl) {
                // Calculate next scan (7 days from last research)
                const lastResearch = this.consultation_data?.research_timestamp || 
                                     this.consultation_data?.last_research_date ||
                                     new Date().toISOString();
                const nextDate = new Date(lastResearch);
                nextDate.setDate(nextDate.getDate() + 7);
                nextScanEl.textContent = nextDate.toLocaleDateString();
            }
        } catch (e) {
            console.warn('Could not update dashboard footer:', e);
        }
    }

    // ✅ FIX: Complete the consultation and show the generated website
    completeConsultationAndShowWebsite(websiteUrl) {
        console.log('🎉 Completing consultation and showing website...');
        
        try {
            // Update the phase to complete
            this.advancePhase();
            
            // Show a final completion banner in the chat
            this.addNodeMessageToUI('system', `
                <div style="background: linear-gradient(135deg, #059669 0%, #10b981 100%); border-radius: 12px; padding: 20px; text-align: center; margin: 15px 0;">
                    <div style="font-size: 48px; margin-bottom: 10px;">🎉</div>
                    <h3 style="color: white; margin: 0 0 10px 0;">Your Website is Live!</h3>
                    <p style="color: rgba(255,255,255,0.9); margin: 0 0 15px 0; font-size: 14px;">
                        Your complete business package has been generated successfully.
                    </p>
                    ${websiteUrl ? `
                        <a href="${websiteUrl}" target="_blank" style="
                            display: inline-block; padding: 12px 24px; border-radius: 8px;
                            background: white; color: #059669; text-decoration: none;
                            font-weight: 600; font-size: 14px;
                        ">
                            🌐 View Your Website →
                        </a>
                    ` : ''}
                </div>
            `);
            
            // After 2 more seconds, close the wizard modal
            setTimeout(() => {
                try {
                    // Close the consultation modal
                    this.closeBusinessWizard();
                    
                    // If website URL exists and preview didn't load, open in new tab
                    if (websiteUrl && this._generatedWebsiteUrl) {
                        window.open(websiteUrl, '_blank');
                    }
                    
                    console.log('✅ Consultation wizard closed, website visible');
                } catch (closeError) {
                    console.warn('Could not auto-close wizard:', closeError);
                }
            }, 2000);
            
        } catch (e) {
            console.error('Error completing consultation:', e);
            // Still try to show the website even if there's an error
            if (websiteUrl) {
                window.open(websiteUrl, '_blank');
            }
        }
    }

    // ✅ NEW: Show refinement mode - close wizard but keep preview open for editing
    showRefinementMode(postId, businessName) {
        console.log('🎨 Entering refinement mode for:', businessName);
        
        try {
            // Close the consultation wizard modal
            this.closeBusinessWizard();
            
            // Show a floating refinement toolbar/notification
            this.showRefinementNotification(postId, businessName);
            
            // Enable click-to-edit on the preview iframe
            this.enablePreviewEditing();
            
            // Dispatch event for other components
            document.dispatchEvent(new CustomEvent('vmo:refinement-mode', {
                detail: { postId, businessName, status: 'draft' }
            }));
            
            console.log('✅ Refinement mode active - user can now edit and publish');
            
        } catch (e) {
            console.error('Error entering refinement mode:', e);
        }
    }

    // Show a notification that the website is ready for refinement
    showRefinementNotification(postId, businessName) {
        // Remove any existing notification
        const existing = document.getElementById('vmo-refinement-notification');
        if (existing) existing.remove();
        
        // Store draft post ID globally for the existing Publish button to use
        window.vmoDraftPostId = postId;
        window.vmoDraftBusinessName = businessName;
        
        // Highlight the existing Publish button to draw attention
        const publishBtn = document.getElementById('vmo-publish-btn');
        if (publishBtn) {
            publishBtn.style.animation = 'vmo-pulse-publish 1.5s ease-in-out 3';
            publishBtn.style.boxShadow = '0 0 20px rgba(34, 197, 94, 0.6)';
        }
        
        const notification = document.createElement('div');
        notification.id = 'vmo-refinement-notification';
        notification.innerHTML = `
            <div style="
                position: fixed; bottom: 20px; right: 20px; z-index: 10000000;
                background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
                border: 1px solid rgba(59, 130, 246, 0.4);
                border-radius: 16px; padding: 20px; max-width: 380px;
                box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
                animation: vmo-slide-in 0.3s ease-out;
            ">
                <style>
                    @keyframes vmo-slide-in { from { transform: translateY(100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
                    @keyframes vmo-pulse-publish { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
                </style>
                <div style="display: flex; align-items: flex-start; gap: 12px;">
                    <span style="font-size: 32px;">🎨</span>
                    <div style="flex: 1;">
                        <h4 style="margin: 0 0 8px 0; color: #f1f5f9; font-size: 16px;">Website Ready for Review</h4>
                        <p style="margin: 0 0 12px 0; color: #94a3b8; font-size: 13px; line-height: 1.5;">
                            <strong style="color: #60a5fa;">${businessName}</strong> is showing in the preview. 
                            Click elements to edit, replace media, then use the <strong style="color: #22c55e;">Publish</strong> button when ready.
                        </p>
                        <div style="display: flex; gap: 8px; flex-wrap: wrap;">
                            <button id="vmo-got-it-btn" style="
                                padding: 10px 20px; border-radius: 8px;
                                background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                                border: none; color: white; font-weight: 600;
                                font-size: 13px; cursor: pointer;
                            ">
                                ✓ Got it!
                            </button>
                        </div>
                    </div>
                    <button id="vmo-close-refinement-btn" style="
                        background: transparent; border: none; color: #64748b;
                        font-size: 20px; cursor: pointer; padding: 0;
                    ">&times;</button>
                </div>
            </div>
        `;
        
        document.body.appendChild(notification);
        
        // Event handlers
        const closeNotification = () => {
            notification.remove();
            // Remove highlight from publish button
            if (publishBtn) {
                publishBtn.style.animation = '';
                publishBtn.style.boxShadow = '';
            }
        };
        
        document.getElementById('vmo-close-refinement-btn').addEventListener('click', closeNotification);
        document.getElementById('vmo-got-it-btn').addEventListener('click', closeNotification);
        
        // Auto-dismiss after 10 seconds
        setTimeout(() => {
            if (document.getElementById('vmo-refinement-notification')) {
                closeNotification();
            }
        }, 10000);
    }

    // Enable editing on the preview iframe
    enablePreviewEditing() {
        try {
            const iframe = document.getElementById('vmo-preview-iframe') || 
                           document.getElementById('vmo-preview-frame') || 
                           document.getElementById('vmo-live-preview') ||
                           document.querySelector('.vmo-preview-iframe');
            
            if (iframe) {
                // Add visual indicator that editing is enabled
                iframe.style.outline = '3px solid rgba(59, 130, 246, 0.5)';
                iframe.style.outlineOffset = '-3px';

                if (typeof window.makeIframeTextEditable === 'function') {
                    window.makeIframeTextEditable(iframe);
                }
                if (typeof window.vmoInjectReplaceButtonsInIframe === 'function') {
                    setTimeout(window.vmoInjectReplaceButtonsInIframe, 80);
                }
                console.log('✅ Preview iframe editing tools ready');
            }
        } catch (e) {
            console.warn('Could not enable preview editing:', e);
        }
    }

    // Publish the draft website (used by consultation-only flows)
    // NOTE: The primary builder publish UX is handled by assets/vmo-publish.js.
    async publishDraftWebsite(postId) {
        if (!postId) {
            console.error('No post ID to publish');
            return;
        }
        
        try {
            const AX = this.getAjax();
            
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_publish_draft_website',
                    security: AX.nonce,
                    post_id: postId
                })
            });
            
            const data = await response.json();
            
            if (data.success) {
                // Show success notification
                this.showPublishSuccessNotification(data.data?.url, data.data?.title);
                return;
            } else {
                throw new Error(data.data?.message || 'Failed to publish');
            }
        } catch (e) {
            console.error('Publish error:', e);
            alert('Failed to publish website: ' + e.message);
        }
    }

    // Show success notification after publishing
    showPublishSuccessNotification(url, title) {
        const notification = document.createElement('div');
        notification.innerHTML = `
            <div style="
                position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                z-index: 10000001; background: linear-gradient(135deg, #059669 0%, #10b981 100%);
                border-radius: 20px; padding: 40px; text-align: center;
                box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
                animation: vmo-pop-in 0.3s ease-out;
            ">
                <style>
                    @keyframes vmo-pop-in { from { transform: translate(-50%, -50%) scale(0.8); opacity: 0; } to { transform: translate(-50%, -50%) scale(1); opacity: 1; } }
                </style>
                <div style="font-size: 64px; margin-bottom: 20px;">🎉</div>
                <h2 style="color: white; margin: 0 0 10px 0; font-size: 24px;">Website Published!</h2>
                <p style="color: rgba(255,255,255,0.9); margin: 0 0 20px 0; font-size: 16px;">
                    ${title || 'Your website'} is now live!
                </p>
                <a href="${url}" target="_blank" style="
                    display: inline-block; padding: 14px 32px; border-radius: 10px;
                    background: white; color: #059669; text-decoration: none;
                    font-weight: 700; font-size: 16px;
                ">
                    🌐 View Live Website →
                </a>
                <button onclick="this.parentElement.parentElement.remove()" style="
                    display: block; margin: 20px auto 0; background: transparent;
                    border: none; color: rgba(255,255,255,0.7); cursor: pointer;
                    font-size: 14px;
                ">Close</button>
            </div>
            <div onclick="this.parentElement.remove()" style="
                position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 10000000;
            "></div>
        `;
        document.body.appendChild(notification);
    }
    
    // Build comprehensive generation prompt from consultation data
    buildGenerationPrompt(info, research, approvals) {
        const businessName = info.businessName || info.business_name || 'Your Business';
        const industry = info.industry || 'professional services';
        const offerings = info.offerings || info.products || info.services || '';
        const audience = info.targetAudience || info.target_audience || '';
        const goals = info.businessGoals || info.goals || '';
        const style = info.preferredStyle || info.style || info.designPreferences || '';
        const colors = info.colors || info.brandColors || info.preferredColors || '';
        const references = info.references || info.inspirationSites || '';
        
        // Get competitor insights from research
        const competitors = research.competitors_list || [];
        const competitorNames = competitors.slice(0, 5).map(c => c.name || c).join(', ');
        
        return `Generate a complete, professional website for "${businessName}".

BUSINESS DETAILS:
- Industry: ${industry}
- Products/Services: ${offerings}
- Target Audience: ${audience}
- Business Goals: ${goals}

DESIGN REQUIREMENTS:
- Style: ${style || 'Modern, professional, trustworthy'}
- Brand Colors: ${colors || 'Professional color scheme that conveys trust and expertise'}
- References/Inspiration: ${references || 'Clean, conversion-focused design'}

COMPETITIVE POSITIONING:
- Key competitors: ${competitorNames || 'Local competitors'}
- Our advantage: Superior user experience, faster loading, clearer messaging

PAGES TO CREATE:
1. Homepage - Hero section, value proposition, services overview, testimonials, CTA
2. About - Company story, team, mission, values
3. Services/Products - Detailed offerings with benefits and pricing
4. Contact - Contact form, location, phone, email, social links
5. Additional pages based on business needs

REQUIREMENTS:
- Mobile-responsive design
- Fast loading
- SEO-optimized structure
- Clear calls-to-action
- ALL images and videos MUST be AI-generated with detailed prompts (use data-vmo-generate='true' data-vmo-prompt='[description]' for images, data-vmo-sora='true' data-vmo-sora-prompt='[description]' for videos)
- NEVER leave any section without media — every hero, about, product, gallery, and feature section MUST have rich visual content
- NEVER use placeholder URLs (placeholder.com, example.com, picsum.photos, coverr.co)
- If the user did not provide specific images or videos, generate professional AI media prompts for every visual location
- Trust signals (testimonials, guarantees)

Generate the complete HTML with modern CSS styling.`;
    }
    
    // ✅ REMOVED: generateSupportingDocuments() was a stub that showed a misleading
    // UI message ("Preparing your legal documents...") but never actually generated
    // anything. It was never called from anywhere. Will be re-implemented when the
    // document generation backend is ready.
    
    // Fallback AI website generation
    async requestAIGeneratedWebsite(prompt) {
        const AX = this.getAjax();
        
        const response = await fetch(AX.url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action: 'vmo_generate_content',
                security: AX.nonce,
                prompt: prompt,
                mode: 'full_generation'
            })
        });
        
        if (response.ok) {
            const data = await response.json();
            if (data.success && data.data) {
                let html = data.data?.html || '';

                if (data.data.grapes_plan && window.vmoGrapesAdapter && typeof window.vmoGrapesAdapter.composeWebsite === 'function') {
                    try {
                        const extracted = this.consultation_data?.extracted_information || {};
                        const composeContext = {
                            phoneNumber: extracted.business_phone || extracted.phone || extracted.supportPhone || '',
                            emailAddress: extracted.business_email || extracted.businessEmail || extracted.email || extracted.supportEmail || ''
                        };
                        const composed = window.vmoGrapesAdapter.composeWebsite(data.data.grapes_plan, composeContext);
                        if (composed && composed.ok && composed.html) {
                            html = composed.html;
                            data.data.html = composed.html;
                        } else {
                            console.warn('[VMO Consultation] GrapesJS composition returned empty output.', composed);
                        }
                    } catch (composeError) {
                        console.error('[VMO Consultation] GrapesJS composition failed:', composeError);
                    }
                }

                if (html) {
                    const fullHtml = window.wrapIframeContent ? window.wrapIframeContent(html) : html;

                    // Inject into iframe
                    const iframe = document.getElementById('vmo-preview-frame');
                    if (iframe && iframe.contentDocument) {
                        iframe.contentDocument.open();
                        iframe.contentDocument.write(fullHtml);
                        iframe.contentDocument.close();
                    } else if (iframe) {
                        iframe.srcdoc = fullHtml;
                    }
                }
            }
        }
    }
    
    // Update the main consultation progress bar (not the deep research one)
    updateMainProgressBar(percent, label = '') {
        // ✅ FIX: Update the actual wizard header progress bar
        const wizardProgressFill = document.getElementById('vmo-wizard-progress');
        const wizardProgressText = document.getElementById('vmo-wizard-progress-text');
        
        // Get current phase info for display
        const phaseIndex = this.consultation_data.consultation_flow?.current_index || 0;
        const totalPhases = 8;
        const phaseNames = [
            'Phase 1: Business Foundations',
            'Phase 2: Executive Blueprint',
            'Phase 3: Deep Research',
            'Phase 4: Recommendations',
            'Phase 5: Website Architecture',
            'Phase 6: Systems & Compliance',
            'Phase 7: Roadmap & Tasks',
            'Phase 8: Launch Readiness'
        ];
        
        // Calculate phase-based percentage if not explicitly provided
        const phasePercent = percent || Math.round(((phaseIndex + 1) / totalPhases) * 100);
        
        // Update the wizard header progress fill (the green bar)
        if (wizardProgressFill) {
            wizardProgressFill.style.width = `${phasePercent}%`;
            wizardProgressFill.style.transition = 'width 0.5s ease';
            console.log(`📊 Wizard progress bar updated to ${phasePercent}%`);
        }
        
        // Update the progress text with phase name
        if (wizardProgressText) {
            const phaseName = phaseNames[phaseIndex] || `Phase ${phaseIndex + 1}`;
            const displayText = label || `${phaseName} · ${phasePercent}%`;
            wizardProgressText.textContent = displayText;
            console.log(`📊 Wizard progress text updated to: ${displayText}`);
        }
        
        // Also update fallback selectors for compatibility
        const progressBar = document.querySelector('.vmo-wizard-progress-bar, .vmo-consultation-progress-bar, #vmo-main-progress-bar');
        const progressLabel = document.querySelector('.vmo-wizard-progress-label, .vmo-consultation-progress-label, #vmo-progress-label');
        
        if (progressBar) {
            progressBar.style.width = `${phasePercent}%`;
        }
        
        if (progressLabel && label) {
            progressLabel.textContent = label;
        }
        
        // Store progress
        this.consultation_data.progress_percent = phasePercent;
    }

    // Decide if we can skip introductory questions due to uploaded docs
    shouldFastStartFromDocument() {
        const hasBlueprint = !!this.blueprintRaw || !!this.blueprintSummary;
        return hasBlueprint;
    }

    applyScenario(scenarioKey, stage = null) {
        if (!scenarioKey) return;
        const normalized = scenarioKey || 'scenario_a_new';
        let changed = false;
        if (this.scenarioType !== normalized) {
            this.scenarioType = normalized;
            changed = true;
        }
        if (stage && this.businessStage !== stage) {
            this.businessStage = stage;
            changed = true;
        }
        this.consultation_data.scenario_type = this.scenarioType;
        this.consultation_data.business_stage = this.businessStage;
        if (changed && window.vmo_consultant_context_v1) {
            this.saveConsultationData({
                scenario_type: this.scenarioType,
                business_stage: this.businessStage
            });
        }
    }

    detectScenarioFromMessage(message = '') {
        // Document-first always maps to Scenario C
        if (this.blueprintRaw || this.blueprintSummary || this.mode === 'document') {
            this.applyScenario('scenario_c_document', 'document_first');
            return;
        }

        const text = (message || '').toLowerCase();
        if (!text && this.priorBusinessInfo?.exists) {
            this.applyScenario('scenario_b_existing', 'existing_business');
            return;
        }

        const existingPatterns = /(existing|current|already have|my website|my shop|rebuild|redesign|optimize|improve)/i;
        const newPatterns = /(start|starting|launch|new idea|planning|from scratch|no website)/i;

        if (existingPatterns.test(text)) {
            this.applyScenario('scenario_b_existing', 'existing_business');
            return;
        }

        if (newPatterns.test(text)) {
            this.applyScenario('scenario_a_new', 'new_business');
            return;
        }

        if (this.priorBusinessInfo?.exists) {
            this.applyScenario('scenario_b_existing', 'existing_business');
        }
    }

    async updatePhaseFlag(flagKey, value = true) {
        if (!flagKey) return;
        if (!this.consultation_data.phase_flags) {
            this.consultation_data.phase_flags = {};
        }
        this.consultation_data.phase_flags[flagKey] = value;
        if (window.vmo_consultant_context_v1) {
            try {
                await this.saveConsultationData({ phase_flags: this.consultation_data.phase_flags });
            } catch (e) {
                console.warn('Phase flag save skipped', e);
            }
        }
    }
    
    // Unified file upload handler with auto-transcription and description
    async handleFileUpload(file) {
        if (!window.vmo_consultant_context_v1) {
            // Fallback to legacy path
            return null;
        }
        
        console.log('📁 Unified file upload:', file.name, file.type);
        // ✅ FIX: Stop any active TTS when a file is dropped in to keep voice and chat responses in sync
        try { this._progressiveTTSAbort(); } catch(_) {}
        
        try {
            // 1. Upload file to server
            const attachment = await this.uploadFileToServer(file);
            if (!attachment || !attachment.url) {
                throw new Error('Upload failed');
            }
            
            let meta = { filename: file.name, size: file.size };
            let role = null;
            
            // 2. Auto-transcribe audio files
            if (file.type.startsWith('audio/')) {
                console.log('🎵 Transcribing audio...');
                const transcription = await this.transcribeAudioOnServer(attachment.url);
                if (transcription) {
                    meta.transcription = transcription;
                    console.log('✅ Audio transcribed');
                }
            }
            
            // 3. Auto-describe image files
            if (file.type.startsWith('image/')) {
                console.log('🖼️ Describing image...');
                const description = await this.describeImageOnServer(attachment.url);
                if (description) {
                    meta.description = description;
                    // Auto-assign role based on description keywords
                    const lower = description.toLowerCase();
                    if (lower.includes('logo') || lower.includes('brand')) role = 'logo';
                    else if (lower.includes('hero') || lower.includes('header')) role = 'hero_image';
                    else if (lower.includes('product')) role = 'product_image';
                    else role = 'gallery';
                    console.log(`✅ Image described, auto-assigned role: ${role}`);
                }
            }
            
            // 4. Store in consultation_data.media_files
            const mediaRecord = {
                id: attachment.id || Date.now().toString(),
                url: attachment.url,
                title: file.name,
                mime: file.type,
                role: role,
                meta: meta,
                uploaded_at: new Date().toISOString()
            };
            
            this.consultation_data.media_files.push(mediaRecord);
            await this.saveConsultationData(this.consultation_data);
            
            console.log('✅ File uploaded and stored in consultation_data');
            return mediaRecord;
            
        } catch (error) {
            console.error('❌ File upload error:', error);
            return null;
        }
    }
    
    async uploadFileToServer(file) {
        const AX = this.getAjax();
        const formData = new FormData();
        formData.append('file', file);
        formData.append('action', 'vmo_upload_media');
        formData.append('security', AX.nonce);
        formData.append('session_id', this.sessionId);
        
        const response = await fetch(AX.url, {
            method: 'POST',
            body: formData
        });
        
        const data = await response.json();
        if (data.success && data.data) {
            return data.data;
        }
        throw new Error(data.data?.message || 'Upload failed');
    }
    
    async transcribeAudioOnServer(audioUrl) {
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_transcribe_audio_url',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    audio_url: audioUrl
                })
            });
            
            const data = await response.json();
            if (data.success && data.data?.transcription) {
                return data.data.transcription;
            }
        } catch (error) {
            console.warn('Transcription failed:', error);
        }
        return null;
    }
    
    async describeImageOnServer(imageUrl) {
        try {
            const AX = this.getAjax();
            const response = await fetch(AX.url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({
                    action: 'vmo_describe_image',
                    security: AX.nonce,
                    nonce: AX.nonce,
                    _ajax_nonce: AX.nonce,
                    image_url: imageUrl
                })
            });
            
            const data = await response.json();
            if (data.success && data.data?.description) {
                return data.data.description;
            }
        } catch (error) {
            console.warn('Image description failed:', error);
        }
        return null;
    }
    
    // Handle file selection
    handleFileSelected(attachment) {
        console.log('📁 Processing selected file:', attachment);
        
        const fileInfo = {
            id: attachment.id,
            url: attachment.url,
            filename: attachment.filename,
            type: attachment.type,
            title: attachment.title || attachment.filename
        };
        
        // Add to conversation
        const fileTypeEmoji = {
            'image': '🖼️',
            'video': '🎥',
            'audio': '🎵',
            'application': '📄'
        };
        
        const emoji = fileTypeEmoji[attachment.type] || '📎';
        
    // Show user message with file
    this.addMessageToUI('user', `${emoji} Uploaded: ${fileInfo.title}`);
        
        // Store in collected information
        const assetRecord = this.registerUploadedAsset(fileInfo);
        
        // Persist to DB as a user message with media attachment
        this.saveMessageToDatabase('user', `${emoji} Uploaded: ${fileInfo.title}`, [fileInfo]);

        // Immediately analyze this uploaded item as part of the conversation
        this.analyzeUploadedMedia({ ...fileInfo, kind: assetRecord.kind });
        // Render quick assignment controls so user can tag how this media should be used
        if (assetRecord.kind === 'media') {
            try { this.renderMediaRoleControls(fileInfo); } catch(_) {}
        }
        console.log('✅ File processed and analysis requested');
    }

    // Render role chips below the conversation for quick media assignment
    renderMediaRoleControls(fileInfo) {
        const chatContainer = document.querySelector('#vmo-wizard-conversation');
        if (!chatContainer) return;
        const container = document.createElement('div');
        container.className = 'vmo-media-role-controls';
        container.style.cssText = 'margin:8px 0 16px; display:flex; flex-wrap:wrap; gap:8px;';
        container.setAttribute('data-url', fileInfo.url);

        const roles = [];
        if ((fileInfo.type || '').startsWith('image')) {
            roles.push({ key: 'hero_image', label: 'Use as Hero' });
            roles.push({ key: 'header_slider', label: 'Header Slider' });
            roles.push({ key: 'gallery', label: 'Gallery' });
            roles.push({ key: 'logo', label: 'Logo' });
        } else if ((fileInfo.type || '').startsWith('video')) {
            roles.push({ key: 'background_video', label: 'Background Video' });
            roles.push({ key: 'header_slider', label: 'Header Slider' });
            roles.push({ key: 'gallery', label: 'Gallery' });
        } else if ((fileInfo.type || '').startsWith('audio')) {
            roles.push({ key: 'background_audio', label: 'Background Audio' });
        } else {
            roles.push({ key: 'business_document', label: 'Business Document' });
        }

        roles.forEach(r => {
            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'vmo-role-chip';
            btn.textContent = r.label;
            btn.style.cssText = 'padding:6px 10px; border-radius:16px; border:1px solid rgba(0,226,255,0.5); background:rgba(0,226,255,0.08); color:#9be; font-size:12px; cursor:pointer;';
            btn.addEventListener('click', async () => {
                btn.disabled = true; btn.textContent = r.label + ' ✓';
                try {
                    const AX = this.getAjax();
                    await fetch(AX.url, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                        body: new URLSearchParams({
                            action: 'vmo_update_consultation_context',
                            security: AX.nonce,
                            nonce: AX.nonce,
                            _ajax_nonce: AX.nonce,
                            session_id: this.sessionId,
                            context_action: 'assign_media_role',
                            url: fileInfo.url,
                            title: fileInfo.title,
                            type: fileInfo.type,
                            role: r.key
                        })
                    });
                    // Keep an in-memory map too
                    if (!this.collectedInformation.mediaRoles) this.collectedInformation.mediaRoles = [];
                    this.collectedInformation.mediaRoles.push({ url: fileInfo.url, title: fileInfo.title, type: fileInfo.type, role: r.key });
                } catch (e) {}
            });
            container.appendChild(btn);
        });

        chatContainer.appendChild(container);
        chatContainer.scrollTop = chatContainer.scrollHeight;
    }

    // Simple one-off transcription that inserts recognized text into the input box
    transcribeOnce(options = {}) {
        const input = this.chatInput || document.querySelector('#vmo-wizard-response');
        if (!input) {
            console.warn('No input found for transcription');
            return;
        }
        const opts = options && typeof options === 'object' ? options : {};
        const autoSend = !!opts.autoSend;
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        if (!SpeechRecognition) {
            this.addMessageToUI('ai', 'Voice-to-text is not supported in this browser.');
            return;
        }

        // If AI is speaking, do a true barge-in stop before transcribing.
        try {
            if ((this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking))
                && window.VMOAudioCoordinator && window.VMOAudioCoordinator.stopAllTTS) {
                window.VMOAudioCoordinator.stopAllTTS('barge-in', 'consultation');
            }
        } catch(_) {}

        const rec = new SpeechRecognition();
        
        // Track active transcription for cancellation (privacy control)
        this.activeTranscription = rec;
        
        try {
            const defaultLang = (document && document.documentElement && document.documentElement.lang)
                ? document.documentElement.lang
                : (navigator.language || 'en-US');
            rec.lang = defaultLang || 'en-US';
        } catch(_) {
            rec.lang = 'en-US';
        }
        rec.continuous = true;          // ✅ Keep listening until user stops
        rec.interimResults = true;      // ✅ Show live transcription as user speaks
        let transcriptBuffer = '';      // Accumulated final results
        let interimText = '';           // Current interim text
        rec.onresult = (e) => {
            // Never accept results while AI is speaking (prevents self-echo loops)
            if (this.isSpeaking || (window.VMOAudioCoordinator && window.VMOAudioCoordinator.isSpeaking)) {
                return;
            }
            // ✅ SELF-LOOP PREVENTION: Cooldown after TTS ends
            if (this._ttsEndedAt && (Date.now() - this._ttsEndedAt < (this._ttsEndCooldownMs || 1800))) {
                return;
            }
            // Collect final + interim
            for (let i = e.resultIndex; i < e.results.length; i++) {
                const t = (e.results[i][0] && e.results[i][0].transcript) ? e.results[i][0].transcript : '';
                if (e.results[i].isFinal) {
                    transcriptBuffer += t;
                    interimText = '';
                } else {
                    interimText = t;
                }
            }
            // ✅ Live-update the input box so user sees words as they speak
            input.value = (transcriptBuffer + interimText).trim();
            input.focus();
            input.scrollTop = input.scrollHeight;
        };
        rec.onerror = (e) => {
            console.warn('Transcription error:', e.error);
            if (e.error === 'no-speech') {
                if (window.showToast) window.showToast('No speech detected. Try again.', 'warning');
            } else if (e.error === 'audio-capture') {
                if (window.showToast) window.showToast('⚠️ Microphone blocked! Please allow microphone access in your browser settings.', 'error');
                alert('MICROPHONE BLOCKED!\n\nPlease:\n1. Click the 🔒 lock icon in your browser address bar\n2. Allow microphone access\n3. Reload the page');
            } else if (e.error === 'not-allowed' || e.error === 'permission-denied') {
                if (window.showToast) window.showToast('⚠️ Microphone permission denied! Please allow access in browser settings.', 'error');
                alert('MICROPHONE PERMISSION DENIED!\n\nPlease:\n1. Click the 🔒 lock icon in your browser address bar\n2. Allow microphone access\n3. Reload the page');
            }
        };
        rec.onstart = () => {
            // Claim mic ownership; allow barge-in behavior
            try {
                const ok = window.VMOAudioCoordinator ? window.VMOAudioCoordinator.requestMic('consultation', { bargeIn: true }) : true;
                if (!ok) {
                    try { rec.stop(); } catch(_) {}
                    return;
                }
            } catch(_) {}
            if (this.transcribeButton) {
                this.transcribeButton.classList.add('active');
                this.transcribeButton.style.backgroundColor = '#e53935';
                this.transcribeButton.style.color = '#fff';
            }
        };
        rec.onend = () => {
            // Clear active transcription reference
            this.activeTranscription = null;
            
            try { window.VMOAudioCoordinator && window.VMOAudioCoordinator.releaseMic('consultation'); } catch(_) {}
            if (this.transcribeButton) {
                this.transcribeButton.classList.remove('active');
                this.transcribeButton.style.backgroundColor = '';
                this.transcribeButton.style.color = '';
            }
            console.log('✅ Transcription ended - mic released');
        };
        // Warm up mic first for reliability
        this.micWarmup && this.micWarmup();
        try { rec.start(); } catch (err) { console.warn('Failed to start recognition:', err); }
        if (window.showToast) {
            window.showToast('Listening... speak now (click again to cancel)', 'info');
        }
    }

    // ✅ REMOVED: askForMediaAndContent() and saveMediaData() were dead code.
    // askForMediaAndContent() called this.getUserInput() which doesn't exist.
    // saveMediaData() used hardcoded '/save-media-data' which is not a WP endpoint.
    // Neither method was called from anywhere in the codebase.
}

// Shared Jarvis-style modal helpers (reused by Consultation)
VMOIntelligentConsultation.prototype.showModalPhase = function(phase) {
    const vp = window.vmoVoicePanel;
    if (vp && typeof vp.showListeningModal === 'function') {
        console.log("Supressing modal takeover allowing UI engagement behind TTS flow");
        // vp.showListeningModal(phase);
        const modal = document.getElementById('vmo-listening-modal');
        if (modal) {
            modal.dataset.owner = 'consultation';
            // Show upload button for consultation voice UI
            const uploadBtn = document.getElementById('vmo-listening-upload');
            if (uploadBtn) uploadBtn.style.display = 'inline-block';
            // Sync mute label to consultation's mute state
            const muteBtn = document.getElementById('vmo-listening-mute');
            if (muteBtn) {
                const muted = !!window.vmoMuteConsultation;
                muteBtn.textContent = muted ? 'Unmute' : 'Mute';
            }
        }
    } else {
        const modal = document.getElementById('vmo-listening-modal');
        const txt = modal?.querySelector('.vmo-listening-text');
        if (modal) { modal.style.display = 'none'; console.log("Voice modal hidden to allow interaction"); }
        if (txt) txt.textContent = phase === 'speaking' ? 'Speaking...' : (phase === 'thinking' ? 'Processing...' : 'Listening...');
        if (modal) modal.dataset.owner = 'consultation';
        const uploadBtn = document.getElementById('vmo-listening-upload');
        if (uploadBtn) uploadBtn.style.display = 'inline-block';
    }
};

VMOIntelligentConsultation.prototype.hideModal = function() {
    const vp = window.vmoVoicePanel;
    if (vp && typeof vp.hideListeningModal === 'function') {
        vp.hideListeningModal();
    } else {
        const modal = document.getElementById('vmo-listening-modal');
        if (modal) modal.style.display = 'none';
    }
    // Default to hiding upload button when modal isn't owned by consultation
    const uploadBtn = document.getElementById('vmo-listening-upload');
    if (uploadBtn) uploadBtn.style.display = 'none';
    // Clear live transcript when hiding modal
    this.clearLiveTranscript();
};

/**
 * ✅ LIVE TRANSCRIPT: Update the transcript display in real-time
 * Shows what the user or AI is currently saying in the listening modal
 * @param {string} text - The text to display
 * @param {string} speaker - 'user' or 'ai' to style accordingly
 */
VMOIntelligentConsultation.prototype.updateLiveTranscript = function(text, speaker = 'user') {
    // Get or cache the transcript element
    if (!this._liveTranscriptEl) {
        this._liveTranscriptEl = document.getElementById('vmo-listening-transcript');
    }
    
    if (this._liveTranscriptEl && text) {
        // Style based on speaker
        const prefix = speaker === 'ai' ? '🤖 ' : '🎤 ';
        this._liveTranscriptEl.textContent = prefix + text;
        this._liveTranscriptEl.style.opacity = '1';
        
        // Add visual distinction for AI vs user
        if (speaker === 'ai') {
            this._liveTranscriptEl.style.color = '#4CAF50'; // Green for AI
            this._liveTranscriptEl.style.fontStyle = 'normal';
        } else {
            this._liveTranscriptEl.style.color = '#fff'; // White for user
            this._liveTranscriptEl.style.fontStyle = 'italic';
        }
    }
};

/**
 * ✅ LIVE TRANSCRIPT: Clear the transcript display
 * Called when speech/message is complete
 */
VMOIntelligentConsultation.prototype.clearLiveTranscript = function() {
    if (!this._liveTranscriptEl) {
        this._liveTranscriptEl = document.getElementById('vmo-listening-transcript');
    }
    
    if (this._liveTranscriptEl) {
        this._liveTranscriptEl.textContent = '';
        this._liveTranscriptEl.style.opacity = '0';
    }
    
    // Reset buffers
    this._transcriptBuffer = '';
    this._interimTranscript = '';
};

/**
 * ✅ VOICE MODAL NOTIFICATION: Tell user to close modal to see interactive elements
 * @param {string} elementType - The type of interactive element (e.g., 'button', 'checklist', 'form')
 * @param {string} actionDescription - What the user can do after closing the modal
 */
VMOIntelligentConsultation.prototype.notifyCloseModalToSee = function(elementType, actionDescription) {
    if (!this.isVoiceEnabled) return;
    
    const message = `I've displayed ${elementType} in the chat. You can close this voice conversation window to see and interact with it. ${actionDescription || ''}`;
    
    // Speak the notification
    this.speak(message);
};

// Initialize on page load
const ensureConsultationBootstrap = (() => {
    let warnedMissingAjax = false;
    let warnedMissingMarkup = false;

    return (context = 'direct') => {
        if (window.vmoConsultation) {
            return true;
        }

        // Ensure global access for non-module scripts / fallback click handlers.
        try { window.VMOIntelligentConsultation = VMOIntelligentConsultation; } catch (_) {}

        const wizardModal = document.getElementById('vmo-business-wizard-modal');
        if (!wizardModal) {
            if (context === 'final' && !warnedMissingMarkup) {
                console.warn('⚠️ Business wizard markup not present on this page; consultation bootstrap skipped.');
                warnedMissingMarkup = true;
            }
            return false;
        }

        const hasAjaxConfig = (typeof vmoAjax !== 'undefined') || (typeof vmo_ajax !== 'undefined');
        if (!hasAjaxConfig) {
            // Don't block opening the wizard UI: the modal + disclaimer can still function,
            // and AJAX-dependent features can fail gracefully later.
            if (!warnedMissingAjax) {
                console.warn('⚠️ vmoAjax/vmo_ajax not defined yet - initializing consultation in limited mode.');
                warnedMissingAjax = true;
            }
        }

        window.vmoConsultation = new VMOIntelligentConsultation();

        // ✅ CRITICAL: Expose as window.vmoAI for backward compatibility
        window.vmoAI = window.vmoConsultation;
        
        // Also expose as global for button clicks
        window.VMOIntelligentConsultation = VMOIntelligentConsultation;
        
        // Setup event listeners
        window.vmoConsultation.setupEventListeners();

        console.log('✅ VMO Intelligent Consultation v9.0.1 FIXED VERSION ready');
        console.log('✅ Available as: window.vmoConsultation and window.vmoAI');
        console.log('🔇 CRITICAL FIX: Microphone stops while AI speaks - NO MORE FEEDBACK LOOP!');
        return true;
    };
})();

function watchForConsultationMarkup() {
    if (ensureConsultationBootstrap()) {
        return;
    }

    const observerAvailable = typeof MutationObserver !== 'undefined';
    let observer = null;

    if (observerAvailable && document.body) {
        observer = new MutationObserver(() => {
            if (ensureConsultationBootstrap('observer')) {
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    let attempts = 0;
    const maxAttempts = 30;
    const pollInterval = 350;
    const poller = setInterval(() => {
        attempts += 1;
        if (ensureConsultationBootstrap('poll')) {
            clearInterval(poller);
            if (observer) {
                observer.disconnect();
            }
            return;
        }
        if (attempts >= maxAttempts) {
            clearInterval(poller);
            if (observer) {
                observer.disconnect();
            }
            ensureConsultationBootstrap('final');
        }
    }, pollInterval);
}

// Always override legacy launchers so the header button never opens an older wizard UI.
window.launchBusinessWizard = () => {
    if (!window.vmoConsultation && !ensureConsultationBootstrap()) {
        console.warn('⚠️ Business wizard not ready yet.');
        return false;
    }
    return window.vmoConsultation ? window.vmoConsultation.openBusinessWizard() : false;
};

// ═══════════════════════════════════════════════════════════════════════════
// Named Function Exports (Spec Compliance)
// These wrap inline class logic into discrete, callable functions as
// documented in the VMO Plugin Specification.
// ═══════════════════════════════════════════════════════════════════════════

/**
 * presentBlueprintConfirmation()
 * Presents the Phase 2 blueprint summary to the user and waits for their
 * confirmation before proceeding to deep research.
 * Wraps the tail of autoConfirmDocument() where topics are locked.
 */
window.presentBlueprintConfirmation = async function() {
    const c = window.vmoConsultation;
    if (!c) { console.warn('[VMO] Consultation not initialized'); return false; }

    try {
        // Mark all consultation topics as covered
        if (c.consultationTopics) {
            c.consultationTopics.forEach(topic => c.topicsCovered?.add(topic));
        }

        // Lock the document context
        if (typeof c.lockDocumentContext === 'function') {
            await c.lockDocumentContext();
        }

        // Ensure we're at least at Phase 1
        if (typeof c.ensurePhaseAtLeast === 'function') {
            await c.ensurePhaseAtLeast(1);
        }

        // Set phase flag
        try { c.updatePhaseFlag?.('phase2_summary_presented', true); } catch (_) {}

        // Present confirmation message
        const msg = '✅ **Your business blueprint has been analyzed and locked in!**\n\n'
            + 'Please take a moment to review the summary above. If everything looks good, '
            + 'just say **"looks good"** or **"proceed"** and I\'ll begin the comprehensive '
            + 'deep research phase.\n\nIf you\'d like to make any changes or add more details, '
            + 'just let me know before we proceed.';

        if (typeof c.addMessageToUI === 'function') {
            c.addMessageToUI('ai', msg);
        }
        if (typeof c.saveMessageToDatabase === 'function') {
            await c.saveMessageToDatabase('ai', 'Blueprint analyzed and locked. Waiting for user confirmation.');
        }

        // Voice feedback
        if (c.isVoiceEnabled && typeof c.speak === 'function') {
            try {
                c.speak('Your business blueprint has been analyzed and locked in. Please review the summary. Say looks good or proceed to continue.');
            } catch (_) {}
        }

        return true;
    } catch (err) {
        console.error('[VMO] presentBlueprintConfirmation error:', err);
        return false;
    }
};

/**
 * startPhase3DeepResearch()
 * Confirms the user's blueprint and triggers the Phase 3 deep research process.
 * Wraps confirmAndStartDeepResearch() logic.
 */
window.startPhase3DeepResearch = async function() {
    const c = window.vmoConsultation;
    if (!c) { console.warn('[VMO] Consultation not initialized'); return false; }

    try {
        // Stop any ongoing speech
        try { c.stopSpeaking?.(); } catch (_) {}

        // Clear confirmation flag
        c._awaitingResearchConfirmation = false;

        // Set phase flags
        if (c.consultation_data?.phase_flags) {
            c.consultation_data.phase_flags.phase2_confirmed = true;
        }
        c.phase3ResearchRequested = true;

        // Persist phase2_confirmed to server
        try { c.updatePhaseFlag?.('phase2_confirmed', true); } catch (_) {}

        // Ensure phase advancement
        if (typeof c.ensurePhaseAtLeast === 'function') {
            await c.ensurePhaseAtLeast(2);
        }

        // Display starting message
        if (typeof c.addMessageToUI === 'function') {
            c.addMessageToUI('ai', '✅ Perfect! Starting comprehensive Phase 3 research now...');
        }
        if (typeof c.saveMessageToDatabase === 'function') {
            await c.saveMessageToDatabase('ai', 'Phase 3 deep research started.');
        }

        // Trigger deep research
        if (typeof c.triggerDeepResearch === 'function') {
            await c.triggerDeepResearch();
        } else if (typeof c.confirmAndStartDeepResearch === 'function') {
            await c.confirmAndStartDeepResearch();
        }

        return true;
    } catch (err) {
        console.error('[VMO] startPhase3DeepResearch error:', err);
        return false;
    }
};

/**
 * processResearchChunk(chunkIndex, totalChunks, chunkLabel)
 * Processes a single deep research chunk with retry logic.
 * Wraps the inner loop body of triggerDeepResearch().
 * Returns { success, data, isComplete }.
 */
window.processResearchChunk = async function(chunkIndex, totalChunks, chunkLabel) {
    const c = window.vmoConsultation;
    if (!c) { console.warn('[VMO] Consultation not initialized'); return { success: false }; }

    const MAX_RETRIES = 5;
    let retryCount = 0;
    let chunkSuccess = false;
    let chunkData = null;

    // Update progress UI
    if (typeof c.updateResearchProgress === 'function') {
        c.updateResearchProgress(chunkIndex + 1, totalChunks, chunkLabel || `Research chunk ${chunkIndex + 1}`);
    }

    const ajaxUrl = (typeof vmoGetAdminAjaxUrl === 'function')
        ? vmoGetAdminAjaxUrl()
        : (window.vmo_ajax?.ajaxurl || window.ajaxurl || '/wp-admin/admin-ajax.php');

    while (!chunkSuccess && retryCount < MAX_RETRIES) {
        try {
            const controller = new AbortController();
            const timeout = setTimeout(() => controller.abort(), 600000); // 10 min

            const formData = new FormData();
            formData.append('action', 'vmo_trigger_deep_research');
            formData.append('nonce', window.vmo_ajax?.nonce || '');
            formData.append('chunk_index', chunkIndex);
            formData.append('total_chunks', totalChunks);

            if (c.consultation_data?.extracted_information) {
                formData.append('consultation_data', JSON.stringify(c.consultation_data.extracted_information));
            }

            const response = await fetch(ajaxUrl, {
                method: 'POST',
                body: formData,
                credentials: 'same-origin',
                signal: controller.signal,
            });

            clearTimeout(timeout);

            if (!response.ok) {
                if (response.status === 422) {
                    console.warn('[VMO] Research chunk gate violation, retrying...');
                    retryCount++;
                    continue;
                }
                throw new Error(`HTTP ${response.status}`);
            }

            const result = await response.json();
            if (result.success) {
                chunkSuccess = true;
                chunkData = result.data || {};
            } else {
                retryCount++;
                if (retryCount >= MAX_RETRIES) {
                    throw new Error(result?.data?.message || 'Research chunk failed after retries');
                }
                await new Promise(r => setTimeout(r, 2000 * retryCount)); // Exponential backoff
            }
        } catch (err) {
            retryCount++;
            if (retryCount >= MAX_RETRIES) {
                console.error(`[VMO] Research chunk ${chunkIndex} failed:`, err);
                return { success: false, error: err.message };
            }
            await new Promise(r => setTimeout(r, 2000 * retryCount));
        }
    }

    return {
        success: chunkSuccess,
        data: chunkData,
        isComplete: chunkData?.is_complete || false,
        nextChunk: chunkIndex + 1,
    };
};

/**
 * handleRecommendationApproval(approvedIds, rejectedIds, userChoices)
 * Processes the user's approval/rejection of Phase 4-5 recommendations.
 * Wraps the approveBtn click handler logic.
 */
window.handleRecommendationApproval = async function(approvedIds, rejectedIds, userChoices) {
    const c = window.vmoConsultation;
    if (!c) { console.warn('[VMO] Consultation not initialized'); return false; }

    try {
        const recommendationsApproval = {
            accepted: approvedIds || [],
            rejected: rejectedIds || [],
            user_choices: userChoices || {},
            approved_at: new Date().toISOString(),
        };

        // Save to state
        if (!c.state) c.state = {};
        if (!c.state.approvals) c.state.approvals = {};
        c.state.approvals.recommendations = recommendationsApproval;

        // Save to consultation data
        if (c.consultation_data) {
            c.consultation_data.recommendations_approval = recommendationsApproval;
        }

        // Persist to server
        if (typeof c.saveConsultationData === 'function') {
            await c.saveConsultationData();
        }

        // Display confirmation
        const acceptedCount = (approvedIds || []).length;
        const rejectedCount = (rejectedIds || []).length;
        if (typeof c.addMessageToUI === 'function') {
            c.addMessageToUI('ai',
                `✅ Perfect — your personalized recommendations have been locked in! `
                + `(${acceptedCount} accepted, ${rejectedCount} declined)\n\n`
                + `I\'ll now proceed with building your customized solution based on your selections.`
            );
        }
        if (typeof c.saveMessageToDatabase === 'function') {
            await c.saveMessageToDatabase('ai', `Recommendations approved: ${acceptedCount} accepted, ${rejectedCount} declined.`);
        }

        // Set phase flag and advance
        try { c.updatePhaseFlag?.('phase4_confirmed', true); } catch (_) {}
        if (typeof c.advancePhase === 'function') {
            await c.advancePhase();
        }

        return true;
    } catch (err) {
        console.error('[VMO] handleRecommendationApproval error:', err);
        return false;
    }
};

/**
 * handleZohoOAuth(clientId, clientSecret)
 * Initiates the Zoho OAuth flow — saves credentials, opens popup, listens for token.
 * Wraps the vmo-zoho-generate-token click handler logic.
 */
window.handleZohoOAuth = async function(clientId, clientSecret) {
    if (!clientId || !clientSecret) {
        console.error('[VMO] Zoho OAuth requires clientId and clientSecret');
        return { success: false, error: 'Missing credentials' };
    }

    const ajaxUrl = (typeof vmoGetAdminAjaxUrl === 'function')
        ? vmoGetAdminAjaxUrl()
        : (window.vmo_ajax?.ajaxurl || window.ajaxurl || '/wp-admin/admin-ajax.php');

    try {
        // Step 1: Save credentials
        const saveForm = new FormData();
        saveForm.append('action', 'vmo_zoho_save_credentials');
        saveForm.append('security', window.vmo_ajax?.nonce || '');
        saveForm.append('client_id', clientId);
        saveForm.append('client_secret', clientSecret);

        const saveResp = await fetch(ajaxUrl, { method: 'POST', body: saveForm, credentials: 'same-origin' });
        const saveData = await saveResp.json();
        if (!saveData.success) {
            return { success: false, error: saveData?.data?.message || 'Failed to save credentials' };
        }

        // Step 2: Get auth URL
        const authForm = new FormData();
        authForm.append('action', 'vmo_zoho_get_auth_url');
        authForm.append('security', window.vmo_ajax?.nonce || '');

        const authResp = await fetch(ajaxUrl, { method: 'POST', body: authForm, credentials: 'same-origin' });
        const authData = await authResp.json();
        if (!authData.success || !authData.data?.auth_url) {
            return { success: false, error: 'Failed to get Zoho auth URL' };
        }

        const authUrl = authData.data.auth_url;
        const phpRedirectUri = authData.data.redirect_uri || '';
        const oauthState = authData.data.oauth_state || '';
        console.log('[VMO] Zoho OAuth redirect_uri:', phpRedirectUri);

        // Step 3: Open popup
        const w = 600, h = 700;
        const left = (screen.width - w) / 2, top = (screen.height - h) / 2;
        const popup = window.open(authUrl, 'vmo_zoho_auth', `width=${w},height=${h},left=${left},top=${top},scrollbars=yes`);

        if (!popup || popup.closed) {
            return { success: false, error: 'Popup was blocked. Please allow popups and try again.' };
        }

        // Step 4: Listen for token via postMessage + poll
        const tokenResult = await new Promise((resolve) => {
            const timeout = 300000; // 5 minutes
            const startTime = Date.now();
            let settled = false;
            let poller = null;

            const finish = (result) => {
                if (settled) {
                    return;
                }
                settled = true;
                if (poller) {
                    clearInterval(poller);
                }
                window.removeEventListener('message', messageHandler);
                resolve(result);
            };

            // ✅ FIX: Listen for the CORRECT postMessage type that PHP sends
            const messageHandler = (event) => {
                if (event.origin !== window.location.origin) {
                    return;
                }
                if (event.data?.type !== 'vmo_zoho_token_obtained') {
                    return;
                }
                if (oauthState && event.data?.state && event.data.state !== oauthState) {
                    return;
                }

                finish({
                    success: !!event.data?.success,
                    message: event.data?.message || ''
                });
            };
            window.addEventListener('message', messageHandler);

            // Also poll server for token status
            poller = setInterval(async () => {
                if (Date.now() - startTime > timeout) {
                    finish({ success: false, message: '' });
                    return;
                }
                try {
                    const pollForm = new FormData();
                    pollForm.append('action', 'vmo_zoho_check_token_status');
                    pollForm.append('security', window.vmo_ajax?.nonce || '');
                    pollForm.append('oauth_state', oauthState);
                    const resp = await fetch(ajaxUrl, { method: 'POST', body: pollForm, credentials: 'same-origin' });
                    const data = await resp.json();
                    if (data.success) {
                        const pollStatus = data.data?.status || '';
                        if (pollStatus === 'success') {
                            finish({
                                success: true,
                                message: data.data?.message || ''
                            });
                            return;
                        }
                        if (pollStatus === 'error') {
                            finish({
                                success: false,
                                message: data.data?.message || ''
                            });
                            return;
                        }
                    }
                } catch (_) {}

                if (popup.closed) {
                    finish({ success: false, message: '' });
                }
            }, 3000);
        });

        return {
            success: tokenResult.success,
            redirect_uri: phpRedirectUri,
            error: tokenResult.success ? '' : (tokenResult.message || 'Authorization failed or the popup was closed.')
        };
    } catch (err) {
        console.error('[VMO] Zoho OAuth error:', err);
        return { success: false, error: err.message };
    }
};

document.addEventListener('DOMContentLoaded', () => {
    if (!ensureConsultationBootstrap()) {
        watchForConsultationMarkup();
    }
});
