// TubePress admin — CSV/JSON upload tab of /admin/import.
// Static and cacheable for the same reason as import-catalogue.js. PHP-computed
// values arrive via the boot island in templates/admin/import/_uploads_js.php.
const UP_BOOT = JSON.parse(document.getElementById('tp-up-boot').textContent);
window.IMP = (function() {
    const CSRF = UP_BOOT.csrf;
    const BATCH_SIZE = 3; 

    const FIELDS = [
        { key: 'video_id', label: 'Video ID', info: 'Your own unique video identifier. Used for duplicate detection (skip or update).' },
        { key: 'title', label: 'Title', required: true, info: 'The video title. Required for every row.' },
        { key: 'slug', label: 'Slug URL', info: 'The URL-friendly slug for the video page.<br>If not provided, it will be <strong>automatically generated</strong> from the title.' },
        { key: 'description', label: 'Description', info: 'Video description. Plain text or HTML.' },
        { key: 'embed_code', label: 'Embed Code', info: 'For <strong>videos</strong>: an iframe embed code or a video URL.<br>Example: <code>&lt;iframe src="..."&gt;&lt;/iframe&gt;</code><br>Galleries use <strong>Images</strong> instead.' },
        { key: 'thumbnail', label: 'Thumbnail / Cover URL', info: 'URL to the thumbnail (videos) or cover (galleries). For a gallery it leads the image set.<br>If "Download thumbnails" is enabled, video thumbnails are saved locally.' },
        { key: 'images', label: 'Images (Album)', info: 'For <strong>Albums (galleries)</strong>: the image URLs, separated by comma, pipe or newline (or a JSON array of URLs). When this is mapped, the row is imported as an <strong>Album</strong> instead of a video.' },
        { key: 'media_type', label: 'Type (video / album)', info: 'Optional. A column containing <code>video</code> or <code>album</code> to force the kind. If empty, rows with <strong>Images</strong> and no <strong>Embed Code</strong> are auto-imported as Albums.' },
        { key: 'duration', label: 'Duration', info: 'Accepted formats:<br><code>1:23:45</code> (h:mm:ss)<br><code>12:34</code> (mm:ss)<br><code>754</code> (seconds)' },
        { key: 'published_at', label: 'Publish Date', info: 'When the video was published.<br>Accepted formats:<br><code>2025-01-31</code> or <code>2025-01-31 14:30:00</code><br><code>01/31/2025</code> or <code>31-01-2025</code><br><code>Jan 31, 2025</code><br>If empty, current date is used.' },
        { key: 'views', label: 'Views', info: 'Number of views. Must be a whole number.' },
        { key: 'likes', label: 'Likes', info: 'Number of likes. Must be a whole number.' },
        { key: 'categories', label: 'Categories', info: 'One or multiple categories, separated by comma, semicolon or pipe.<br>Example: <code>Category A, Category B</code>' },
        { key: 'tags', label: 'Tags', info: 'One or multiple tags, separated by comma, semicolon or pipe.<br>Example: <code>tag1, tag2, tag3</code>' },
        { key: 'performers', label: 'Pornstars', info: 'One or multiple pornstar names, separated by comma, semicolon or pipe.<br>Example: <code>Name A, Name B</code>' },
        { key: 'performer_bio', label: 'Pornstar Bio', info: 'Biography text for the pornstar.<br>Only used when a <strong>single</strong> pornstar is mapped per row.' },
        { key: 'performer_photo', label: 'Pornstar Photo', info: 'URL to the pornstar photo.<br>Only used when a single pornstar is mapped per row.' },
        { key: 'performer_country', label: 'Pornstar Country', info: 'Country code or name (e.g. <code>US</code>, <code>France</code>).<br>Only used when a single pornstar is mapped per row.' },
        { key: 'performer_birth_date', label: 'Pornstar DOB', info: 'Date of birth.<br>Accepted formats:<br><code>1995-03-15</code> or <code>03/15/1995</code><br><code>15-03-1995</code> or <code>Mar 15, 1995</code><br>Only used when a single pornstar is mapped per row.' },
        { key: 'performer_link', label: 'Pornstar Link', info: 'Website or social link URL for the pornstar.<br>Only used when a single pornstar is mapped per row.' },
        { key: 'channels', label: 'Channel', info: 'Channel name for this video.' },
        { key: 'channel_description', label: 'Channel Desc.', info: 'Description text for the channel.<br>Only used when a <strong>single</strong> channel is mapped per row.' },
        { key: 'channel_logo', label: 'Channel Logo', info: 'URL to the channel logo image. Always downloaded locally.<br>Only used when a single channel is mapped per row.' },
        { key: 'channel_website', label: 'Channel Website', info: 'Website URL for the channel.<br>Only used when a single channel is mapped per row.' },
    ];

    
    const IMP_LANGUAGES = UP_BOOT.languages;
    let IMP_API_CONNECTED = UP_BOOT.apiConnected;
    const IMP_AI_TRANSLATE_COST = UP_BOOT.aiTranslateCost;
    const IMP_AI_TRANSLATE_PROMPT = UP_BOOT.aiTranslatePrompt;
    const IMP_AI_TITLE_COST = UP_BOOT.aiTitleCost;
    const IMP_AI_TITLE_PROMPT = UP_BOOT.aiTitlePrompt;
    const IMP_AI_DESC_COST = UP_BOOT.aiDescCost;
    const IMP_AI_DESC_PROMPT = UP_BOOT.aiDescPrompt;
    const IMP_AI_TRANSLATE_DESC_COST = UP_BOOT.aiTranslateDescCost;
    const IMP_VIDEO_COST = UP_BOOT.videoCost;
    const IMP_AI_TRANSLATE_DESC_PROMPT = UP_BOOT.aiTranslateDescPrompt;
    IMP_LANGUAGES.forEach(function(lang) {
        FIELDS.push(
            { key: 'title_' + lang.code, label: 'Title (' + lang.code.toUpperCase() + ')', info: 'Translated title in <strong>' + lang.name + '</strong>.<br>Saved as a video translation for the <code>' + lang.code + '</code> locale.', isTranslation: true },
            { key: 'description_' + lang.code, label: 'Desc. (' + lang.code.toUpperCase() + ')', info: 'Translated description in <strong>' + lang.name + '</strong>.<br>Requires a translated title to be mapped.', isTranslation: true }
        );
    });

    const REQUIRED_FIELDS = FIELDS.filter(f => f.required).map(f => f.key);

    

    function normalizeField(str) {
        return str.toLowerCase().replace(/[\s_\-\.()*/+,;:'"!?#@&]+/g, '');
    }

    const AUTO_MAP_LOOKUP = (function() {
        const lookup = {};
        
        FIELDS.forEach(function(f) {
            var nk = normalizeField(f.key);
            if (!lookup[nk]) lookup[nk] = f.key;
            var nl = normalizeField(f.label);
            if (!lookup[nl]) lookup[nl] = f.key;
        });
        
        var aliases = {
            'embed':           'embed_code',
            'embedurl':        'embed_code',
            'iframe':          'embed_code',
            'embedsrc':        'embed_code',
            'videourl':        'embed_code',
            'playercode':      'embed_code',
            'thumb':           'thumbnail',
            'thumbnailurl':    'thumbnail',
            'thumburl':        'thumbnail',
            'poster':          'thumbnail',
            'image':           'thumbnail',
            'imageurl':        'thumbnail',
            'preview':         'thumbnail',
            'previewurl':      'thumbnail',
            'coverimage':      'thumbnail',
            'date':            'published_at',
            'publishdate':     'published_at',
            'published':       'published_at',
            'pubdate':         'published_at',
            'createdat':       'published_at',
            'dateadded':       'published_at',
            'addeddate':       'published_at',
            'uploaddate':      'published_at',
            'pornstar':        'performers',
            'pornstars':       'performers',
            'performer':       'performers',
            'actors':          'performers',
            'actor':           'performers',
            'models':          'performers',
            'model':           'performers',
            'stars':           'performers',
            'star':            'performers',
            'cast':            'performers',
            'channel':          'channels',
            'channels':         'channels',
            'channel':         'channels',
            'producer':        'channels',
            'category':        'categories',
            'cats':            'categories',
            'tag':             'tags',
            'keywords':        'tags',
            'keyword':         'tags',
            'id':              'video_id',
            'externalid':      'video_id',
            'videoid':         'video_id',
            'extid':           'video_id',
            'urlslug':         'slug',
            'slugurl':         'slug',
            'permalink':       'slug',
            'view':            'views',
            'viewcount':       'views',
            'nbviews':         'views',
            'like':            'likes',
            'likecount':       'likes',
            'nblikes':         'likes',
            'rating':          'likes',
            'desc':            'description',
            'length':          'duration',
            'time':            'duration',
            'runtime':         'duration',
            'gallery':         'images',
            'galleries':       'images',
            'photos':          'images',
            'pics':            'images',
            'pictures':        'images',
            'imageurls':       'images',
            'imagelist':       'images',
            'galleryimages':   'images',
            'photourls':       'images',
            'itemtype':        'media_type',
            'kind':            'media_type',
            'type':            'media_type',
        };
        for (var a in aliases) {
            if (!lookup[a]) lookup[a] = aliases[a];
        }
        return lookup;
    })();

    function autoMap(columns) {
        var mapping = {};
        var usedKeys = {};
        columns.forEach(function(col) {
            var norm = normalizeField(col);
            var fieldKey = AUTO_MAP_LOOKUP[norm];
            if (fieldKey && !usedKeys[fieldKey]) {
                mapping[fieldKey] = col;
                usedKeys[fieldKey] = true;
            }
        });
        return mapping;
    }

    function isReady(mapping) {
        
        return !!mapping.title && (!!mapping.embed_code || !!mapping.images);
    }

    
    let files = [];
    let idCounter = 0;
    

    function handleDrop(e) {
        e.preventDefault();
        e.currentTarget.classList.remove('border-primary-500', 'bg-primary-50');
        addFiles(e.dataTransfer.files);
    }

    function handleSelect(input) {
        addFiles(input.files);
        input.value = '';
    }

    function addFiles(fileList) {
        const file = Array.from(fileList).find(f => {
            const ext = f.name.split('.').pop().toLowerCase();
            return ['csv', 'json'].includes(ext);
        });
        if (!file) {
            showUploadError('Please select a CSV or JSON file.');
            return;
        }
        hideUploadError();
        uploadFile(file);
    }

    function uploadFile(file) {
        const id = ++idCounter;
        const entry = {
            id, originalName: file.name, filename: null, columns: [], preview: [],
            totalRows: 0, mapping: {}, status: 'uploading', stats: null, error: null
        };
        files.push(entry);
        showQueue();
        renderCard(entry);

        const fd = new FormData();
        fd.append('file', file);
        fd.append('csrf_token', CSRF);

        fetch('/admin/import/parse', {
            method: 'POST', headers: { 'X-CSRF-TOKEN': CSRF }, body: fd, credentials: 'same-origin'
        })
        .then(r => {
            if (r.redirected || r.status === 401 || r.status === 403) {
                throw new Error('Your session has expired. Please refresh the page and log in again.');
            }
            return r.json().catch(() => {
                throw new Error('Server returned an invalid response (HTTP ' + r.status + '). Check server logs or try again.');
            });
        })
        .then(data => {
            if (data.error) { entry.status = 'error'; entry.error = data.error; }
            else {
                entry.filename = data.filename;
                entry.columns = data.columns;
                entry.preview = data.preview;
                entry.totalRows = data.totalRows;
                entry.mapping = autoMap(data.columns);
                entry._autoMapCount = Object.keys(entry.mapping).length;
                entry.status = 'mapping';
            }
            renderCard(entry);
            updateGlobal();
        })
        .catch(err => {
            entry.status = 'error';
            entry.error = err.message || 'Upload failed. Please check your connection and try again.';
            renderCard(entry);
            updateGlobal();
        });
    }

    

    function showQueue() {
        document.getElementById('importLanding').classList.add('hidden');
        document.getElementById('importQueue').classList.remove('hidden');
    }

    function renderCard(entry) {
        let card = document.getElementById('card-' + entry.id);
        if (!card) {
            card = document.createElement('div');
            card.id = 'card-' + entry.id;
            card.className = 'bg-white rounded-lg border border-gray-200 overflow-hidden transition-all';
            document.getElementById('fileCards').appendChild(card);
        }

        const statusBadge = {
            uploading: '<span class="px-2 py-0.5 text-xs rounded-full bg-blue-50 text-blue-600">Uploading...</span>',
            mapping:   '<span class="px-2 py-0.5 text-xs rounded-full bg-amber-50 text-amber-600">Step 2 — Map fields</span>',
            ready:     '<span class="px-2 py-0.5 text-xs rounded-full bg-green-50 text-green-600">Step 3 — Ready to import</span>',
            testing:   '<span class="px-2 py-0.5 text-xs rounded-full bg-purple-50 text-purple-600">Testing...</span>',
            tested:    '<span class="px-2 py-0.5 text-xs rounded-full bg-purple-50 text-purple-700">Test complete</span>',
            importing: '<span class="px-2 py-0.5 text-xs rounded-full bg-primary-50 text-primary-600">Importing...</span>',
            submitted: '<span class="px-2 py-0.5 text-xs rounded-full bg-blue-50 text-blue-600">Submitted to server</span>',
            done:      '<span class="px-2 py-0.5 text-xs rounded-full bg-green-50 text-green-700">Done</span>',
            error:     '<span class="px-2 py-0.5 text-xs rounded-full bg-red-50 text-red-600">Error</span>',
        }[entry.status] || '';

        const canRemove = entry.status !== 'importing' && entry.status !== 'submitted';
        const ext = entry.originalName.split('.').pop().toUpperCase();

        let headerHtml =
            '<div class="px-4 py-3 flex items-center gap-3">' +
                '<div class="w-9 h-9 rounded-lg bg-gray-100 flex items-center justify-center shrink-0">' +
                    '<span class="text-xs font-bold text-gray-400">' + esc(ext) + '</span>' +
                '</div>' +
                '<div class="flex-1 min-w-0">' +
                    '<div class="flex items-center gap-2">' +
                        '<span class="text-sm font-medium text-gray-800 truncate">' + esc(entry.originalName) + '</span>' +
                        statusBadge +
                    '</div>' +
                    (entry.totalRows ? '<span class="text-xs text-gray-400">' + entry.totalRows.toLocaleString() + ' rows &middot; ' + entry.columns.length + ' columns</span>' : '') +
                '</div>' +
                (canRemove ? '<button type="button" onclick="event.stopPropagation();IMP.removeFile(' + entry.id + ')" class="p-1 text-gray-300 hover:text-red-500 transition-colors shrink-0"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18L18 6M6 6l12 12"/></svg></button>' : '') +
            '</div>';

        let bodyHtml = '';

        if (entry.status === 'error' && entry.error) {
            bodyHtml += '<div class="px-4 pb-3"><div class="bg-red-50 text-red-600 text-xs rounded-lg px-3 py-2">' + esc(entry.error) + '</div></div>';
        }

        
        if (!['uploading', 'error', 'done', 'submitted'].includes(entry.status)) {
            const step = ['mapping'].includes(entry.status) ? 2 : 3;
            bodyHtml += '<div class="px-4 py-3 border-t border-gray-100 bg-gray-50/50">' +
                renderStepper(step, entry.id) +
            '</div>';
        }

        if (['importing', 'testing'].includes(entry.status) && entry.stats) {
            const pct = Math.round((entry.stats.processed / entry.stats.total) * 100);
            bodyHtml +=
                '<div class="px-4 pb-3 pt-2">' +
                    '<div class="w-full bg-gray-100 rounded-full h-2 mb-2 overflow-hidden">' +
                        '<div class="bg-primary-500 h-2 rounded-full transition-all" style="width:' + pct + '%"></div>' +
                    '</div>' +
                    '<div class="flex justify-between text-xs text-gray-500">' +
                        '<span>' + entry.stats.processed.toLocaleString() + ' / ' + entry.stats.total.toLocaleString() + ' rows</span>' +
                        '<span>' + pct + '%</span>' +
                    '</div>' +
                '</div>';
        }

        if (entry.status === 'tested' && entry.stats) {
            const s = entry.stats;
            bodyHtml +=
                '<div class="px-4 pb-3 pt-2">' +
                    '<div class="bg-purple-50 border border-purple-200 rounded-lg p-3 mb-3">' +
                        '<div class="text-sm font-medium text-purple-800 mb-1">Test import completed</div>' +
                        '<div class="flex gap-4 text-xs">' +
                            '<span class="text-green-600"><strong>' + s.imported.toLocaleString() + '</strong> imported</span>' +
                            (s.skipped > 0 ? '<span class="text-amber-600"><strong>' + s.skipped.toLocaleString() + '</strong> skipped (duplicates)</span>' : '') +
                            (s.errors > 0 ? '<span class="text-red-600"><strong>' + s.errors.toLocaleString() + '</strong> errors</span>' : '') +
                        '</div>';
            
            if (entry.testErrorDetails && entry.testErrorDetails.length > 0) {
                bodyHtml +=
                        '<div class="mt-2 bg-red-50 border border-red-200 rounded-lg px-3 py-2">' +
                            '<div class="text-xs font-medium text-red-700 mb-1">Error details:</div>' +
                            '<div class="text-xs text-red-600 space-y-0.5 font-mono">';
                entry.testErrorDetails.forEach(function(detail) {
                    bodyHtml += '<div class="truncate">' + esc(detail) + '</div>';
                });
                bodyHtml += '</div></div>';
            }
            bodyHtml +=
                        '<p class="text-xs text-purple-600 mt-2">Check the imported videos in your admin panel. If everything looks good, import the rest.</p>' +
                    '</div>' +
                    '<div id="imp-credit-warn-' + entry.id + '" class="hidden mb-2"></div>' +
                    '<div class="flex items-center gap-2">' +
                        '<button type="button" onclick="IMP.importRemaining(' + entry.id + ')" class="inline-flex items-center gap-1.5 px-4 py-2 bg-primary-600 text-white text-xs font-medium rounded-lg hover:bg-primary-700 transition-colors">' +
                            '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/></svg>' +
                            'Import remaining ' + (entry.totalRows - (entry.testCount || 10)).toLocaleString() + ' rows' +
                        '</button>' +
                        '<button type="button" onclick="IMP.cancelTest(' + entry.id + ')" class="px-3 py-2 text-xs text-gray-500 hover:text-red-600 transition-colors">Cancel & delete test videos</button>' +
                    '</div>' +
                '</div>';
        }

        if (entry.status === 'done' && entry.stats) {
            const s = entry.stats;
            bodyHtml +=
                '<div class="px-4 pb-3 flex gap-4 text-xs">' +
                    '<span class="text-green-600"><strong>' + s.imported.toLocaleString() + '</strong> imported</span>' +
                    (s.skipped > 0 ? '<span class="text-amber-600"><strong>' + s.skipped.toLocaleString() + '</strong> skipped</span>' : '') +
                    (s.errors > 0 ? '<span class="text-red-600"><strong>' + s.errors.toLocaleString() + '</strong> errors</span>' : '') +
                '</div>';
        }

        if (entry.status === 'done' && entry._jobId) {
            bodyHtml +=
                '<div class="px-4 pb-3"><div class="bg-green-50 border border-green-200 text-green-700 text-xs rounded-lg px-3 py-2 flex items-center justify-between gap-3">' +
                    '<div class="flex items-center gap-2">' +
                        '<svg class="w-4 h-4 animate-spin shrink-0 text-green-600" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>' +
                        '<span>Import job #' + entry._jobId + ' is running in the background.</span>' +
                    '</div>' +
                    '<a href="/admin/import/downloads" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold text-white bg-green-600 hover:bg-green-700 rounded-lg transition-colors no-underline shrink-0">' +
                        '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg>' +
                        'View Download Queue' +
                    '</a>' +
                '</div></div>';
        }

        if (entry.status === 'submitted') {
            bodyHtml +=
                '<div class="px-4 pb-3"><div class="bg-blue-50 border border-blue-200 text-blue-700 text-xs rounded-lg px-3 py-2 flex items-center gap-2">' +
                    '<svg class="w-4 h-4 animate-spin shrink-0" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>' +
                    'Submitting to server...' +
                '</div></div>';
        }

        
        if (entry.status === 'mapping') {
            bodyHtml += renderMappingPanel(entry);
            
            const canContinue = isReady(entry.mapping);
            const mappedCount = Object.keys(entry.mapping).length;
            const _miss = [];
            if (!entry.mapping.title) _miss.push('Title');
            if (!entry.mapping.embed_code && !entry.mapping.images) _miss.push('Embed Code or Images');
            const missingReq = _miss.join(', ');
            bodyHtml += '<div class="px-4 py-4 border-t border-gray-100 bg-gray-50/50">' +
                '<div class="flex items-center justify-between">' +
                    '<div>' +
                        '<span class="text-sm font-semibold ' + (canContinue ? 'text-green-600' : 'text-gray-500') + '">' + mappedCount + ' of ' + FIELDS.length + ' fields mapped</span>' +
                        (!canContinue && missingReq ? '<p class="text-[11px] text-red-400 mt-0.5">Missing required: ' + esc(missingReq) + '</p>' : '') +
                        (canContinue ? '<p class="text-[11px] text-green-500 mt-0.5">All required fields mapped - ready to continue</p>' : '') +
                    '</div>' +
                    '<button type="button" onclick="IMP.toStep3(' + entry.id + ')" ' + (canContinue ? '' : 'disabled ') +
                        'class="inline-flex items-center gap-2 px-6 py-2.5 bg-primary-600 text-white text-sm font-bold rounded-xl hover:bg-primary-700 transition-colors shadow-md shadow-primary-600/20 disabled:opacity-40 disabled:cursor-not-allowed disabled:shadow-none">' +
                        'Continue' +
                        '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>' +
                    '</button>' +
                '</div>' +
            '</div>';
        }

        
        if (entry.status === 'ready') {
            bodyHtml += renderStep3(entry);
        }

        card.innerHTML = headerHtml + bodyHtml;
    }

    function renderMappingPanel(entry) {
        const autoCount = entry._autoMapCount || 0;
        let html = '<div class="border-t border-gray-100">';

        
        if (autoCount > 0) {
            html += '<div class="px-4 py-2.5 bg-green-50 border-b border-green-200 flex items-center gap-2">' +
                '<svg class="w-4 h-4 text-green-500 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>' +
                '<span class="text-sm text-green-700"><strong>' + autoCount + ' field' + (autoCount > 1 ? 's' : '') + '</strong> auto-matched from your column names. Review and adjust below.</span>' +
            '</div>';
        } else {
            html += '<div class="px-4 py-2.5 bg-blue-50 border-b border-blue-200 flex items-center gap-2">' +
                '<svg class="w-4 h-4 text-blue-500 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"/></svg>' +
                '<span class="text-sm text-blue-700">Drag source columns from the right panel and drop them onto the target fields on the left.</span>' +
            '</div>';
        }

        html += '<div class="grid grid-cols-1 lg:grid-cols-12 gap-0 divide-y lg:divide-y-0 lg:divide-x divide-gray-100">';

        
        html += '<div class="lg:col-span-5 p-4 overflow-y-auto" style="max-height:520px">';
        html += '<div class="flex items-center gap-2 mb-3">' +
            '<div class="text-xs font-semibold text-gray-500 uppercase tracking-wide">Video Fields</div>' +
            '<div class="flex items-center gap-1 text-[10px] text-gray-400 bg-gray-100 px-2 py-0.5 rounded-full">' +
                '<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"/></svg>' +
                'Drag columns here' +
            '</div>' +
        '</div>';
        const performerKeys = ['performer_bio','performer_photo','performer_country','performer_birth_date','performer_link'];
        const channelKeys = ['channel_description','channel_logo','channel_website'];
        let showedPerformerHeader = false;
        let showedChannelHeader = false;
        let showedTranslationHeader = false;
        FIELDS.forEach(f => {
            if (performerKeys.includes(f.key) && !showedPerformerHeader) {
                showedPerformerHeader = true;
                html += '<div class="border-t border-pink-200 mt-4 mb-2 pt-3 flex items-center gap-2">' +
                    '<svg class="w-3.5 h-3.5 text-pink-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0"/></svg>' +
                    '<div class="text-xs font-semibold text-pink-500 uppercase tracking-wide">Pornstar Fields</div>' +
                '</div>';
            }
            if (channelKeys.includes(f.key) && !showedChannelHeader) {
                showedChannelHeader = true;
                html += '<div class="border-t border-indigo-200 mt-4 mb-2 pt-3 flex items-center gap-2">' +
                    '<svg class="w-3.5 h-3.5 text-indigo-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15"/></svg>' +
                    '<div class="text-xs font-semibold text-indigo-500 uppercase tracking-wide">Channel Fields</div>' +
                '</div>';
            }
            
            if (f.isTranslation && f.key.startsWith('description_') && !entry.mapping['description']) {
                return;
            }
            if (f.isTranslation && !showedTranslationHeader) {
                showedTranslationHeader = true;
                const visibleTransCount = IMP_LANGUAGES.length;
                html += '<div class="border-t border-sky-200 mt-4 mb-2 pt-3 flex items-center gap-2">' +
                    '<svg class="w-3.5 h-3.5 text-sky-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3"/></svg>' +
                    '<div class="text-xs font-semibold text-sky-500 uppercase tracking-wide">Translations</div>' +
                    '<span class="text-[10px] text-gray-400">(' + visibleTransCount + ' languages)</span>' +
                '</div>';
            }
            const mapped = entry.mapping[f.key];
            html +=
                '<div class="target-zone flex items-center gap-2 px-2.5 py-2 rounded-lg transition-all text-sm mb-1 border ' +
                    (mapped ? 'border-primary-200 bg-primary-50/40' : 'border-dashed border-gray-200 hover:border-gray-300') + '" data-file="' + entry.id + '" data-field="' + f.key + '"' +
                ' ondragover="event.preventDefault();this.classList.add(\'ring-2\',\'ring-primary-300\',\'bg-primary-50/30\')"' +
                ' ondragleave="this.classList.remove(\'ring-2\',\'ring-primary-300\',\'bg-primary-50/30\')"' +
                ' ondrop="event.preventDefault();this.classList.remove(\'ring-2\',\'ring-primary-300\',\'bg-primary-50/30\');IMP.dropMap(' + entry.id + ',\'' + f.key + '\',event)"' +
                ' onclick="IMP.clickTarget(' + entry.id + ',\'' + f.key + '\')">' +
                    '<span class="text-gray-600 shrink-0 text-xs flex items-center gap-1 font-medium" style="min-width:8rem">' +
                        esc(f.label) + (f.required ? ' <span class="text-red-500 font-bold">*</span>' : '') +
                        (f.info ? '<span class="info-tip relative" onclick="event.stopPropagation();IMP.showFieldInfo(this,\'' + f.key + '\')">' +
                            '<svg class="w-3 h-3 text-gray-300 hover:text-primary-500 cursor-help" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>' +
                        '</span>' : '') +
                    '</span>';
            if (mapped) {
                html += '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 bg-primary-100 text-primary-700 rounded-md text-xs font-medium border border-primary-200">' +
                    '<svg class="w-3 h-3 text-primary-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-2.54a4.5 4.5 0 00-1.242-7.244l-4.5-4.5a4.5 4.5 0 00-6.364 6.364L4.343 8.28"/></svg>' +
                    esc(mapped) +
                    '<button type="button" onclick="event.stopPropagation();IMP.unmap(' + entry.id + ',\'' + f.key + '\')" class="text-primary-400 hover:text-primary-700 ml-0.5">&times;</button></span>';
            } else {
                html += '<span class="flex-1 border border-dashed border-gray-200 rounded-md px-2.5 py-1 text-[10px] text-gray-300 text-center cursor-default">drop here</span>';
            }
            html += '</div>';
        });
        html += '</div>';

        
        html += '<div class="lg:col-span-7 p-4 flex flex-col overflow-hidden bg-gray-50/30">';
        html += '<div class="flex items-center gap-2 mb-3">' +
            '<div class="text-xs font-semibold text-gray-500 uppercase tracking-wide">Source Columns</div>' +
            '<span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[10px] font-medium bg-gray-200 text-gray-500">' + entry.columns.length + '</span>' +
            '<div class="flex items-center gap-1 text-[10px] text-gray-400 bg-white px-2 py-0.5 rounded-full border border-gray-100">' +
                'Grab & drag' +
                '<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>' +
            '</div>' +
        '</div>';
        html += '<div class="space-y-1 overflow-y-auto flex-1 min-h-0 pr-1" style="max-height:520px">';
        entry.columns.forEach(col => {
            const isMapped = Object.values(entry.mapping).includes(col);
            const sampleVal = entry.preview.length > 0 ? String(entry.preview[0][col] ?? '') : '';
            const sampleTrunc = sampleVal.length > 40 ? sampleVal.substring(0, 40) + '...' : sampleVal;
            html +=
                '<div class="source-chip group flex items-center gap-2 px-3 py-2 rounded-lg text-xs transition-all ' +
                    (isMapped ? 'opacity-50 bg-gray-50 border border-gray-100' : 'bg-white border border-gray-200 shadow-sm hover:shadow-md hover:border-primary-300 cursor-grab active:cursor-grabbing') + '"' +
                ' draggable="' + (isMapped ? 'false' : 'true') + '"' +
                ' ondragstart="event.dataTransfer.setData(\'text/plain\',\'' + escAttr(col) + '\');event.dataTransfer.effectAllowed=\'move\';this.style.opacity=\'0.5\'"' +
                ' ondragend="this.style.opacity=\'\'"' +
                ' onclick="IMP.clickSource(' + entry.id + ',\'' + escAttr(col) + '\')">' +
                    '<svg class="w-3.5 h-3.5 text-gray-300 shrink-0 ' + (isMapped ? '' : 'group-hover:text-gray-400') + '" fill="currentColor" viewBox="0 0 20 20"><circle cx="7" cy="4" r="1.5"/><circle cx="13" cy="4" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="16" r="1.5"/><circle cx="13" cy="16" r="1.5"/></svg>' +
                    '<div class="flex-1 min-w-0">' +
                        '<span class="font-medium text-gray-700 truncate block">' + esc(col) + '</span>' +
                        (sampleTrunc ? '<span class="text-[10px] text-gray-400 truncate block">' + esc(sampleTrunc) + '</span>' : '') +
                    '</div>' +
                    (isMapped ? '<span class="ml-auto inline-flex items-center gap-1 text-green-500 text-[10px] shrink-0"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg> mapped</span>' : '') +
                    (!isMapped ? '<button type="button" class="ml-auto opacity-0 group-hover:opacity-100 text-gray-400 hover:text-primary-600 p-0.5 shrink-0" onclick="event.stopPropagation();IMP.previewCol(' + entry.id + ',\'' + escAttr(col) + '\',this)" title="Preview values"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>' : '') +
                '</div>';
        });
        html += '</div></div>';

        html += '</div></div>';
        return html;
    }

    function renderStepper(currentStep, entryId) {
        var steps = [
            { num: 1, label: 'Upload' },
            { num: 2, label: 'Map Fields' },
            { num: 3, label: 'Configure & Import' }
        ];
        var html = '<div class="flex items-center w-full">';
        steps.forEach(function(s, idx) {
            var isCompleted = s.num < currentStep;
            var isActive = s.num === currentStep;
            var isPending = s.num > currentStep;
            var clickable = (s.num === 2 && currentStep === 3);
            
            html += '<div class="flex items-center gap-2' + (clickable ? ' cursor-pointer' : '') + '"' +
                (clickable ? ' onclick="IMP.backToMapping(' + entryId + ')" title="Go back to mapping"' : '') + '>';
            if (isCompleted) {
                html += '<span class="w-7 h-7 rounded-full flex items-center justify-center bg-green-500 text-white shadow-sm">' +
                    '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>' +
                '</span>';
            } else if (isActive) {
                html += '<span class="w-7 h-7 rounded-full flex items-center justify-center bg-primary-600 text-white text-xs font-bold ring-4 ring-primary-100 shadow-sm">' + s.num + '</span>';
            } else {
                html += '<span class="w-7 h-7 rounded-full flex items-center justify-center bg-gray-200 text-gray-400 text-xs font-bold">' + s.num + '</span>';
            }
            
            var labelCls = isCompleted ? 'text-green-700 font-medium' : (isActive ? 'text-primary-700 font-semibold' : 'text-gray-400');
            html += '<span class="text-xs ' + labelCls + ' hidden sm:inline">' + s.label + '</span>';
            html += '</div>';
            
            if (idx < steps.length - 1) {
                var lineCls = (s.num < currentStep) ? 'bg-green-300' : 'bg-gray-200';
                html += '<div class="flex-1 h-0.5 mx-2 rounded ' + lineCls + '"></div>';
            }
        });
        html += '</div>';
        return html;
    }

    function renderStep3(entry) {
        const mappedCount = Object.keys(entry.mapping).length;
        let html = '<div class="border-t border-gray-100 px-4 py-5 space-y-5">';

        
        html += '<div class="bg-gray-50 rounded-xl border border-gray-200 p-4">' +
            '<div class="flex items-center justify-between mb-3">' +
                '<div class="flex items-center gap-2.5">' +
                    '<svg class="w-4.5 h-4.5 text-gray-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"/></svg>' +
                    '<span class="text-sm font-semibold text-gray-700">Field Mapping</span>' +
                    '<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-semibold bg-primary-100 text-primary-700">' + mappedCount + '/' + FIELDS.length + '</span>' +
                '</div>' +
                '<button type="button" onclick="IMP.backToMapping(' + entry.id + ')" class="inline-flex items-center gap-1 text-xs font-semibold text-primary-600 hover:text-primary-700 bg-white border border-gray-200 hover:border-primary-200 px-2.5 py-1 rounded-lg transition-colors">' +
                    '<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"/></svg>' +
                    'Edit' +
                '</button>' +
            '</div>' +
            '<div class="flex flex-wrap gap-1.5">';
        FIELDS.forEach(f => {
            if (entry.mapping[f.key]) {
                html += '<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-white border border-gray-200 text-gray-700 rounded-lg text-[11px] shadow-sm">' +
                    '<span class="text-gray-400 font-medium">' + esc(f.label) + '</span>' +
                    '<svg class="w-2.5 h-2.5 text-gray-300" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/></svg>' +
                    '<span class="font-medium">' + esc(entry.mapping[f.key]) + '</span>' +
                '</span>';
            }
        });
        html += '</div></div>';

        
        html += '<div>' +
            '<div class="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-3">Import Settings</div>' +
            '<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">' +
                
                '<div class="bg-white border border-gray-200 rounded-xl p-4">' +
                    '<label class="flex items-start gap-3 cursor-pointer">' +
                        '<input type="checkbox" data-opt-cache="' + entry.id + '" ' + (entry._optCache ? 'checked ' : '') + 'class="mt-0.5 rounded border-gray-300 text-primary-600 focus:ring-primary-500">' +
                        '<div>' +
                            '<span class="text-sm font-semibold text-gray-700 block">Download thumbnails</span>' +
                            '<p class="text-xs text-gray-400 mt-1 leading-relaxed">Save remote thumbnail images to your local server for faster loading.</p>' +
                        '</div>' +
                    '</label>' +
                '</div>' +
                
                '<div class="bg-white border border-gray-200 rounded-xl p-4">' +
                    '<span class="text-sm font-semibold text-gray-700 block mb-2">Duplicate Video ID</span>' +
                    '<p class="text-xs text-gray-400 mb-3 leading-relaxed">What to do when a video ID already exists.</p>' +
                    '<div class="flex gap-3">' +
                        '<label class="flex items-center gap-2 cursor-pointer"><input type="radio" name="dup_' + entry.id + '" value="skip" ' + ((entry._dupMode || 'skip') === 'skip' ? 'checked ' : '') + 'class="border-gray-300 text-primary-600 focus:ring-primary-500"><span class="text-sm text-gray-600">Skip</span></label>' +
                        '<label class="flex items-center gap-2 cursor-pointer"><input type="radio" name="dup_' + entry.id + '" value="update" ' + ((entry._dupMode) === 'update' ? 'checked ' : '') + 'class="border-gray-300 text-primary-600 focus:ring-primary-500"><span class="text-sm text-gray-600">Update</span></label>' +
                    '</div>' +
                '</div>' +
                
                '<div class="bg-white border border-gray-200 rounded-xl p-4">' +
                    '<span class="text-sm font-semibold text-gray-700 block mb-2">Import Speed</span>' +
                    '<p class="text-xs text-gray-400 mb-3 leading-relaxed">Videos per batch. Lower values use less server resources.</p>' +
                    '<select data-opt-speed="' + entry.id + '" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none">' +
                        '<option value="1"' + ((entry._importSpeed || 3) == 1 ? ' selected' : '') + '>1 — Slowest (safest)</option>' +
                        '<option value="2"' + ((entry._importSpeed || 3) == 2 ? ' selected' : '') + '>2 — Slow</option>' +
                        '<option value="3"' + ((entry._importSpeed || 3) == 3 ? ' selected' : '') + '>3 — Normal (recommended)</option>' +
                        '<option value="4"' + ((entry._importSpeed || 3) == 4 ? ' selected' : '') + '>4 — Fast</option>' +
                        '<option value="5"' + ((entry._importSpeed || 3) == 5 ? ' selected' : '') + '>5 — Faster</option>' +
                        '<option value="6"' + ((entry._importSpeed || 3) == 6 ? ' selected' : '') + '>6 — Maximum</option>' +
                    '</select>' +
                '</div>' +
            '</div>' +
        '</div>';

        
        {
            const aiRemainingLangs = IMP_LANGUAGES.filter(function(l) {
                return !entry.mapping['title_' + l.code];
            });
            const hasDesc = !!entry.mapping['description'];

            
            html += '<div class="border border-purple-200 rounded-xl overflow-hidden">' +
                '<div class="bg-gradient-to-r from-purple-50 to-purple-100/50 px-4 py-3 border-b border-purple-200">' +
                    '<div class="flex items-center gap-2.5">' +
                        '<div class="w-7 h-7 rounded-lg bg-white/80 border border-purple-200 flex items-center justify-center">' +
                            '<svg class="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"/></svg>' +
                        '</div>' +
                        '<div>' +
                            '<span class="text-sm font-semibold text-gray-800">AI Processing</span>' +
                            '<p class="text-[11px] text-purple-600">Enhance content with AI during import</p>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
                '<div class="p-4">';

            if (!IMP_API_CONNECTED) {
                html += '<p class="text-sm text-gray-600 mb-1">Rewrite titles, translate, and rewrite descriptions using AI.</p>' +
                    '<p class="text-xs text-gray-400 mb-3">Connect your TubePress API to enable AI-powered processing during import.</p>' +
                    '<button type="button" onclick="CAT.autoConnect()" class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-purple-500 hover:bg-purple-600 rounded-lg transition-colors shadow-sm">' +
                        '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-2.54a4.5 4.5 0 00-1.242-7.244l-4.5-4.5a4.5 4.5 0 00-6.364 6.364L4.343 8.28"/></svg>' +
                        'Connect API' +
                    '</button>';
            } else {
                html += '<div class="divide-y divide-gray-100 rounded-xl border border-gray-200 overflow-hidden">';

                
                var aiTitleChecked = entry._aiRewriteTitle ? 'checked ' : '';
                html += '<div class="transition-all duration-200">' +
                    '<label class="flex items-center gap-3 px-4 py-3.5 cursor-pointer hover:bg-gray-50/80 transition-colors">' +
                        '<input type="checkbox" data-opt-ai-title="' + entry.id + '" ' + aiTitleChecked + 'onchange="IMP.toggleAiCard(this)" class="w-4 h-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">' +
                        '<div class="w-8 h-8 rounded-lg bg-indigo-50 flex items-center justify-center shrink-0">' +
                            '<svg class="w-4 h-4 text-indigo-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"/></svg>' +
                        '</div>' +
                        '<div class="flex-1 min-w-0">' +
                            '<div class="text-sm font-semibold text-gray-900">Rewrite Title</div>' +
                            '<div class="text-[11px] text-gray-400">Unique, SEO-friendly title</div>' +
                        '</div>' +
                        '<span class="shrink-0 text-[11px] font-bold text-indigo-600 bg-indigo-50 px-2.5 py-1 rounded-lg">+' + IMP_AI_TITLE_COST.toFixed(2) + ' Cr</span>' +
                    '</label>' +
                    '<div class="border-t border-gray-100" style="max-height:0;overflow:hidden;transition:all .2s">' +
                        '<div class="px-4 py-3 bg-gray-50/50">' +
                            '<label class="block text-[10px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Prompt</label>' +
                            '<textarea data-opt-ai-title-prompt="' + entry.id + '" rows="2" maxlength="500" class="w-full text-xs px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-400 resize-none font-mono leading-relaxed transition-shadow" placeholder="Custom prompt...">' + esc(entry._aiTitlePrompt || IMP_AI_TITLE_PROMPT || '') + '</textarea>' +
                        '</div>' +
                    '</div>' +
                '</div>';

                
                if (aiRemainingLangs.length > 0) {
                    var aiTransChecked = entry._aiTranslate ? 'checked ' : '';
                    var langList = aiRemainingLangs.map(function(l) { return l.code.toUpperCase(); }).join(', ');
                    html += '<div class="transition-all duration-200">' +
                        '<label class="flex items-center gap-3 px-4 py-3.5 cursor-pointer hover:bg-gray-50/80 transition-colors">' +
                            '<input type="checkbox" data-opt-ai="' + entry.id + '" ' + aiTransChecked + 'onchange="IMP.toggleAiCard(this)" class="w-4 h-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500">' +
                            '<div class="w-8 h-8 rounded-lg bg-sky-50 flex items-center justify-center shrink-0">' +
                                '<svg class="w-4 h-4 text-sky-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"/></svg>' +
                            '</div>' +
                            '<div class="flex-1 min-w-0">' +
                                '<div class="text-sm font-semibold text-gray-900">Translate Title</div>' +
                                '<div class="text-[11px] text-gray-400">' + aiRemainingLangs.length + ' language' + (aiRemainingLangs.length > 1 ? 's' : '') + ': ' + langList + '</div>' +
                            '</div>' +
                            '<span class="shrink-0 text-[11px] font-bold text-sky-600 bg-sky-50 px-2.5 py-1 rounded-lg">+' + IMP_AI_TRANSLATE_COST.toFixed(2) + ' Cr</span>' +
                        '</label>' +
                        
                        '<div class="imp-ai-rewrite-notice-' + entry.id + ' ' + (aiTitleChecked && aiTransChecked ? '' : 'hidden ') + 'flex items-center gap-2 mx-4 mb-2 mt-1 px-3 py-2 bg-sky-50 border border-sky-200 rounded-lg text-[11px] text-sky-700">' +
                            '<svg class="w-3.5 h-3.5 text-sky-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-2.54a4.5 4.5 0 00-1.242-7.244l-4.5-4.5a4.5 4.5 0 00-6.364 6.364L4.343 8.28"/></svg>' +
                            '<span><strong>Rewrite Title</strong> is on &mdash; translations use the <strong>rewritten</strong> title.</span>' +
                        '</div>' +
                        '<div class="border-t border-gray-100" style="max-height:0;overflow:hidden;transition:all .2s">' +
                            '<div class="px-4 py-3 bg-gray-50/50">' +
                                '<label class="block text-[10px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Prompt</label>' +
                                '<textarea data-opt-ai-prompt="' + entry.id + '" rows="3" maxlength="500" class="w-full text-xs px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-sky-500/20 focus:border-sky-400 resize-none font-mono leading-relaxed transition-shadow" placeholder="Custom translation instructions...">' + esc(entry._aiPrompt || IMP_AI_TRANSLATE_PROMPT || '') + '</textarea>' +
                            '</div>' +
                        '</div>' +
                    '</div>';
                }

                
                if (hasDesc) {
                    var aiDescChecked = entry._aiRewriteDesc ? 'checked ' : '';
                    html += '<div class="transition-all duration-200">' +
                        '<label class="flex items-center gap-3 px-4 py-3.5 cursor-pointer hover:bg-gray-50/80 transition-colors">' +
                            '<input type="checkbox" data-opt-ai-desc="' + entry.id + '" ' + aiDescChecked + 'onchange="IMP.toggleAiCard(this)" class="w-4 h-4 rounded border-gray-300 text-emerald-600 focus:ring-emerald-500">' +
                            '<div class="w-8 h-8 rounded-lg bg-emerald-50 flex items-center justify-center shrink-0">' +
                                '<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"/></svg>' +
                            '</div>' +
                            '<div class="flex-1 min-w-0">' +
                                '<div class="text-sm font-semibold text-gray-900">Rewrite Description</div>' +
                                '<div class="text-[11px] text-gray-400">Unique SEO description</div>' +
                            '</div>' +
                            '<span class="shrink-0 text-[11px] font-bold text-emerald-600 bg-emerald-50 px-2.5 py-1 rounded-lg">+' + IMP_AI_DESC_COST.toFixed(2) + ' Cr</span>' +
                        '</label>' +
                        '<div class="border-t border-gray-100" style="max-height:0;overflow:hidden;transition:all .2s">' +
                            '<div class="px-4 py-3 bg-gray-50/50">' +
                                '<label class="block text-[10px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Prompt</label>' +
                                '<textarea data-opt-ai-desc-prompt="' + entry.id + '" rows="2" maxlength="500" class="w-full text-xs px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-400 resize-none font-mono leading-relaxed transition-shadow" placeholder="Custom prompt...">' + esc(entry._aiDescPrompt || IMP_AI_DESC_PROMPT || '') + '</textarea>' +
                            '</div>' +
                        '</div>' +
                    '</div>';
                }

                
                if (hasDesc && aiRemainingLangs.length > 0) {
                    var aiTransDescChecked = entry._aiTransDesc ? 'checked ' : '';
                    var langListD = aiRemainingLangs.map(function(l) { return l.code.toUpperCase(); }).join(', ');
                    html += '<div class="transition-all duration-200">' +
                        '<label class="flex items-center gap-3 px-4 py-3.5 cursor-pointer hover:bg-gray-50/80 transition-colors">' +
                            '<input type="checkbox" data-opt-ai-transdesc="' + entry.id + '" ' + aiTransDescChecked + 'onchange="IMP.toggleAiCard(this)" class="w-4 h-4 rounded border-gray-300 text-teal-600 focus:ring-teal-500">' +
                            '<div class="w-8 h-8 rounded-lg bg-teal-50 flex items-center justify-center shrink-0">' +
                                '<svg class="w-4 h-4 text-teal-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802"/></svg>' +
                            '</div>' +
                            '<div class="flex-1 min-w-0">' +
                                '<div class="text-sm font-semibold text-gray-900">Translate Description</div>' +
                                '<div class="text-[11px] text-gray-400">' + aiRemainingLangs.length + ' language' + (aiRemainingLangs.length > 1 ? 's' : '') + ': ' + langListD + '</div>' +
                            '</div>' +
                            '<span class="shrink-0 text-[11px] font-bold text-teal-600 bg-teal-50 px-2.5 py-1 rounded-lg">+' + IMP_AI_TRANSLATE_DESC_COST.toFixed(2) + ' Cr</span>' +
                        '</label>' +
                        '<div class="border-t border-gray-100" style="max-height:0;overflow:hidden;transition:all .2s">' +
                            '<div class="px-4 py-3 bg-gray-50/50">' +
                                '<label class="block text-[10px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Prompt</label>' +
                                '<textarea data-opt-ai-transdesc-prompt="' + entry.id + '" rows="3" maxlength="500" class="w-full text-xs px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-teal-500/20 focus:border-teal-400 resize-none font-mono leading-relaxed transition-shadow" placeholder="Custom translation instructions...">' + esc(entry._aiTransDescPrompt || IMP_AI_TRANSLATE_DESC_PROMPT || '') + '</textarea>' +
                            '</div>' +
                        '</div>' +
                    '</div>';
                }

                html += '</div>'; 

                
                html += '<p class="text-[11px] text-gray-400 mt-3 imp-ai-cost-estimate" data-entry-id="' + entry.id + '" data-total-rows="' + entry.totalRows + '"></p>';
            }

            html += '</div></div>';
        }

        
        html += '<div id="imp-credit-warn-' + entry.id + '" class="hidden"></div>';

        
        html += '<div class="bg-gray-50 rounded-xl border border-gray-200 px-4 py-3 flex items-center justify-end gap-3">' +
            '<button type="button" onclick="IMP.testImport(' + entry.id + ')" id="imp-btn-test-' + entry.id + '" class="inline-flex items-center gap-2 px-5 py-2.5 bg-white border-2 border-purple-300 text-purple-700 text-sm font-semibold rounded-xl hover:bg-purple-50 hover:border-purple-400 transition-colors shadow-sm disabled:opacity-40 disabled:cursor-not-allowed">' +
                '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"/></svg>' +
                'Test with 10 videos' +
            '</button>' +
            '<button type="button" onclick="IMP.importAll(' + entry.id + ')" id="imp-btn-import-' + entry.id + '" class="inline-flex items-center gap-2 px-6 py-2.5 bg-primary-600 text-white text-sm font-semibold rounded-xl hover:bg-primary-700 transition-colors shadow-sm shadow-primary-600/20 disabled:opacity-40 disabled:cursor-not-allowed">' +
                '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/></svg>' +
                'Import all ' + entry.totalRows.toLocaleString() + ' rows' +
            '</button>' +
        '</div>';

        html += '</div>';
        return html;
    }

    

    function removeFile(id) {
        files = files.filter(f => f.id !== id);
        const card = document.getElementById('card-' + id);
        if (card) card.remove();
        updateGlobal();
        if (files.length === 0) {
            document.getElementById('importQueue').classList.add('hidden');
            document.getElementById('importLanding').classList.remove('hidden');
        }
    }

    function clearAll() {
        files = [];
        document.getElementById('fileCards').innerHTML = '';
        document.getElementById('importQueue').classList.add('hidden');
        document.getElementById('importLanding').classList.remove('hidden');
    }

    let selectedSource = {};

    function clickSource(fileId, col) {
        const entry = files.find(f => f.id === fileId);
        if (!entry || Object.values(entry.mapping).includes(col)) return;
        selectedSource[fileId] = col;
        renderCard(entry);
    }

    function clickTarget(fileId, fieldKey) {
        if (selectedSource[fileId]) {
            doMap(fileId, fieldKey, selectedSource[fileId]);
            delete selectedSource[fileId];
        }
    }

    function dropMap(fileId, fieldKey, e) {
        const col = e.dataTransfer.getData('text/plain');
        if (col) doMap(fileId, fieldKey, col);
    }

    function doMap(fileId, fieldKey, col) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        for (const [k, v] of Object.entries(entry.mapping)) {
            if (v === col) delete entry.mapping[k];
        }
        entry.mapping[fieldKey] = col;
        renderCard(entry);
        updateGlobal();
    }

    function unmap(fileId, fieldKey) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        delete entry.mapping[fieldKey];
        entry.status = 'mapping';
        renderCard(entry);
        updateGlobal();
    }

    function toStep3(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry || !isReady(entry.mapping)) return;
        entry.status = 'ready';
        renderCard(entry);
        updateGlobal();
    }

    function backToMapping(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        entry.status = 'mapping';
        renderCard(entry);
        updateGlobal();
    }

    function getEntryOptions(entry) {
        const cache = document.querySelector('[data-opt-cache="' + entry.id + '"]');
        const dup = document.querySelector('input[name="dup_' + entry.id + '"]:checked');
        const speed = document.querySelector('[data-opt-speed="' + entry.id + '"]');
        const aiCb = document.querySelector('[data-opt-ai="' + entry.id + '"]');
        const aiPrompt = document.querySelector('[data-opt-ai-prompt="' + entry.id + '"]');
        const aiTitleCb = document.querySelector('[data-opt-ai-title="' + entry.id + '"]');
        const aiTitlePrompt = document.querySelector('[data-opt-ai-title-prompt="' + entry.id + '"]');
        const aiDescCb = document.querySelector('[data-opt-ai-desc="' + entry.id + '"]');
        const aiDescPrompt = document.querySelector('[data-opt-ai-desc-prompt="' + entry.id + '"]');
        const aiTransDescCb = document.querySelector('[data-opt-ai-transdesc="' + entry.id + '"]');
        const aiTransDescPrompt = document.querySelector('[data-opt-ai-transdesc-prompt="' + entry.id + '"]');
        
        const aiLangs = IMP_LANGUAGES.filter(function(l) { return !entry.mapping['title_' + l.code]; });
        return {
            cacheThumbs: cache ? cache.checked : false,
            duplicateMode: dup ? dup.value : 'skip',
            importSpeed: speed ? parseInt(speed.value, 10) : 3,
            aiTranslate: aiCb ? aiCb.checked : false,
            aiTranslatePrompt: (aiPrompt ? aiPrompt.value.trim() : '') || IMP_AI_TRANSLATE_PROMPT || '',
            aiTranslateLanguages: aiLangs,
            aiRewriteTitle: aiTitleCb ? aiTitleCb.checked : false,
            aiTitlePrompt: (aiTitlePrompt ? aiTitlePrompt.value.trim() : '') || IMP_AI_TITLE_PROMPT || '',
            aiRewriteDesc: aiDescCb ? aiDescCb.checked : false,
            aiDescPrompt: (aiDescPrompt ? aiDescPrompt.value.trim() : '') || IMP_AI_DESC_PROMPT || '',
            aiTranslateDesc: aiTransDescCb ? aiTransDescCb.checked : false,
            aiTranslateDescPrompt: (aiTransDescPrompt ? aiTransDescPrompt.value.trim() : '') || IMP_AI_TRANSLATE_DESC_PROMPT || ''
        };
    }

    
    function calcAiCostPerVideo(opts) {
        var cost = 0;
        if (opts.aiRewriteTitle) cost += IMP_AI_TITLE_COST;
        if (opts.aiTranslate)    cost += IMP_AI_TRANSLATE_COST;
        if (opts.aiRewriteDesc)  cost += IMP_AI_DESC_COST;
        if (opts.aiTranslateDesc) cost += IMP_AI_TRANSLATE_DESC_COST;
        return cost;
    }

    
    async function checkAiCredits(opts, videoCount) {
        var costPerVideo = calcAiCostPerVideo(opts);
        if (costPerVideo <= 0) return null; 
        var totalCost = costPerVideo * videoCount;
        try {
            var resp = await fetch('/admin/catalogue/balance', {
                method: 'GET',
                headers: { 'X-CSRF-TOKEN': CSRF },
                credentials: 'same-origin'
            });
            var data = await resp.json();
            if (data.error) return { error: true, message: 'Could not verify credit balance: ' + data.error };
            var balance = parseFloat(data.credits || 0);
            if (balance < totalCost) {
                return {
                    error: true,
                    insufficient: true,
                    balance: balance,
                    totalCost: totalCost,
                    costPerVideo: costPerVideo,
                    videoCount: videoCount,
                    message: 'Insufficient credits. You need ' + totalCost.toFixed(2) + ' credits (' + costPerVideo.toFixed(2) + '/video × ' + videoCount.toLocaleString() + ' videos) but only have ' + balance.toFixed(2) + '.'
                };
            }
            return null;
        } catch (e) {
            return { error: true, message: 'Could not verify credit balance. Please check your connection.' };
        }
    }

    
    function showCreditWarning(entryId, creditResult) {
        var warnEl = document.getElementById('imp-credit-warn-' + entryId);
        if (!warnEl) return;
        var missing = (creditResult.totalCost - creditResult.balance).toFixed(2);
        warnEl.innerHTML =
            '<div class="bg-amber-50 border border-amber-300 rounded-xl p-4 flex flex-col gap-3">' +
                '<div class="flex items-start gap-3">' +
                    '<div class="w-9 h-9 rounded-lg bg-amber-100 flex items-center justify-center shrink-0">' +
                        '<svg class="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>' +
                    '</div>' +
                    '<div class="flex-1 min-w-0">' +
                        '<div class="text-sm font-bold text-amber-800">Insufficient Credits</div>' +
                        '<p class="text-xs text-amber-700 mt-1 leading-relaxed">' +
                            'You need <strong>' + creditResult.totalCost.toFixed(2) + '</strong> credits ' +
                            '(' + creditResult.costPerVideo.toFixed(2) + '/video × ' + creditResult.videoCount.toLocaleString() + ' videos) ' +
                            'but only have <strong>' + creditResult.balance.toFixed(2) + '</strong>. ' +
                            'You\'re missing <strong>' + missing + '</strong> credits.' +
                        '</p>' +
                    '</div>' +
                '</div>' +
                '<div class="flex items-center gap-2 ml-12">' +
                    '<button type="button" onclick="IMP.openTopUp(' + entryId + ')" class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-amber-500 hover:bg-amber-600 rounded-lg transition-colors shadow-sm">' +
                        '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>' +
                        'Top Up Credits' +
                    '</button>' +
                    '<button type="button" onclick="document.getElementById(\'imp-credit-warn-' + entryId + '\').classList.add(\'hidden\')" class="inline-flex items-center px-3 py-2 text-xs font-semibold text-amber-700 hover:bg-amber-100 rounded-lg transition-colors">' +
                        'Dismiss' +
                    '</button>' +
                '</div>' +
            '</div>';
        warnEl.classList.remove('hidden');
    }

    
    function hideCreditWarning(entryId) {
        var warnEl = document.getElementById('imp-credit-warn-' + entryId);
        if (warnEl) { warnEl.classList.add('hidden'); warnEl.innerHTML = ''; }
    }

    
    function openTopUp(entryId) {
        if (window.CAT && CAT.openTopUpModal) {
            CAT.openTopUpModal();
        } else if (window.MP && MP.openTopUpModal) {
            MP.openTopUpModal();
        }
    }

    async function testImport(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        const opts = getEntryOptions(entry);

        
        var testCount = Math.min(10, entry.totalRows);
        var creditResult = await checkAiCredits(opts, testCount);
        if (creditResult) {
            if (creditResult.insufficient) {
                showCreditWarning(entry.id, creditResult);
            } else {
                entry.status = 'error'; entry.error = creditResult.message;
                renderCard(entry); updateGlobal();
            }
            return;
        }
        hideCreditWarning(entry.id);

        
        entry._optCache = opts.cacheThumbs;
        entry._dupMode = opts.duplicateMode;
        entry._importSpeed = opts.importSpeed;
        entry._aiTranslate = opts.aiTranslate;
        entry._aiPrompt = opts.aiTranslatePrompt;
        entry._aiLanguages = opts.aiTranslateLanguages;
        entry._aiRewriteTitle = opts.aiRewriteTitle;
        entry._aiTitlePrompt = opts.aiTitlePrompt;
        entry._aiRewriteDesc = opts.aiRewriteDesc;
        entry._aiDescPrompt = opts.aiDescPrompt;
        entry._aiTransDesc = opts.aiTranslateDesc;
        entry._aiTransDescPrompt = opts.aiTranslateDescPrompt;
        entry.status = 'testing';
        entry.testCount = Math.min(10, entry.totalRows);
        entry.stats = { processed: 0, total: entry.testCount, imported: 0, skipped: 0, errors: 0 };
        entry.testVideoIds = [];
        renderCard(entry);
        updateGlobal();

        try {
            const res = await fetch('/admin/import/batch', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ filename: entry.filename, mapping: entry.mapping, offset: 0, limit: entry.testCount, options: opts, returnIds: true }),
                credentials: 'same-origin'
            });
            if (res.redirected || res.status === 401 || res.status === 403) {
                throw new Error('Your session has expired. Please refresh the page and log in again.');
            }
            const data = await res.json().catch(() => {
                throw new Error('Server returned an invalid response (HTTP ' + res.status + '). Check server logs or try again.');
            });
            if (data.error) {
                entry.status = 'error'; entry.error = data.error;
            } else {
                entry.stats.processed = data.processed;
                entry.stats.imported += data.imported;
                entry.stats.skipped += data.skipped;
                entry.stats.errors += data.errors;
                entry.testVideoIds = data.videoIds || [];
                entry.testErrorDetails = data.errorDetails || [];
                entry.status = 'tested';
            }
        } catch (err) {
            entry.status = 'error';
            entry.error = err.message || 'Connection error during test import. Please check your connection and try again.';
        }
        renderCard(entry);
        updateGlobal();
    }

    async function importAll(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        const opts = getEntryOptions(entry);

        
        var creditResult = await checkAiCredits(opts, entry.totalRows);
        if (creditResult) {
            if (creditResult.insufficient) {
                showCreditWarning(entry.id, creditResult);
            } else {
                entry.status = 'error'; entry.error = creditResult.message;
                renderCard(entry); updateGlobal();
            }
            return;
        }
        hideCreditWarning(entry.id);

        entry._optCache = opts.cacheThumbs;
        entry._dupMode = opts.duplicateMode;
        entry._importSpeed = opts.importSpeed;
        entry._aiTranslate = opts.aiTranslate;
        entry._aiPrompt = opts.aiTranslatePrompt;
        entry._aiLanguages = opts.aiTranslateLanguages;
        entry._aiRewriteTitle = opts.aiRewriteTitle;
        entry._aiTitlePrompt = opts.aiTitlePrompt;
        entry._aiRewriteDesc = opts.aiRewriteDesc;
        entry._aiDescPrompt = opts.aiDescPrompt;
        entry._aiTransDesc = opts.aiTranslateDesc;
        entry._aiTransDescPrompt = opts.aiTranslateDescPrompt;
        entry.status = 'submitted';
        renderCard(entry);
        updateGlobal();

        try {
            const res = await fetch('/admin/import/start-job', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ filename: entry.filename, mapping: entry.mapping, options: opts, startOffset: 0 }),
                credentials: 'same-origin'
            });
            if (res.redirected || res.status === 401 || res.status === 403) {
                throw new Error('Your session has expired. Please refresh the page and log in again.');
            }
            const data = await res.json().catch(() => {
                throw new Error('Server returned an invalid response (HTTP ' + res.status + '). Check server logs or try again.');
            });
            if (data.error) {
                entry.status = 'error'; entry.error = data.error;
                renderCard(entry); updateGlobal();
                return;
            }
            entry.status = 'done';
            entry._jobId = data.job?.id;
            renderCard(entry);
            updateGlobal();
            startPolling();
        } catch (err) {
            entry.status = 'error';
            entry.error = err.message || 'Failed to start background import. Please check your connection and try again.';
            renderCard(entry); updateGlobal();
        }
    }

    async function importRemaining(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;
        const startOffset = entry.testCount || 10;
        const remainingCount = entry.totalRows - startOffset;
        const opts = getFileOptions(entry);

        
        if (remainingCount > 0) {
            var creditResult = await checkAiCredits(opts, remainingCount);
            if (creditResult) {
                if (creditResult.insufficient) {
                    showCreditWarning(entry.id, creditResult);
                } else {
                    entry.status = 'error'; entry.error = creditResult.message;
                    renderCard(entry); updateGlobal();
                }
                return;
            }
            hideCreditWarning(entry.id);
        }

        entry.status = 'submitted';
        renderCard(entry);
        updateGlobal();
        try {
            const res = await fetch('/admin/import/start-job', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ filename: entry.filename, mapping: entry.mapping, options: opts, startOffset }),
                credentials: 'same-origin'
            });
            if (res.redirected || res.status === 401 || res.status === 403) {
                throw new Error('Your session has expired. Please refresh the page and log in again.');
            }
            const data = await res.json().catch(() => {
                throw new Error('Server returned an invalid response (HTTP ' + res.status + '). Check server logs or try again.');
            });
            if (data.error) {
                entry.status = 'error'; entry.error = data.error;
                renderCard(entry); updateGlobal();
                return;
            }
            entry.status = 'done';
            entry._jobId = data.job?.id;
            renderCard(entry);
            updateGlobal();
            startPolling();
        } catch (err) {
            entry.status = 'error';
            entry.error = err.message || 'Failed to start background import. Please check your connection and try again.';
            renderCard(entry); updateGlobal();
        }
    }

    async function cancelTest(fileId) {
        const entry = files.find(f => f.id === fileId);
        if (!entry || !entry.testVideoIds || entry.testVideoIds.length === 0) {
            if (entry) { entry.status = 'ready'; renderCard(entry); updateGlobal(); }
            return;
        }
        try {
            await fetch('/admin/import/delete-test', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ ids: entry.testVideoIds }),
                credentials: 'same-origin'
            });
        } catch (e) {}
        entry.testVideoIds = [];
        entry.stats = null;
        entry.status = 'ready';
        renderCard(entry);
        updateGlobal();
    }

    

    function previewCol(fileId, col, btn) {
        const entry = files.find(f => f.id === fileId);
        if (!entry) return;

        const popover = document.getElementById('colPreview');
        const title = document.getElementById('colPreviewTitle');
        const body = document.getElementById('colPreviewBody');

        title.textContent = col;
        body.innerHTML = '';

        const values = entry.preview.map(row => String(row[col] ?? ''));
        if (values.every(v => v === '')) {
            body.innerHTML = '<div class="text-xs text-gray-400 italic px-1 py-2">No data in preview rows</div>';
        } else {
            values.forEach((val, i) => {
                const r = document.createElement('div');
                r.className = 'flex gap-2 px-2 py-1 rounded text-xs ' + (i % 2 ? '' : 'bg-gray-50');
                r.innerHTML = '<span class="text-gray-300 w-3 text-right shrink-0">' + (i+1) + '</span>' +
                    '<span class="text-gray-600 break-all">' + esc(val.length > 120 ? val.substring(0,120) + '...' : (val || '<em class="text-gray-300">empty</em>')) + '</span>';
                body.appendChild(r);
            });
        }

        const rect = btn.getBoundingClientRect();
        popover.classList.remove('hidden');
        const pr = popover.getBoundingClientRect();
        let top = rect.top - pr.height / 2 + 8;
        let left = rect.left - pr.width - 8;
        if (left < 8) left = rect.right + 8;
        if (top + pr.height > window.innerHeight - 8) top = window.innerHeight - pr.height - 8;
        if (top < 8) top = 8;
        popover.style.top = top + 'px';
        popover.style.left = left + 'px';
    }

    function closePreview() {
        document.getElementById('colPreview').classList.add('hidden');
    }

    function showFieldInfo(el, key) {
        const field = FIELDS.find(f => f.key === key);
        if (!field || !field.info) return;
        const pop = document.getElementById('fieldInfo');
        document.getElementById('fieldInfoBody').innerHTML = field.info;
        pop.classList.remove('hidden');
        const rect = el.getBoundingClientRect();
        const pr = pop.getBoundingClientRect();
        let top = rect.bottom + 6;
        let left = rect.left - pr.width / 2 + 6;
        if (left < 8) left = 8;
        if (left + pr.width > window.innerWidth - 8) left = window.innerWidth - pr.width - 8;
        if (top + pr.height > window.innerHeight - 8) top = rect.top - pr.height - 6;
        pop.style.top = top + 'px';
        pop.style.left = left + 'px';
    }

    function closeFieldInfo() {
        document.getElementById('fieldInfo').classList.add('hidden');
    }

    function getFileOptions(entry) {
        return {
            cacheThumbs: entry._optCache || false,
            duplicateMode: entry._dupMode || 'skip',
            importSpeed: entry._importSpeed || 3,
            aiTranslate: entry._aiTranslate || false,
            aiTranslatePrompt: entry._aiPrompt || '',
            aiTranslateLanguages: entry._aiLanguages || [],
            aiRewriteTitle: entry._aiRewriteTitle || false,
            aiTitlePrompt: entry._aiTitlePrompt || '',
            aiRewriteDesc: entry._aiRewriteDesc || false,
            aiDescPrompt: entry._aiDescPrompt || '',
            aiTranslateDesc: entry._aiTransDesc || false,
            aiTranslateDescPrompt: entry._aiTransDescPrompt || ''
        };
    }

    document.addEventListener('click', function(e) {
        if (!e.target.closest('#colPreview') && !e.target.closest('.preview-btn') && !e.target.closest('[onclick*="previewCol"]')) {
            closePreview();
        }
        if (!e.target.closest('#fieldInfo') && !e.target.closest('.info-tip')) {
            closeFieldInfo();
        }
    });

    

    function updateGlobal() {
        const ready = files.filter(f => f.status === 'ready').length;
        const importing = files.filter(f => f.status === 'importing').length;
        const done = files.filter(f => f.status === 'done').length;
        const testing = files.filter(f => ['testing', 'tested'].includes(f.status)).length;
        const totalFiles = files.length;

        const parts = [totalFiles + ' file' + (totalFiles !== 1 ? 's' : '')];
        if (importing > 0) parts.push(importing + ' importing');
        if (testing > 0) parts.push(testing + ' testing');
        if (ready > 0) parts.push(ready + ' ready');
        if (done > 0) parts.push(done + ' completed');
        document.getElementById('queueSummary').textContent = parts.join(' — ');
    }

    

    function startAll() {
        files.filter(f => f.status === 'ready').forEach(f => {
            importAll(f.id);
        });
    }

    

    let pollTimer = null;

    function startPolling() {
        if (pollTimer) return;
        pollJobs();
        pollTimer = setInterval(pollJobs, 5000);
    }

    function stopPolling() {
        if (pollTimer) {
            clearInterval(pollTimer);
            pollTimer = null;
        }
    }

    let pollErrorCount = 0;

    async function pollJobs() {
        try {
            const res = await fetch('/admin/import/jobs', { credentials: 'same-origin' });
            if (res.redirected || res.status === 401 || res.status === 403) {
                
                stopPolling();
                const container = document.getElementById('activeImports');
                const list = document.getElementById('activeJobsList');
                container.classList.remove('hidden');
                list.innerHTML = '<div class="bg-amber-50 border border-amber-200 text-amber-700 text-sm rounded-lg px-4 py-3">' +
                    '<strong>Session expired.</strong> Your import jobs continue running in the background. ' +
                    '<a href="/admin/import" class="underline font-medium">Refresh the page</a> to see current progress.' +
                '</div>';
                return;
            }
            const data = await res.json();
            pollErrorCount = 0;
            renderActiveJobs(data.jobs || []);
        } catch (e) {
            pollErrorCount++;
            
            if (pollErrorCount >= 5) {
                stopPolling();
                const container = document.getElementById('activeImports');
                const list = document.getElementById('activeJobsList');
                container.classList.remove('hidden');
                list.innerHTML = '<div class="bg-red-50 border border-red-200 text-red-700 text-sm rounded-lg px-4 py-3">' +
                    '<strong>Connection lost.</strong> Could not reach the server after multiple attempts. ' +
                    'Your import jobs continue running in the background. ' +
                    '<a href="/admin/import" class="underline font-medium">Refresh the page</a> to reconnect.' +
                '</div>';
            }
        }
    }

    function renderActiveJobs(jobs) {
        const container = document.getElementById('activeImports');
        const list = document.getElementById('activeJobsList');
        const summary = document.getElementById('activeImportsSummary');

        if (!jobs || jobs.length === 0) {
            container.classList.add('hidden');
            stopPolling();
            return;
        }

        container.classList.remove('hidden');

        const processing = jobs.filter(j => j.status === 'processing' || j.status === 'pending').length;
        const paused = jobs.filter(j => j.status === 'paused').length;
        const completed = jobs.filter(j => j.status === 'completed').length;
        const failed = jobs.filter(j => j.status === 'failed').length;
        const cancelled = jobs.filter(j => j.status === 'cancelled').length;
        const parts = [];
        if (processing > 0) parts.push(processing + ' active');
        if (paused > 0) parts.push(paused + ' paused');
        if (completed > 0) parts.push(completed + ' completed');
        if (failed > 0) parts.push(failed + ' failed');
        if (cancelled > 0) parts.push(cancelled + ' cancelled');
        summary.textContent = parts.join(' · ');

        
        if (processing === 0) {
            stopPolling();
        }

        list.innerHTML = jobs.map(renderJobCard).join('');
    }

    function renderJobCard(job) {
        const pct = job.total_rows > 0 ? Math.round((job.processed_rows / job.total_rows) * 100) : 0;
        const isActive = job.status === 'processing' || job.status === 'pending';
        const isPaused = job.status === 'paused';
        const isDone = ['completed', 'cancelled', 'failed'].includes(job.status);

        const statusColors = {
            pending:    'bg-amber-50 text-amber-600',
            processing: 'bg-blue-50 text-blue-600',
            paused:     'bg-orange-50 text-orange-600',
            completed:  'bg-green-50 text-green-700',
            cancelled:  'bg-gray-100 text-gray-500',
            failed:     'bg-red-50 text-red-600',
        };
        const statusLabels = {
            pending:    'Queued',
            processing: 'Importing...',
            paused:     'Paused',
            completed:  'Completed',
            cancelled:  'Cancelled',
            failed:     'Failed',
        };

        const progressBarColor = {
            processing: 'bg-primary-500',
            pending:    'bg-primary-500',
            paused:     'bg-orange-400',
            completed:  'bg-green-500',
            failed:     'bg-red-400',
            cancelled:  'bg-gray-300',
        };

        let html = '<div class="bg-white rounded-lg border border-gray-200 p-3">';

        
        const timeInfo = job.completed_at
            ? 'Finished ' + esc(job.completed_at)
            : (job.started_at ? 'Started ' + esc(job.started_at) : 'Created ' + esc(job.created_at || ''));

        html += '<div class="flex items-center gap-3 mb-2">' +
            '<div class="flex-1 min-w-0">' +
                '<span class="text-sm font-medium text-gray-800 truncate block">' + esc(job.original_name || job.filename) + '</span>' +
                '<span class="text-xs text-gray-400">Job #' + job.id + ' · ' + timeInfo + '</span>' +
            '</div>' +
            '<span class="px-2 py-0.5 text-xs rounded-full ' + (statusColors[job.status] || '') + '">' + (statusLabels[job.status] || job.status) + '</span>' +
        '</div>';

        
        html += '<div class="w-full bg-gray-100 rounded-full h-1.5 mb-2 overflow-hidden">' +
            '<div class="h-1.5 rounded-full transition-all ' + (progressBarColor[job.status] || 'bg-gray-300') + '" style="width:' + pct + '%"></div>' +
        '</div>';

        
        html += '<div class="flex flex-wrap items-center gap-3 text-xs">' +
            '<span class="text-gray-500">' + Number(job.processed_rows).toLocaleString() + ' / ' + Number(job.total_rows).toLocaleString() + ' rows (' + pct + '%)</span>' +
            '<span class="text-green-600">' + Number(job.imported_count).toLocaleString() + ' imported</span>' +
            (parseInt(job.skipped_count) > 0 ? '<span class="text-amber-600">' + Number(job.skipped_count).toLocaleString() + ' skipped (duplicates)</span>' : '') +
            (parseInt(job.error_count) > 0 ? '<span class="text-red-600">' + Number(job.error_count).toLocaleString() + ' errors</span>' : '') +
        '</div>';

        
        html += '<div class="flex items-center gap-1.5 mt-2 pt-2 border-t border-gray-100">';

        if (isActive) {
            
            html += '<button type="button" onclick="IMP.pauseBgJob(' + job.id + ')" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md bg-orange-50 text-orange-700 border border-orange-200 hover:bg-orange-100 transition-colors" title="Pause import">' +
                '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5"/></svg>' +
                'Pause' +
            '</button>';
            
            html += '<button type="button" onclick="IMP.cancelBgJob(' + job.id + ')" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors" title="Cancel import">' +
                '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18L18 6M6 6l12 12"/></svg>' +
                'Cancel' +
            '</button>';
        }

        if (isPaused) {
            
            html += '<button type="button" onclick="IMP.resumeBgJob(' + job.id + ')" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md bg-primary-50 text-primary-700 border border-primary-200 hover:bg-primary-100 transition-colors" title="Resume import">' +
                '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/></svg>' +
                'Resume' +
            '</button>';
            
            html += '<button type="button" onclick="IMP.cancelBgJob(' + job.id + ')" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors" title="Cancel import">' +
                '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18L18 6M6 6l12 12"/></svg>' +
                'Cancel' +
            '</button>';
        }

        if (isDone || isPaused) {
            
            html += '<button type="button" onclick="IMP.deleteBgJob(' + job.id + ')" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors ml-auto" title="Remove from list">' +
                '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>' +
                'Delete' +
            '</button>';
        }

        html += '</div>';

        
        if (job.error_message) {
            const isFatal = job.status === 'failed';
            const bgClass = isFatal ? 'bg-red-50 border-red-200' : 'bg-amber-50 border-amber-200';
            const textClass = isFatal ? 'text-red-700' : 'text-amber-700';
            const title = isFatal ? 'Import failed' : 'Row errors encountered';
            const lines = job.error_message.split('\n').filter(l => l.trim());
            const maxShow = 5;
            const showing = lines.slice(0, maxShow);
            const remaining = lines.length - maxShow;

            html += '<div class="mt-2 border rounded-lg px-3 py-2 ' + bgClass + '">' +
                '<div class="text-xs font-medium ' + textClass + ' mb-1">' + title + '</div>' +
                '<div class="text-xs ' + textClass + ' space-y-0.5 font-mono">';
            showing.forEach(line => {
                html += '<div class="truncate">' + esc(line) + '</div>';
            });
            if (remaining > 0) {
                html += '<div class="text-xs opacity-60 mt-1">... and ' + remaining + ' more error(s)</div>';
            }
            html += '</div></div>';
        }

        html += '</div>';
        return html;
    }

    async function jobAction(endpoint, jobId, confirmMsg) {
        if (confirmMsg && !confirm(confirmMsg)) return;
        try {
            const res = await fetch('/admin/import/' + endpoint, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ id: jobId }),
                credentials: 'same-origin'
            });
            if (res.redirected || res.status === 401 || res.status === 403) {
                alert('Your session has expired. Please refresh the page.');
                return;
            }
            if (!res.ok) {
                const data = await res.json().catch(() => ({}));
                alert(data.error || 'Action failed. Please try again.');
                return;
            }
            pollJobs();
            
            if (endpoint === 'resume-job') startPolling();
        } catch (e) {
            alert('Connection error. Please try again.');
        }
    }

    function cancelBgJob(jobId) {
        jobAction('cancel-job', jobId, 'Cancel this import? Already imported videos will be kept.');
    }

    function pauseBgJob(jobId) {
        jobAction('pause-job', jobId);
    }

    function resumeBgJob(jobId) {
        jobAction('resume-job', jobId);
    }

    function deleteBgJob(jobId) {
        jobAction('delete-job', jobId, 'Remove this job from the list? Imported videos will not be deleted.');
    }

    

    function showUploadError(msg) {
        const el = document.getElementById('uploadError');
        el.textContent = msg;
        el.classList.remove('hidden');
    }
    function hideUploadError() {
        document.getElementById('uploadError').classList.add('hidden');
    }

    function esc(str) {
        const d = document.createElement('div');
        d.textContent = String(str);
        return d.innerHTML;
    }

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

    

    let savedFilesCache = null;

    async function loadSavedFiles() {
        try {
            const res = await fetch('/admin/import/files', { credentials: 'same-origin' });
            savedFilesCache = await res.json();
        } catch (e) {
            savedFilesCache = [];
        }
        renderSavedLanding();
        renderSavedDropdown();
    }

    function renderSavedLanding() {
        const list = document.getElementById('savedFilesList');
        const count = document.getElementById('savedFilesCount');
        if (!savedFilesCache || savedFilesCache.length === 0) {
            if (count) count.textContent = '';
            list.innerHTML = '<div class="text-xs text-gray-400 text-center py-6">No saved files yet</div>';
            return;
        }
        if (count) count.textContent = savedFilesCache.length + ' file' + (savedFilesCache.length !== 1 ? 's' : '');
        list.innerHTML = savedFilesCache.map(f =>
            '<div class="flex items-center gap-3 px-3 py-2 rounded-lg border border-gray-100 hover:border-gray-300 transition-colors" id="saved-' + esc(f.filename) + '">' +
                '<div class="w-8 h-8 rounded bg-gray-100 flex items-center justify-center text-[10px] font-bold text-gray-400 shrink-0">' + esc(f.extension) + '</div>' +
                '<div class="flex-1 min-w-0">' +
                    '<div class="text-sm font-medium text-gray-700 truncate">' + esc(f.originalName) + '</div>' +
                    '<div class="text-xs text-gray-400">' + formatSize(f.size) + ' &middot; ' + esc(f.uploadedAt.substring(0, 10)) + '</div>' +
                '</div>' +
                '<button type="button" onclick="IMP.useSaved(\'' + escAttr(f.filename) + '\')" class="px-2.5 py-1 text-xs font-medium rounded bg-primary-600 text-white hover:bg-primary-700 transition-colors shrink-0">Use</button>' +
                '<button type="button" onclick="IMP.deleteSaved(\'' + escAttr(f.filename) + '\')" class="p-1 text-gray-300 hover:text-red-500 transition-colors shrink-0">' +
                    '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18L18 6M6 6l12 12"/></svg>' +
                '</button>' +
            '</div>'
        ).join('');
    }

    function renderSavedDropdown() {
        const list = document.getElementById('savedDropdownList');
        if (!list) return;
        if (!savedFilesCache || savedFilesCache.length === 0) {
            list.innerHTML = '<div class="text-xs text-gray-400 text-center py-3">No saved files</div>';
            return;
        }
        list.innerHTML = savedFilesCache.map(f =>
            '<div class="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-50 transition-colors">' +
                '<div class="w-6 h-6 rounded bg-gray-100 flex items-center justify-center text-[9px] font-bold text-gray-400 shrink-0">' + esc(f.extension) + '</div>' +
                '<div class="flex-1 min-w-0">' +
                    '<div class="text-xs font-medium text-gray-700 truncate">' + esc(f.originalName) + '</div>' +
                    '<div class="text-[10px] text-gray-400">' + formatSize(f.size) + ' &middot; ' + esc(f.uploadedAt.substring(0, 10)) + '</div>' +
                '</div>' +
                '<button type="button" onclick="IMP.useSaved(\'' + escAttr(f.filename) + '\')" class="px-2 py-0.5 text-[11px] font-medium rounded bg-primary-600 text-white hover:bg-primary-700 transition-colors shrink-0">Use</button>' +
                '<button type="button" onclick="IMP.deleteSaved(\'' + escAttr(f.filename) + '\')" class="p-0.5 text-gray-300 hover:text-red-500 transition-colors shrink-0">' +
                    '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" d="M6 18L18 6M6 6l12 12"/></svg>' +
                '</button>' +
            '</div>'
        ).join('');
    }

    function toggleSavedDropdown() {
        const panel = document.getElementById('savedDropdownPanel');
        panel.classList.toggle('hidden');
        if (!panel.classList.contains('hidden') && savedFilesCache === null) {
            loadSavedFiles();
        } else {
            renderSavedDropdown();
        }
    }

    async function useSaved(filename) {
        
        const panel = document.getElementById('savedDropdownPanel');
        if (panel) panel.classList.add('hidden');

        const id = ++idCounter;
        const origName = savedFilesCache?.find(f => f.filename === filename)?.originalName || filename;
        const entry = {
            id, originalName: origName, filename: null, columns: [], preview: [],
            totalRows: 0, mapping: {}, status: 'uploading', stats: null, error: null
        };
        files.push(entry);
        showQueue();
        renderCard(entry);

        try {
            const res = await fetch('/admin/import/reparse', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ filename }),
                credentials: 'same-origin'
            });
            if (res.redirected || res.status === 401 || res.status === 403) {
                throw new Error('Your session has expired. Please refresh the page and log in again.');
            }
            const data = await res.json().catch(() => {
                throw new Error('Server returned an invalid response (HTTP ' + res.status + '). The file may be corrupted.');
            });
            if (data.error) {
                entry.status = 'error';
                entry.error = data.error;
            } else {
                entry.filename = data.filename;
                entry.originalName = data.originalName || origName;
                entry.columns = data.columns;
                entry.preview = data.preview;
                entry.totalRows = data.totalRows;
                entry.mapping = autoMap(data.columns);
                entry._autoMapCount = Object.keys(entry.mapping).length;
                entry.status = 'mapping';
            }
        } catch (err) {
            entry.status = 'error';
            entry.error = err.message || 'Failed to load saved file. Please check your connection and try again.';
        }
        renderCard(entry);
        updateGlobal();
    }

    async function deleteSaved(filename) {
        if (!confirm('Delete this saved file?')) return;
        try {
            await fetch('/admin/import/delete-file', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF },
                body: JSON.stringify({ filename }),
                credentials: 'same-origin'
            });
        } catch (e) {}
        if (savedFilesCache) {
            savedFilesCache = savedFilesCache.filter(f => f.filename !== filename);
        }
        renderSavedLanding();
        renderSavedDropdown();
    }

    function formatSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / 1048576).toFixed(1) + ' MB';
    }

    
    document.addEventListener('click', function(e) {
        if (!e.target.closest('#queueSavedDropdown')) {
            const panel = document.getElementById('savedDropdownPanel');
            if (panel) panel.classList.add('hidden');
        }
    });

    
    loadSavedFiles();

    
    pollJobs().then(function() {
        
        const container = document.getElementById('activeImports');
        if (!container.classList.contains('hidden')) {
            startPolling();
        }
    });

    
    function toggleAiCard(checkbox) {
        var card = checkbox.closest('.transition-all');
        if (!card) return;
        var body = card.querySelector('[style*="max-height"]');
        if (body) {
            if (checkbox.checked) {
                body.style.maxHeight = '500px';
            } else {
                body.style.maxHeight = '0';
            }
        }
        
        var container = checkbox.closest('.p-4');
        if (!container) return;
        var costEl = container.querySelector('.imp-ai-cost-estimate');
        if (!costEl) return;
        var entryId = costEl.getAttribute('data-entry-id');
        var totalRows = parseInt(costEl.getAttribute('data-total-rows'), 10) || 0;
        var cost = 0;
        var titleCb = container.querySelector('[data-opt-ai-title="' + entryId + '"]');
        var transCb = container.querySelector('[data-opt-ai="' + entryId + '"]');
        var descCb = container.querySelector('[data-opt-ai-desc="' + entryId + '"]');
        var transDescCb = container.querySelector('[data-opt-ai-transdesc="' + entryId + '"]');
        if (titleCb && titleCb.checked) cost += IMP_AI_TITLE_COST;
        if (transCb && transCb.checked) cost += IMP_AI_TRANSLATE_COST;
        if (descCb && descCb.checked) cost += IMP_AI_DESC_COST;
        if (transDescCb && transDescCb.checked) cost += IMP_AI_TRANSLATE_DESC_COST;
        if (cost > 0) {
            costEl.innerHTML = '<span class="font-medium text-purple-600">' + cost.toFixed(2) + ' credits/video</span> &middot; Est. total: <span class="font-medium">' + (cost * totalRows).toFixed(2) + '</span> credits for ' + totalRows.toLocaleString() + ' videos';
        } else {
            costEl.textContent = '';
        }
        
        var notice = container.querySelector('.imp-ai-rewrite-notice-' + entryId);
        if (notice) {
            var showNotice = titleCb && titleCb.checked && transCb && transCb.checked;
            notice.classList.toggle('hidden', !showNotice);
        }
    }

    
    function _refreshCards() {
        files.forEach(function(entry) { renderCard(entry); });
    }

    
    return {
        handleDrop, handleSelect, removeFile, clearAll,
        clickSource, clickTarget, dropMap, unmap, previewCol, closePreview,
        showFieldInfo, startAll, doMap, toStep3, backToMapping,
        testImport, importAll, importRemaining, cancelTest,
        useSaved, deleteSaved, toggleSavedDropdown,
        cancelBgJob, pauseBgJob, resumeBgJob, deleteBgJob,
        toggleAiCard, openTopUp, _refreshCards
    };
})();
