(function($) {
    'use strict';

    const MEDIA_DISMISS_KEY = 'vmo_media_nag_dismissed';
    const PROGRESS_ID = 'vmo-media-build-progress';
    const PLACEHOLDER_PATTERNS = [
        /picsum\.photos/i,
        /via\.placeholder/i,
        /placeholderimg/i,
        /placeholder/i,
        /coverr\.co/i,
        /placehold\.co/i,
        /dummyimage\.com/i,
        /example\.com/i
    ];

    function escapeHtml(value) {
        return String(value || '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    function getAjaxUrl() {
        return (window.vmo_ajax && (window.vmo_ajax.ajaxurl || window.vmo_ajax.ajax_url)) || '/wp-admin/admin-ajax.php';
    }

    function getNonce() {
        return (window.vmo_ajax && (window.vmo_ajax.nonce || window.vmo_ajax.builder_nonce)) || '';
    }

    function buildTargetKey(target) {
        return target.mediaId || [target.type, target.prompt, target.blockIndex].filter(Boolean).join('|');
    }

    function normalizePrompt(text, fallback) {
        const value = String(text || '').trim();
        return value || fallback;
    }

    function readContextLabel(element) {
        if (!element) return '';
        const ownLabel = element.getAttribute('aria-label') || element.getAttribute('alt') || '';
        if (ownLabel.trim()) return ownLabel.trim();

        const section = element.closest('section, [data-block-id], .vmo-section, article, div');
        if (!section) return '';
        const heading = section.querySelector('h1, h2, h3, h4, h5, h6');
        if (heading && heading.textContent.trim()) {
            return heading.textContent.trim().slice(0, 140);
        }

        return section.textContent.trim().slice(0, 140);
    }

    function stripGenerationMarkersFromElement(element) {
        element.removeAttribute('data-vmo-generate');
        element.removeAttribute('data-vmo-prompt');
        element.removeAttribute('data-vmo-sora');
        element.removeAttribute('data-vmo-sora-prompt');
    }

    function summarizeTargets(targets) {
        const list = Array.isArray(targets) ? targets : [];
        const images = list.filter((item) => item && item.type === 'image').length;
        const videos = list.filter((item) => item && item.type === 'video').length;
        const parts = [];
        if (images) parts.push(`${images} image${images === 1 ? '' : 's'}`);
        if (videos) parts.push(`${videos} video${videos === 1 ? '' : 's'}`);

        return {
            total: list.length,
            images,
            videos,
            summary: parts.length ? parts.join(', ') : 'media assets'
        };
    }

    function buildRunCopy(source, targets, options = {}) {
        const summary = summarizeTargets(targets);
        const blocking = !!options.blocking;

        if (source === 'showcase') {
            return {
                title: 'Building Demo Showcase Media',
                detail: `AI is preparing the demo media stack: ${summary.summary}. The showcase stays in progress until every layer is inserted.`,
                status: 'Rendering showcase assets'
            };
        }

        if (source === 'generation' || source === 'consultation_generation' || source === 'consultation_preview') {
            if (blocking || source === 'consultation_generation' || source === 'consultation_preview') {
                return {
                    title: 'Generating Required Website Media',
                    detail: `AI is generating and inserting the required website media: ${summary.summary}. This step stays in progress until every required asset is inserted.`,
                    status: 'Required media generation in progress'
                };
            }

            return {
                title: 'Background Media Queue',
                detail: `AI is generating the remaining website media assets: ${summary.summary}. These assets continue rendering in the background and will populate automatically as they finish.`,
                status: 'Server queue running'
            };
        }

        return {
            title: 'Generating Website Media',
            detail: `Preparing ${summary.summary} for the current preview.`,
            status: 'Queued'
        };
    }

    function buildCompletionCopy(source, result) {
        if (result.complete) {
            if (source === 'showcase') {
                return {
                    title: 'Preview Media Complete',
                    detail: 'All preview images and video layers were generated and inserted.',
                    status: 'Website media ready for review.'
                };
            }

            return {
                title: 'Website Media Complete',
                detail: 'All required images and videos were generated and inserted.',
                status: 'Ready for final review.'
            };
        }

        const failedMessage = result.failed.length
            ? result.failed.map((item) => item.message).slice(0, 2).join(' ')
            : 'Some required media could not be generated.';

        return {
            title: source === 'showcase' ? 'Demo Showcase Media Incomplete' : 'Website Media Incomplete',
            detail: failedMessage,
            status: source === 'showcase' ? 'The demo showcase is still incomplete.' : 'The build is still incomplete.'
        };
    }

    function replacementJobAjax(action, payload = {}) {
        return new Promise((resolve, reject) => {
            $.post(getAjaxUrl(), Object.assign({
                action,
                security: getNonce()
            }, payload)).done((response) => {
                if (response && response.success) {
                    resolve(response.data || {});
                    return;
                }
                reject(new Error(response?.data?.message || 'Media job failed.'));
            }).fail((xhr) => {
                reject(new Error(xhr?.responseJSON?.data?.message || 'Media job request failed.'));
            });
        });
    }

    function runQueuedMediaAssetJob(target, type, options = {}) {
        const mediaType = type === 'video' ? 'video' : (type === 'audio' ? 'audio' : 'image');
        const replacementType = mediaType === 'video' ? 'ai_video' : (mediaType === 'audio' ? 'ai_audio' : 'ai_image');
        const request = {
            project_id: window.currentProjectId || window.vmoCurrentProjectId || target.projectId || target.project_id || 0,
            media_type: mediaType,
            replacement_type: replacementType,
            prompt: target.prompt || '',
            model: options.model || target.model || (mediaType === 'video' ? 'sora-2' : 'openai-image-generation-2'),
            seconds: String(options.seconds || target.seconds || '4'),
            size: options.size || target.size || (mediaType === 'video' ? '1280x720' : '1024x1024'),
            slot_context: JSON.stringify({
                schema: 'vmo_media_queue_slot_v1',
                slot_id: target.slot_id || target.mediaId || target.media_id || '',
                media_id: target.mediaId || target.media_id || '',
                page_id: target.page_id || target.pageId || '',
                section_id: target.section_id || target.sectionId || '',
                product_id: target.product_id || target.productId || '',
                placement_role: target.placement || target.placement_role || (mediaType === 'video' ? 'section_video' : 'section_image'),
                media_type: mediaType,
                block_id: target.blockId || target.block_id || '',
                project_id: window.currentProjectId || window.vmoCurrentProjectId || target.projectId || target.project_id || 0,
                source: target.source || 'media_generator'
            })
        };

        let latestJob = null;
        return replacementJobAjax('vmo_estimate_media_replacement_job', request)
            .then((estimate) => {
                if (options.onUpdate) options.onUpdate({ progress: 2, stage_label: 'Cost checked', estimate });
                return replacementJobAjax('vmo_create_media_replacement_job', request);
            })
            .then((createData) => new Promise((resolve, reject) => {
                latestJob = createData.job || {};
                if (options.onUpdate) options.onUpdate(latestJob);
                if (latestJob.status === 'complete' && latestJob.url) {
                    resolve(latestJob.url);
                    return;
                }

                let attempts = 0;
                let softNoticeShown = false;
                const softNoticeAttempts = mediaType === 'video' ? 180 : 80;
                const normalPollDelay = mediaType === 'video' ? 5000 : 3000;
                const slowPollDelay = mediaType === 'video' ? 15000 : 10000;
                const poll = () => {
                    attempts += 1;
                    replacementJobAjax('vmo_get_media_replacement_job', { job_id: latestJob.id }).then((statusData) => {
                        latestJob = statusData.job || latestJob;
                        if (options.onUpdate) options.onUpdate(latestJob);
                        if (latestJob.status === 'complete' && latestJob.url) {
                            resolve(latestJob.url);
                            return;
                        }
                        if (latestJob.status === 'failed') {
                            reject(new Error(latestJob.plain_language_error || latestJob.error || 'Media generation failed.'));
                            return;
                        }
                        if (attempts >= softNoticeAttempts) {
                            softNoticeShown = true;
                            if (options.onUpdate) {
                                options.onUpdate(Object.assign({}, latestJob, {
                                    progress: Math.max(parseInt(latestJob.progress || 0, 10) || 0, 60),
                                    stage_label: 'This generation is taking longer than usual, but the job is still active. VMO will continue processing it safely.',
                                    estimated_remaining_text: 'Still generating safely'
                                }));
                            }
                        }
                        setTimeout(poll, softNoticeShown ? slowPollDelay : normalPollDelay);
                    }).catch((error) => {
                        const message = String(error?.message || 'Media job status check is still pending.');
                        if (/not found|unauthorized|not logged in/i.test(message)) {
                            reject(error);
                            return;
                        }
                        softNoticeShown = softNoticeShown || attempts >= softNoticeAttempts;
                        if (options.onUpdate && softNoticeShown) {
                            options.onUpdate(Object.assign({}, latestJob || {}, {
                                progress: Math.max(parseInt(latestJob?.progress || 0, 10) || 0, 60),
                                stage_label: 'VMO is still checking this media job. Generation continues safely in the background.',
                                estimated_remaining_text: 'Still generating safely'
                            }));
                        }
                        setTimeout(poll, softNoticeShown ? slowPollDelay : 5000);
                    });
                };
                setTimeout(poll, 1500);
            }));
    }

    window.vmoMediaGenerator = {
        activeRun: null,
        queuePollTimer: null,
        queuePollProjectId: null,
        appliedQueueItems: {},

        init: function() {
            $(document).ready(() => {
                if ($('body').hasClass('vmo-builder-active') || $('.vmo-dashboard').length) {
                    setTimeout(() => this.checkMissingMedia(), 3000);
                }
            });
        },

        isPlaceholderUrl: function(src) {
            const value = String(src || '').trim();
            if (!value || value === '#') return true;
            return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value));
        },

        getIframeDocument: function() {
            const iframeEl = document.getElementById('vmo-preview-iframe');
            if (!iframeEl) return null;

            try {
                return iframeEl.contentDocument || iframeEl.contentWindow.document || null;
            } catch (_) {
                return null;
            }
        },

        readElementMediaType: function(element) {
            const tagName = (element && element.tagName ? element.tagName : '').toLowerCase();
            if (!element) return 'image';
            if (element.hasAttribute('data-vmo-sora') || element.getAttribute('data-type') === 'video' || tagName === 'video') {
                return 'video';
            }

            return 'image';
        },

        readElementSourceUrl: function(element) {
            if (!element) return '';

            const tagName = (element.tagName || '').toLowerCase();
            if (tagName === 'video') {
                return element.getAttribute('src') || (element.querySelector('source') && element.querySelector('source').getAttribute('src')) || '';
            }

            if (tagName === 'img') {
                return element.getAttribute('src') || '';
            }

            const nestedMedia = element.querySelector('video, img');
            if (!nestedMedia) {
                return '';
            }

            const nestedTag = (nestedMedia.tagName || '').toLowerCase();
            if (nestedTag === 'video') {
                return nestedMedia.getAttribute('src') || (nestedMedia.querySelector('source') && nestedMedia.querySelector('source').getAttribute('src')) || '';
            }

            return nestedMedia.getAttribute('src') || '';
        },

        readElementPrompt: function(element, type) {
            if (!element) {
                return type === 'video' ? 'Cinematic website background video' : 'Premium website image';
            }

            const directPrompt = type === 'video'
                ? element.getAttribute('data-vmo-sora-prompt')
                : element.getAttribute('data-vmo-prompt');
            const nestedMedia = element.querySelector('img, video');
            const nestedLabel = nestedMedia
                ? (nestedMedia.getAttribute('aria-label') || nestedMedia.getAttribute('alt') || '')
                : '';
            const ariaLabel = element.getAttribute('aria-label') || element.getAttribute('alt') || '';

            return normalizePrompt(
                directPrompt,
                ariaLabel || nestedLabel || readContextLabel(element) || (type === 'video' ? 'Cinematic website background video' : 'Premium website image')
            );
        },

        isPendingPlaceholderElement: function(element, type, sourceUrl) {
            if (!element) return false;

            const tagName = (element.tagName || '').toLowerCase();
            const isPlaceholderContainer = element.classList.contains('vmo-media-placeholder')
                || element.matches('[data-type="image"], [data-type="video"]');

            if (element.hasAttribute('data-vmo-generate') || element.hasAttribute('data-vmo-sora')) {
                return true;
            }

            if (tagName === 'img' || tagName === 'video') {
                return this.isPlaceholderUrl(sourceUrl) || !sourceUrl;
            }

            if (!isPlaceholderContainer) {
                return false;
            }

            if (!sourceUrl) {
                return true;
            }

            return this.isPlaceholderUrl(sourceUrl);
        },

        collectPendingMediaTargets: function() {
            const collected = [];
            const seen = new Set();
            const iframeDoc = this.getIframeDocument();

            const pushTarget = (target) => {
                if (!target || !target.type || !target.prompt) return;
                const key = buildTargetKey(target);
                if (!key || seen.has(key)) return;
                seen.add(key);
                collected.push(target);
            };

            if (iframeDoc) {
                iframeDoc.querySelectorAll('.vmo-media-placeholder, [data-type="image"], [data-type="video"], [data-vmo-generate], [data-vmo-sora], img, video').forEach((element) => {
                    const type = this.readElementMediaType(element);
                    const mediaId = element.getAttribute('data-media-id') || '';
                    const sourceUrl = this.readElementSourceUrl(element);
                    const requiresGeneration = this.isPendingPlaceholderElement(element, type, sourceUrl);

                    if (!requiresGeneration) return;

                    pushTarget({
                        mediaId,
                        type,
                        prompt: this.readElementPrompt(element, type),
                        label: element.getAttribute('aria-label') || element.getAttribute('alt') || readContextLabel(element) || (type === 'video' ? 'Website video' : 'Website image'),
                        source: 'iframe'
                    });
                });
            }

            if (window.vmoState && Array.isArray(window.vmoState.blocks)) {
                window.vmoState.blocks.forEach((block, blockIndex) => {
                    if (!block || !block.html) return;

                    const $tmp = $('<div>').html(block.html);
                    $tmp.find('.vmo-media-placeholder, [data-type="image"], [data-type="video"], [data-vmo-generate], [data-vmo-sora], img, video').each((_, node) => {
                        const $node = $(node);
                        const type = this.readElementMediaType(node);
                        const sourceUrl = this.readElementSourceUrl(node);
                        const requiresGeneration = this.isPendingPlaceholderElement(node, type, sourceUrl);

                        if (!requiresGeneration) return;

                        pushTarget({
                            mediaId: $node.attr('data-media-id') || '',
                            type,
                            prompt: this.readElementPrompt(node, type),
                            label: $node.attr('aria-label') || $node.attr('alt') || (type === 'video' ? 'Website video' : 'Website image'),
                            blockIndex,
                            source: 'blocks'
                        });
                    });
                });
            }

            return collected;
        },

        buildQueueTargets: function(targets, options = {}) {
            const list = Array.isArray(targets) ? targets : [];
            const source = String(options.source || '');
            const preferPremiumVideo = source === 'consultation_generation' || source === 'consultation_preview';
            let videoIndex = 0;

            return list.map((target, index) => {
                const normalized = Object.assign({}, target, {
                    mediaId: target.mediaId || '',
                    blockIndex: typeof target.blockIndex === 'number' ? target.blockIndex : -1,
                    queuePosition: index
                });

                if (normalized.type === 'video') {
                    normalized.model = normalized.model || (preferPremiumVideo ? 'sora-2-pro' : (videoIndex === 0 ? 'sora-2-pro' : 'sora-2'));
                    normalized.seconds = normalized.seconds || '4';
                    normalized.size = normalized.size || '1280x720';
                    videoIndex += 1;
                }

                return normalized;
            });
        },

        stopProjectQueuePolling: function() {
            if (this.queuePollTimer) {
                clearTimeout(this.queuePollTimer);
                this.queuePollTimer = null;
            }
            this.queuePollProjectId = null;
        },

        applyCompletedQueueItems: function(items) {
            const list = Array.isArray(items) ? items : [];
            let appliedCount = 0;

            list.forEach((item) => {
                if (!item || item.status !== 'complete' || !item.url) return;

                const key = item.key || [item.media_id, item.type, item.url].filter(Boolean).join('|');
                if (!key || this.appliedQueueItems[key]) return;

                this.applyGeneratedMedia({
                    mediaId: item.media_id || '',
                    type: item.type || 'image'
                }, item.url, item.type || 'image');

                this.appliedQueueItems[key] = true;
                appliedCount += 1;
            });

            if (appliedCount) {
                if (typeof window.renderBlocks === 'function') {
                    window.renderBlocks();
                }
                if (typeof window.autoSaveCheckpoint === 'function') {
                    window.autoSaveCheckpoint();
                }
                if (typeof window.vmoInjectReplaceButtonsInIframe === 'function') {
                    window.vmoInjectReplaceButtonsInIframe();
                }
            }

            return appliedCount;
        },

        startProjectQueuePolling: function(projectId, options = {}) {
            const postId = parseInt(projectId, 10);
            if (!postId || !getAjaxUrl() || !getNonce()) return Promise.resolve(null);

            this.stopProjectQueuePolling();
            this.queuePollProjectId = postId;
            const blocking = !!options.waitForCompletion || options.source === 'consultation_generation' || options.source === 'consultation_preview';

            return new Promise((resolve) => {
                let settled = false;
                const finish = (queue) => {
                    if (settled) return;
                    settled = true;
                    if (typeof options.onComplete === 'function') {
                        options.onComplete(queue || null);
                    }
                    resolve(queue || null);
                };

                const poll = () => {
                    if (this.queuePollProjectId !== postId) {
                        finish(null);
                        return;
                    }

                    $.post(getAjaxUrl(), {
                        action: 'vmo_get_project_media_queue',
                        security: getNonce(),
                        project_id: postId
                    }, (response) => {
                        if (!response || !response.success || !response.data) {
                            this.queuePollTimer = setTimeout(poll, 8000);
                            return;
                        }

                        const queue = response.data;
                        const counts = queue.counts || {};
                        const total = Math.max(0, counts.total || 0);
                        const completed = Math.max(0, (counts.complete || 0) + (counts.failed || 0));
                        const summary = total
                            ? `${blocking ? 'Required media' : 'Background media queue'}: ${completed}/${total} processed.`
                            : `${blocking ? 'Required media generation' : 'Background media queue'} is idle.`;

                        if (!total && (!queue.items || !queue.items.length) && queue.status === 'complete') {
                            this.stopProjectQueuePolling();
                            finish(queue);
                            return;
                        }

                        this.applyCompletedQueueItems(queue.items || []);

                        this.updateProgressUi({
                            title: queue.status === 'complete'
                                ? (blocking ? 'Required Website Media Complete' : 'Background Media Complete')
                                : (blocking ? 'Generating Required Website Media' : 'Background Media Queue'),
                            detail: total
                                ? (blocking
                                    ? `VMO is generating and inserting every required website media asset. ${summary}`
                                    : `VMO is finishing queued website media in the background. ${summary}`)
                                : (blocking ? 'No required media remain for this project.' : 'No queued media remain for this project.'),
                            status: queue.status === 'complete'
                                ? (blocking ? 'All required assets have been inserted.' : 'All queued assets have been inserted.')
                                : (blocking ? 'The consultation stays in progress until all required media is inserted.' : 'You can keep refining or leave the builder while this continues.'),
                            completed,
                            total: total || 1
                        });

                        if (typeof options.onUpdate === 'function') {
                            options.onUpdate(queue);
                        }

                        if (queue.status === 'complete' || queue.status === 'failed' || queue.status === 'partial') {
                            this.finishProgressUi({
                                total,
                                completed: counts.complete || 0,
                                failed: (queue.items || []).filter((item) => item && item.status === 'failed').map((item) => ({ message: item.error || 'Media generation failed.' })),
                                generated: (queue.items || []).filter((item) => item && item.status === 'complete'),
                                complete: queue.status === 'complete'
                            }, options.source || 'builder');
                            this.stopProjectQueuePolling();
                            finish(queue);
                            return;
                        }

                        this.queuePollTimer = setTimeout(poll, 5000);
                    }).fail(() => {
                        this.queuePollTimer = setTimeout(poll, 9000);
                    });
                };

                poll();
            });
        },

        queueProjectMedia: async function(options = {}) {
            const targets = Array.isArray(options.targets) ? options.targets : this.collectPendingMediaTargets();
            if (!targets.length) {
                return {
                    queued: false,
                    status: 'complete',
                    counts: { total: 0, pending: 0, processing: 0, complete: 0, failed: 0 },
                    items: []
                };
            }

            let projectId = parseInt(options.projectId || options.postId || window.currentProjectId || 0, 10);
            if (!projectId && typeof window.vmoSaveProjectSnapshot === 'function') {
                const saved = await window.vmoSaveProjectSnapshot({
                    title: options.title || window.currentProjectTitle || 'Auto-saved Website',
                    html: options.html,
                    state: options.state || window.vmoState,
                    markSaved: false
                });
                projectId = saved && saved.projectId ? parseInt(saved.projectId, 10) : 0;
            }

            if (!projectId) {
                throw new Error('A saved project is required before queueing media.');
            }

            const manifest = this.buildQueueTargets(targets, options);

            const queueResponse = await new Promise((resolve, reject) => {
                $.post(getAjaxUrl(), {
                    action: 'vmo_queue_project_media',
                    security: getNonce(),
                    project_id: projectId,
                    targets: JSON.stringify(manifest)
                }, (response) => {
                    if (response && response.success && response.data) {
                        resolve(response.data);
                        return;
                    }
                    reject(new Error(response?.data?.message || 'Background media queue request failed.'));
                }).fail((xhr) => {
                    reject(new Error(xhr?.responseJSON?.data?.message || 'Background media queue request failed.'));
                });
            });

            const copy = buildRunCopy(options.source || 'generation', targets, { blocking: !!options.waitForCompletion });
            this.updateProgressUi({
                title: copy.title,
                detail: options.waitForCompletion
                    ? copy.detail
                    : `${copy.detail} VMO will keep rendering these assets even if you leave the builder.`,
                status: options.waitForCompletion ? 'Queued on the server and waiting for completion' : 'Queued on the server',
                completed: 0,
                total: manifest.length
            });

            const pollingPromise = this.startProjectQueuePolling(projectId, options);

            if (options.waitForCompletion) {
                const finalQueue = await pollingPromise;
                const terminalQueue = finalQueue || queueResponse;
                const terminalItems = Array.isArray(terminalQueue.items) ? terminalQueue.items : [];
                const counts = terminalQueue.counts || queueResponse.counts || { total: manifest.length, complete: 0, failed: 0 };

                return Object.assign({
                    queued: true,
                    projectId,
                    total: counts.total || manifest.length,
                    completed: counts.complete || 0,
                    generated: terminalItems.filter((item) => item && item.status === 'complete'),
                    failed: terminalItems.filter((item) => item && item.status === 'failed').map((item) => ({ message: item.error || 'Media generation failed.' })),
                    complete: terminalQueue.status === 'complete'
                }, queueResponse, terminalQueue);
            }

            return Object.assign({ queued: true, projectId }, queueResponse);
        },

        scanIframeForPlaceholders: function(options = {}) {
            const targets = this.collectPendingMediaTargets();
            console.log('[VMO Media] Pending media items:', targets.length);

            if (!targets.length) {
                return options.autoResolve
                    ? Promise.resolve({ complete: true, total: 0, completed: 0, failed: [], generated: [] })
                    : [];
            }

            if (options.autoResolve) {
                return this.resolvePendingMedia(Object.assign({}, options, { targets }));
            }

            this.promptUser(targets);
            return targets;
        },

        checkMissingMedia: function() {
            if (sessionStorage.getItem(MEDIA_DISMISS_KEY) || this.activeRun) return;

            const targets = this.collectPendingMediaTargets();
            if (!targets.length) return;

            if (typeof window.vmoIsShowcasePreviewActive === 'function' && window.vmoIsShowcasePreviewActive()) {
                this.resolvePendingMedia({ targets, source: 'showcase', requireComplete: true });
                return;
            }

            if (targets.length) {
                this.promptUser(targets);
            }
        },

        promptUser: function(missing) {
            if (sessionStorage.getItem(MEDIA_DISMISS_KEY) || !missing.length) return;
            $('#vmo-media-prompt').remove();

            const html = `
                <div id="vmo-media-prompt" class="vmo-modal vmo-show" style="z-index: 99999;">
                    <div class="vmo-modal-content" style="max-width: 540px;">
                        <div class="vmo-modal-header">
                            <h3>Media Still Required</h3>
                        </div>
                        <div class="vmo-modal-body">
                            <p>This website still has <strong>${missing.length}</strong> unresolved media asset${missing.length === 1 ? '' : 's'}.</p>
                            <p>The build is not complete until every required image and video is inserted.</p>

                            <div class="vmo-media-actions" style="display:flex;gap:10px;margin-top:20px;flex-direction:column;">
                                <button id="vmo-media-upload" class="vmo-btn-secondary">I’ll Upload My Own</button>
                                <button id="vmo-media-generate" class="vmo-btn-primary">AI Generate All Required Media</button>
                            </div>
                        </div>
                    </div>
                </div>
            `;

            $('body').append(html);

            $('#vmo-media-upload').on('click', () => {
                $('#vmo-media-prompt').remove();
                sessionStorage.setItem(MEDIA_DISMISS_KEY, 'true');
                alert('Use the replace controls on each media slot to upload your own assets.');
            });

            $('#vmo-media-generate').on('click', () => {
                $('#vmo-media-prompt').remove();
                this.resolvePendingMedia({ targets: missing, source: 'manual_prompt', requireComplete: true });
            });
        },

        ensureProgressUi: function() {
            if ($('#' + PROGRESS_ID).length) return $('#' + PROGRESS_ID);

            const ui = $(
                '<div id="' + PROGRESS_ID + '" style="position:fixed;right:24px;bottom:24px;z-index:100000;width:min(360px,calc(100vw - 32px));padding:18px;border-radius:18px;background:rgba(5,14,24,0.92);border:1px solid rgba(110,231,255,0.24);box-shadow:0 28px 90px rgba(0,0,0,0.32);backdrop-filter:blur(20px);color:#f8fafc;display:none;">'
                + '<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;">'
                + '<div><div id="' + PROGRESS_ID + '-title" style="font-size:12px;letter-spacing:0.18em;text-transform:uppercase;font-weight:700;color:#67e8f9;">Finishing Website Build</div>'
                + '<div id="' + PROGRESS_ID + '-detail" style="margin-top:8px;font-size:14px;line-height:1.5;color:#e2e8f0;">Preparing media pipeline…</div></div>'
                + '<div id="' + PROGRESS_ID + '-ratio" style="font-size:13px;font-weight:700;color:#f8fafc;white-space:nowrap;">0/0</div>'
                + '</div>'
                + '<div style="margin-top:14px;height:8px;border-radius:999px;background:rgba(148,163,184,0.18);overflow:hidden;">'
                + '<div id="' + PROGRESS_ID + '-fill" style="height:100%;width:0%;border-radius:999px;background:linear-gradient(90deg,#22d3ee,#60a5fa);"></div>'
                + '</div>'
                + '<div id="' + PROGRESS_ID + '-status" style="margin-top:10px;font-size:12px;color:#cbd5e1;">Waiting…</div>'
                + '</div>'
            );

            $('body').append(ui);
            return ui;
        },

        updateProgressUi: function(state) {
            const ui = this.ensureProgressUi();
            const total = Math.max(0, state.total || 0);
            const completed = Math.max(0, state.completed || 0);
            const percentage = total ? Math.max(4, Math.min(100, Math.round((completed / total) * 100))) : 100;

            ui.show();
            $('#' + PROGRESS_ID + '-title').text(state.title || 'Finishing Website Build');
            $('#' + PROGRESS_ID + '-detail').html(escapeHtml(state.detail || 'Generating required media…'));
            $('#' + PROGRESS_ID + '-ratio').text(total ? `${completed}/${total}` : '0/0');
            $('#' + PROGRESS_ID + '-fill').css('width', percentage + '%');
            $('#' + PROGRESS_ID + '-status').text(state.status || 'Working…');
        },

        finishProgressUi: function(result, source = 'builder') {
            if (!$('#' + PROGRESS_ID).length) return;

            const completionCopy = buildCompletionCopy(source, result);

            if (result.complete) {
                this.updateProgressUi({
                    title: completionCopy.title,
                    detail: completionCopy.detail,
                    status: completionCopy.status,
                    completed: result.completed,
                    total: result.total || result.completed || 1
                });
                setTimeout(() => $('#' + PROGRESS_ID).fadeOut(280), 2600);
                return;
            }

            this.updateProgressUi({
                title: completionCopy.title,
                detail: completionCopy.detail,
                status: completionCopy.status,
                completed: result.completed,
                total: result.total || result.completed || 1
            });
        },

        resolvePendingMedia: async function(options = {}) {
            if (this.activeRun) {
                return this.activeRun;
            }

            const targets = Array.isArray(options.targets) ? options.targets : this.collectPendingMediaTargets();
            const result = {
                total: targets.length,
                completed: 0,
                generated: [],
                failed: [],
                complete: targets.length === 0
            };

            if (!targets.length) {
                return result;
            }

            const source = options.source || 'builder';
            const onProgress = typeof options.onProgress === 'function' ? options.onProgress : null;
            const runCopy = buildRunCopy(source, targets, { blocking: !!options.requireComplete });

            this.updateProgressUi({
                title: runCopy.title,
                detail: runCopy.detail,
                status: runCopy.status,
                completed: 0,
                total: targets.length
            });

            const runPromise = (async () => {
                for (let index = 0; index < targets.length; index += 1) {
                    const target = targets[index];
                    const label = target.label || (target.type === 'video' ? 'Website video' : 'Website image');

                    this.updateProgressUi({
                        title: runCopy.title,
                        detail: `${target.type === 'video' ? 'Rendering cinematic video' : 'Generating image'} ${index + 1}/${targets.length} for ${label}.`,
                        status: target.type === 'video' ? 'Video rendering can take longer. The build is still in progress.' : 'Generating and inserting required media.',
                        completed: result.completed,
                        total: result.total
                    });

                    if (onProgress) {
                        onProgress({
                            total: result.total,
                            completed: result.completed,
                            target,
                            message: `${target.type === 'video' ? 'Rendering video' : 'Generating image'} for ${label}.`
                        });
                    }

                    try {
                        const url = target.type === 'video'
                            ? await this.generateVideo(target, index)
                            : await this.generateImage(target);

                        this.applyGeneratedMedia(target, url, target.type);
                        result.completed += 1;
                        result.generated.push({ mediaId: target.mediaId, type: target.type, url });

                        if (onProgress) {
                            onProgress({
                                total: result.total,
                                completed: result.completed,
                                target,
                                message: `${label} inserted.`
                            });
                        }
                    } catch (error) {
                        const message = error && error.message ? error.message : 'Media generation failed.';
                        result.failed.push({ target, message });

                        if (onProgress) {
                            onProgress({
                                total: result.total,
                                completed: result.completed,
                                target,
                                message,
                                failed: true
                            });
                        }
                    }
                }

                result.complete = result.failed.length === 0 && result.completed === result.total;

                if (result.generated.length) {
                    if (source === 'showcase') {
                        if (typeof window.vmoCaptureShowcasePreview === 'function') {
                            window.vmoCaptureShowcasePreview();
                        }
                    } else {
                        if (typeof window.renderBlocks === 'function') {
                            window.renderBlocks();
                        }
                        if (typeof window.autoSaveCheckpoint === 'function') {
                            window.autoSaveCheckpoint();
                        }
                    }
                }
                if (result.generated.length && typeof window.vmoInjectReplaceButtonsInIframe === 'function') {
                    window.vmoInjectReplaceButtonsInIframe();
                }

                this.finishProgressUi(result, source);
                return result;
            })().finally(() => {
                this.activeRun = null;
            });

            this.activeRun = runPromise;
            return runPromise;
        },

        generateImage: function(target) {
            return runQueuedMediaAssetJob(target, 'image', {
                model: 'openai-image-generation-2',
                onUpdate: (job) => this.updateProgressUi({
                    title: 'Rendering Website Image',
                    detail: job.stage_label || `Image generation in progress for ${target.label || 'website section'}.`,
                    status: job.estimated_remaining_text ? `Estimated remaining: ${job.estimated_remaining_text}` : (job.stage_label || 'Queued'),
                    completed: Math.max(0, Math.min(100, parseInt(job.progress || 0, 10) || 0)),
                    total: 100
                })
            });
        },

        generateVideo: function(target, index) {
            const source = String(target.source || '');
            const model = target.model || ((source === 'consultation_generation' || source === 'consultation_preview') ? 'sora-2-pro' : (index === 0 ? 'sora-2-pro' : 'sora-2'));
            const seconds = target.seconds || '4';
            const size = target.size || '1280x720';

            return runQueuedMediaAssetJob(target, 'video', {
                model,
                seconds,
                size,
                onUpdate: (job) => this.updateProgressUi({
                    title: 'Rendering Website Video',
                    detail: job.stage_label || `Video generation in progress for ${target.label || 'website section'}.`,
                    status: job.estimated_remaining_text ? `Estimated remaining: ${job.estimated_remaining_text}` : (job.stage_label || 'Queued'),
                    completed: Math.max(0, Math.min(100, parseInt(job.progress || 0, 10) || 0)),
                    total: 100
                })
            });
        },

        applyGeneratedMedia: function(target, url, type) {
            if (!target || !url) return;

            const iframeDoc = this.getIframeDocument();
            if (iframeDoc && target.mediaId) {
                const node = iframeDoc.querySelector('[data-media-id="' + target.mediaId + '"]');
                if (node) {
                    this.applyToDomNode(iframeDoc, node, url, type);
                }
            }

            if (window.vmoState && Array.isArray(window.vmoState.blocks)) {
                window.vmoState.blocks.forEach((block, blockIndex) => {
                    if (!block || !block.html) return;
                    if (typeof target.blockIndex === 'number' && target.blockIndex !== blockIndex && target.mediaId) {
                        if (block.html.indexOf(target.mediaId) === -1) return;
                    }

                    const $tmp = $('<div>').html(block.html);
                    const $matches = target.mediaId
                        ? $tmp.find('[data-media-id="' + target.mediaId + '"]')
                        : $tmp.find(type === 'video' ? '[data-vmo-sora], [data-type="video"], video' : '[data-vmo-generate], [data-type="image"], img').first();

                    if (!$matches.length) return;

                    $matches.each((_, node) => {
                        this.applyToJqueryNode($(node), url, type);
                    });

                    block.html = $tmp.html();
                });
            }
        },

        applyToDomNode: function(doc, node, url, type) {
            const tagName = node.tagName.toLowerCase();

            if (type === 'image') {
                if (tagName === 'img') {
                    node.setAttribute('src', url);
                    node.setAttribute('loading', 'lazy');
                    stripGenerationMarkersFromElement(node);
                    return;
                }

                stripGenerationMarkersFromElement(node);
                node.setAttribute('data-vmo-generated', 'image');
                node.style.backgroundImage = 'url("' + url + '")';
                node.style.backgroundSize = node.style.backgroundSize || 'cover';
                node.style.backgroundPosition = node.style.backgroundPosition || 'center';
                node.style.backgroundRepeat = 'no-repeat';
                node.innerHTML = '';
                return;
            }

            if (tagName === 'video') {
                node.setAttribute('src', url);
                node.setAttribute('autoplay', 'autoplay');
                node.setAttribute('muted', 'muted');
                node.setAttribute('loop', 'loop');
                node.setAttribute('playsinline', 'playsinline');
                stripGenerationMarkersFromElement(node);
                if (typeof node.load === 'function') {
                    node.load();
                }
                return;
            }

            const replacement = doc.createElement('video');
            replacement.setAttribute('src', url);
            replacement.setAttribute('autoplay', 'autoplay');
            replacement.setAttribute('muted', 'muted');
            replacement.setAttribute('loop', 'loop');
            replacement.setAttribute('playsinline', 'playsinline');
            replacement.setAttribute('data-media-id', node.getAttribute('data-media-id') || '');
            replacement.setAttribute('data-type', 'video');
            replacement.className = node.className || 'vmo-media-placeholder';
            replacement.style.cssText = (node.getAttribute('style') || '') + ';width:100%;height:100%;object-fit:cover;display:block;';
            replacement.innerHTML = '';
            node.replaceWith(replacement);
        },

        applyToJqueryNode: function($node, url, type) {
            const tagName = ($node.prop('tagName') || '').toLowerCase();

            $node.removeAttr('data-vmo-generate data-vmo-prompt data-vmo-sora data-vmo-sora-prompt');

            if (type === 'image') {
                if (tagName === 'img') {
                    $node.attr('src', url).attr('loading', 'lazy');
                    return;
                }

                $node.attr('data-vmo-generated', 'image');
                $node.css({
                    backgroundImage: 'url("' + url + '")',
                    backgroundSize: $node.css('background-size') || 'cover',
                    backgroundPosition: $node.css('background-position') || 'center',
                    backgroundRepeat: 'no-repeat'
                });
                $node.empty();
                return;
            }

            if (tagName === 'video') {
                $node.attr({
                    src: url,
                    autoplay: 'autoplay',
                    muted: 'muted',
                    loop: 'loop',
                    playsinline: 'playsinline'
                });
                return;
            }

            const replacement = $('<video>', {
                src: url,
                autoplay: 'autoplay',
                muted: 'muted',
                loop: 'loop',
                playsinline: 'playsinline',
                'data-media-id': $node.attr('data-media-id') || '',
                'data-type': 'video',
                class: $node.attr('class') || 'vmo-media-placeholder',
                style: (($node.attr('style') || '') + ';width:100%;height:100%;object-fit:cover;display:block;').trim()
            });

            $node.replaceWith(replacement);
        }
    };

    window.vmoMediaGenerator.init();

})(jQuery);