import { showToast, openCivitai, openHuggingFace, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, sendEmbeddingToWorkflow, openExampleImagesFolder, buildLoraSyntax, sendModelPathToWorkflow } from '../../utils/uiHelpers.js';
import { state, getCurrentPageState } from '../../state/index.js';
import { showModelModal } from './ModelModal.js';
import { bulkManager } from '../../managers/BulkManager.js';
import { modalManager } from '../../managers/ModalManager.js';
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, getMatureBlurThreshold, MODEL_SUBTYPE_DISPLAY_NAMES, MODEL_CARD_DRAG_MIME_TYPE } from '../../utils/constants.js';
import { MODEL_TYPES } from '../../api/apiConfig.js';
import { getModelApiClient } from '../../api/modelApiFactory.js';
import { showDeleteModal } from '../../utils/modalUtils.js';
import { translate } from '../../utils/i18nHelpers.js';
import { eventManager } from '../../utils/EventManager.js';

// Helper function to get display name based on settings
function getDisplayName(model) {
    const displayNameSetting = state.global.settings.model_name_display || 'model_name';

    if (displayNameSetting === 'file_name') {
        return model.file_name || model.model_name || 'Unknown Model';
    }

    return model.model_name || model.file_name || 'Unknown Model';
}

// Add global event delegation handlers using event manager
export function setupModelCardEventDelegation(modelType) {
    // Remove any existing handler first
    eventManager.removeHandler('click', 'modelCard-delegation');

    // Register model card event delegation with event manager
    eventManager.addHandler('click', 'modelCard-delegation', (event) => {
        return handleModelCardEvent_internal(event, modelType);
    }, {
        priority: 60, // Medium priority for model card interactions
        targetSelector: '#modelGrid',
        skipWhenModalOpen: false // Allow model card interactions even when modals are open (for some actions)
    });
}

// Event delegation handler for all model card events
function handleModelCardEvent_internal(event, modelType) {
    // Find the closest card element
    const card = event.target.closest('.model-card');
    if (!card) return false; // Continue with other handlers

    // Handle specific elements within the card
    if (event.target.closest('.toggle-blur-btn')) {
        event.stopPropagation();
        toggleBlurContent(card);
        return true; // Stop propagation
    }

    if (event.target.closest('.show-content-btn')) {
        event.stopPropagation();
        showBlurredContent(card);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-star')) {
        event.stopPropagation();
        toggleFavorite(card);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-globe')) {
        event.stopPropagation();
        if (card.dataset.from_civitai === 'true') {
            openCivitai(card.dataset.filepath);
        } else if (card.dataset.hf_url) {
            openHuggingFace(card.dataset.hf_url);
        }
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-paper-plane')) {
        event.stopPropagation();
        handleSendToWorkflow(card, event.shiftKey, modelType);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-copy')) {
        event.stopPropagation();
        handleCopyAction(card, modelType);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-trash')) {
        event.stopPropagation();
        showDeleteModal(card.dataset.filepath);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-image')) {
        event.stopPropagation();
        getModelApiClient().replaceModelPreview(card.dataset.filepath);
        return true; // Stop propagation
    }

    if (event.target.closest('.fa-folder-open')) {
        event.stopPropagation();
        handleExampleImagesAccess(card, modelType);
        return true; // Stop propagation
    }

    if (event.target.closest('.version-count-link')) {
        event.stopPropagation();
        handleViewLocalVersionsFromCard(card, modelType);
        return true;
    }

    // If no specific element was clicked, handle the card click (show modal or toggle selection)
    if (state.bulkMode && event.shiftKey) {
        event.preventDefault(); // keep shift+click from extending a text selection
    }
    handleCardClick(card, modelType, event.shiftKey);
    return false; // Continue with other handlers (e.g., bulk selection)
}

// Helper functions for event handling
function toggleBlurContent(card) {
    const preview = card.querySelector('.card-preview');
    const isBlurred = preview.classList.toggle('blurred');
    const icon = card.querySelector('.toggle-blur-btn i');

    // Update the icon based on blur state
    if (isBlurred) {
        icon.className = 'fas fa-eye';
    } else {
        icon.className = 'fas fa-eye-slash';
    }

    // Toggle the overlay visibility
    const overlay = card.querySelector('.nsfw-overlay');
    if (overlay) {
        overlay.style.display = isBlurred ? 'flex' : 'none';
    }
}

function showBlurredContent(card) {
    const preview = card.querySelector('.card-preview');
    preview.classList.remove('blurred');

    // Update the toggle button icon
    const toggleBtn = card.querySelector('.toggle-blur-btn');
    if (toggleBtn) {
        toggleBtn.querySelector('i').className = 'fas fa-eye-slash';
    }

    // Hide the overlay
    const overlay = card.querySelector('.nsfw-overlay');
    if (overlay) {
        overlay.style.display = 'none';
    }
}

async function toggleFavorite(card) {
    const starIcon = card.querySelector('.fa-star');
    const isFavorite = starIcon.classList.contains('fas');
    const newFavoriteState = !isFavorite;

    try {
        await getModelApiClient().saveModelMetadata(card.dataset.filepath, {
            favorite: newFavoriteState
        });

        if (newFavoriteState) {
            showToast('modelCard.favorites.added', {}, 'success');
        } else {
            showToast('modelCard.favorites.removed', {}, 'success');
        }
    } catch (error) {
        console.error('Failed to update favorite status:', error);
        showToast('modelCard.favorites.updateFailed', {}, 'error');
    }
}

function handleSendToWorkflow(card, replaceMode, modelType) {
    if (modelType === MODEL_TYPES.LORA) {
        const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
        const folder = card.dataset.folder || '';
        const loraName = folder ? `${folder}/${card.dataset.file_name}` : card.dataset.file_name;
        const loraSyntax = buildLoraSyntax(loraName, usageTips);
        sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
    } else if (modelType === MODEL_TYPES.CHECKPOINT) {
        const modelPath = card.dataset.filepath;
        if (!modelPath) {
            const message = translate('modelCard.sendToWorkflow.missingPath', {}, 'Unable to determine model path for this card');
            showToast(message, {}, 'error');
            return;
        }

        const subtype = (card.dataset.sub_type || 'checkpoint').toLowerCase();
        const isDiffusionModel = subtype === 'diffusion_model';
        const widgetName = isDiffusionModel ? 'unet_name' : 'ckpt_name';
        const actionTypeText = translate(
            isDiffusionModel ? 'uiHelpers.nodeSelector.diffusionModel' : 'uiHelpers.nodeSelector.checkpoint',
            {},
            isDiffusionModel ? 'Diffusion Model' : 'Checkpoint'
        );
        const successMessage = translate(
            'uiHelpers.workflow.modelUpdated',
            {},
            'Model updated in workflow'
        );
        const failureMessage = translate(
            'uiHelpers.workflow.modelFailed',
            {},
            'Failed to update model node'
        );
        const missingNodesMessage = translate(
            'uiHelpers.workflow.noMatchingNodes',
            {},
            'No compatible nodes available in the current workflow'
        );
        const missingTargetMessage = translate(
            'uiHelpers.workflow.noTargetNodeSelected',
            {},
            'No target node selected'
        );

        sendModelPathToWorkflow(modelPath, {
            widgetName,
            collectionType: MODEL_TYPES.CHECKPOINT,
            actionTypeText,
            successMessage,
            failureMessage,
            missingNodesMessage,
            missingTargetMessage,
        });
    } else if (modelType === MODEL_TYPES.EMBEDDING) {
        const folder = card.dataset.folder || '';
        const name = card.dataset.file_name || '';
        const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
        sendEmbeddingToWorkflow(embeddingCode);
    } else {
        showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
    }
}

function handleCopyAction(card, modelType) {
    if (modelType === MODEL_TYPES.LORA) {
        copyLoraSyntax(card);
    } else if (modelType === MODEL_TYPES.CHECKPOINT) {
        // Checkpoint copy functionality - copy checkpoint name
        const checkpointName = card.dataset.file_name;
        const message = translate('modelCard.actions.checkpointNameCopied', {}, 'Checkpoint name copied');
        copyToClipboard(checkpointName, message);
    } else if (modelType === MODEL_TYPES.EMBEDDING) {
        const folder = card.dataset.folder || '';
        const name = card.dataset.file_name || '';
        const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
        const message = translate('modelCard.actions.embeddingNameCopied', {}, 'Embedding syntax copied');
        copyToClipboard(embeddingCode, message);
    }
}

function handleReplacePreview(filePath, modelType) {
    apiClient.replaceModelPreview(filePath);
}

async function handleExampleImagesAccess(card, modelType) {
    const modelHash = card.dataset.sha256;

    try {
        const response = await fetch(`/api/lm/has-example-images?model_hash=${modelHash}`);
        const data = await response.json();

        if (data.has_images) {
            openExampleImagesFolder(modelHash);
        } else {
            showExampleAccessModal(card, modelType);
        }
    } catch (error) {
        console.error('Error checking for example images:', error);
        showToast('modelCard.exampleImages.checkError', {}, 'error');
    }
}

function handleViewLocalVersionsFromCard(card, modelType) {
    const modelId = card.dataset.modelId;
    const modelName = card.dataset.name;
    if (!modelId) return;
    // Respect version_grouping: only filter by base model when the strategy says so
    const strategy = state.global?.settings?.version_grouping;
    const shouldFilterByBase = strategy === 'same_base';
    const baseModel = shouldFilterByBase && card.dataset.base_model !== 'Unknown'
        ? card.dataset.base_model
        : undefined;
    // Use the no-reload VLM flow via PageControls
    if (window.pageControls && typeof window.pageControls.triggerVlmView === 'function') {
        window.pageControls.triggerVlmView(modelId, modelName, baseModel, modelType);
    }
}

function handleCardClick(card, modelType, extendSelection = false) {
    const pageState = getCurrentPageState();

    if (state.bulkMode) {
        // Toggle selection using the bulk manager
        bulkManager.toggleCardSelection(card, extendSelection);
    } else if (pageState && pageState.duplicatesMode) {
        // In duplicates mode, don't open modal when clicking cards
        return;
    } else {
        // Normal behavior - show modal
        showModelModalFromCard(card, modelType);
    }
}

// Preview URL is not in the dataset; read it from the card's rendered media
function getCardPreviewUrl(card) {
    const cardMedia = card.querySelector('.card-preview img, .card-preview video');
    if (!cardMedia) return '';
    return cardMedia.tagName === 'VIDEO'
        ? (cardMedia.dataset.src || '')
        : (cardMedia.src || '');
}

async function showModelModalFromCard(card, modelType) {
    // Create model metadata object
    const modelMeta = {
        sha256: card.dataset.sha256,
        autov3: card.dataset.autov3 || '',
        preview_url: getCardPreviewUrl(card),
        file_path: card.dataset.filepath,
        model_name: card.dataset.name,
        file_name: card.dataset.file_name,
        folder: card.dataset.folder,
        modified: card.dataset.modified,
        file_size: parseInt(card.dataset.file_size || '0'),
        from_civitai: card.dataset.from_civitai === 'true',
        hf_url: card.dataset.hf_url || '',
        base_model: card.dataset.base_model,
        notes: card.dataset.notes || '',
        favorite: card.dataset.favorite === 'true',
        // Parse civitai metadata from the card's dataset
        civitai: JSON.parse(card.dataset.meta || '{}'),
        tags: JSON.parse(card.dataset.tags || '[]'),
        update_available: card.dataset.update_available === 'true',
        modelDescription: card.dataset.modelDescription || '',
        // LoRA specific fields
        ...(modelType === MODEL_TYPES.LORA && {
            usage_tips: card.dataset.usage_tips,
        })
    };

    await showModelModal(modelMeta, modelType);
}

// Function to show the example access modal (generalized for lora and checkpoint)
function showExampleAccessModal(card, modelType) {
    const modal = document.getElementById('exampleAccessModal');
    if (!modal) return;

    // Get download button and determine if download should be enabled
    const downloadBtn = modal.querySelector('#downloadExamplesBtn');
    let hasRemoteExamples = false;

    try {
        const metaData = JSON.parse(card.dataset.meta || '{}');
        hasRemoteExamples = metaData.images &&
            Array.isArray(metaData.images) &&
            metaData.images.length > 0 &&
            metaData.images[0].url;
    } catch (e) {
        console.error('Error parsing meta data:', e);
    }

    // Enable or disable download button
    if (downloadBtn) {
        if (hasRemoteExamples) {
            downloadBtn.classList.remove('disabled');
            downloadBtn.removeAttribute('title');
            downloadBtn.onclick = async () => {
                // Get the model hash
                const modelHash = card.dataset.sha256;
                if (!modelHash) {
                    showToast('modelCard.exampleImages.missingHash', {}, 'error');
                    return;
                }

                // Close the modal
                modalManager.closeModal('exampleAccessModal');

                try {
                    // Use the appropriate model API client to download examples
                    const apiClient = getModelApiClient(modelType);
                    await apiClient.downloadExampleImages([modelHash]);

                    // Open the example images folder if successful
                    openExampleImagesFolder(modelHash);
                } catch (error) {
                    console.error('Error downloading example images:', error);
                    // Error already shown by the API client
                }
            };
        } else {
            downloadBtn.classList.add('disabled');
            const noRemoteImagesTitle = translate('modelCard.exampleImages.noRemoteImagesAvailable', {}, 'No remote example images available for this model on Civitai');
            downloadBtn.setAttribute('title', noRemoteImagesTitle);
            downloadBtn.onclick = null;
        }
    }

    // Set up import button
    const importBtn = modal.querySelector('#importExamplesBtn');
    if (importBtn) {
        importBtn.onclick = async () => {
            modalManager.closeModal('exampleAccessModal');

            // Get the model data from card dataset (works for both lora and checkpoint)
            const modelMeta = {
                sha256: card.dataset.sha256,
                autov3: card.dataset.autov3 || '',
                preview_url: getCardPreviewUrl(card),
                file_path: card.dataset.filepath,
                model_name: card.dataset.name,
                file_name: card.dataset.file_name,
                folder: card.dataset.folder,
                modified: card.dataset.modified,
                file_size: card.dataset.file_size,
                from_civitai: card.dataset.from_civitai === 'true',
                hf_url: card.dataset.hf_url || '',
                base_model: card.dataset.base_model,
                notes: card.dataset.notes,
                favorite: card.dataset.favorite === 'true',
                civitai: JSON.parse(card.dataset.meta || '{}'),
                tags: JSON.parse(card.dataset.tags || '[]'),
                modelDescription: card.dataset.modelDescription || ''
            };

            // Add usage_tips if present (for lora)
            if (card.dataset.usage_tips) {
                modelMeta.usage_tips = card.dataset.usage_tips;
            }

            // Show the model modal
            await showModelModal(modelMeta, modelType);

            // Reveal the import entry once the modal content has rendered
            setTimeout(() => {
                // Gallery mode: the import button is always visible — expand the zone
                const importBtn = document.querySelector('#modelModal .gallery-import-btn');
                if (importBtn) {
                    importBtn.click();
                    return;
                }
                // Empty state: the import area is the whole tab content — scroll to it
                const importArea = document.querySelector('#modelModal .example-import-area');
                if (importArea) {
                    importArea.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
                }
            }, 500);
        };
    }

    // Show the modal
    modalManager.showModal('exampleAccessModal');
}

export function createModelCard(model, modelType) {
    const card = document.createElement('div');
    card.className = 'model-card';  // Reuse the same class for styling
    // Always draggable (move-to-folder in the sidebar). Accidental micro-drags
    // from click jitter are rendered harmless by the preview-drop handlers
    // below, which ignore internal card drags via MODEL_CARD_DRAG_MIME_TYPE.
    card.draggable = true;
    card.dataset.sha256 = model.sha256;
    card.dataset.autov3 = model.autov3 || '';
    card.dataset.filepath = model.file_path;
    card.dataset.name = model.model_name;
    card.dataset.file_name = model.file_name;
    card.dataset.folder = model.folder || '';
    card.dataset.modified = model.modified;
    card.dataset.file_size = model.file_size;
    card.dataset.from_civitai = model.from_civitai;
    card.dataset.usage_count = String(model.usage_count);
    card.dataset.notes = model.notes || '';
    card.dataset.base_model = model.base_model || 'Unknown';
    card.dataset.favorite = model.favorite ? 'true' : 'false';
    card.dataset.exclude = model.exclude ? 'true' : 'false';
    card.dataset.hf_url = model.hf_url || '';
    const hasUpdateAvailable = Boolean(model.update_available);
    card.dataset.update_available = hasUpdateAvailable ? 'true' : 'false';
    card.dataset.skip_metadata_refresh = model.skip_metadata_refresh ? 'true' : 'false';
    // Store version_count for group-by-model display
    if (model.version_count !== undefined) {
        card.dataset.version_count = model.version_count;
    }

    // To only show usage_count when sorting by usage. 
    const pageState = getCurrentPageState();
    const isUsageSort = pageState?.sortBy?.startsWith('usage');
    const hasUsageCount = isUsageSort && typeof model.usage_count === 'number';

    const civitaiData = model.civitai || {};
    const modelId = civitaiData?.modelId ?? civitaiData?.model_id;
    if (modelId !== undefined && modelId !== null && modelId !== '') {
        card.dataset.modelId = modelId;
    } else if (model.hf_url) {
        // For HF-only models, derive a group key from hf_url for version grouping
        const match = model.hf_url.match(/https?:\/\/huggingface\.co\/([^/]+\/[^/]+)/);
        if (match) {
            card.dataset.modelId = 'hf:' + match[1];
        }
    }

    // LoRA specific data
    if (modelType === MODEL_TYPES.LORA) {
        card.dataset.usage_tips = model.usage_tips;
    }

    // Set sub_type for all model types (lora/locon/dora, checkpoint/diffusion_model, embedding)
    if (model.sub_type) {
        card.dataset.sub_type = model.sub_type;
    }

    // Store metadata if available
    if (model.civitai) {
        card.dataset.meta = JSON.stringify(model.civitai || {});
    }

    // Store tags if available
    if (model.tags && Array.isArray(model.tags)) {
        card.dataset.tags = JSON.stringify(model.tags);
    }

    if (model.modelDescription) {
        card.dataset.modelDescription = model.modelDescription;
    }

    // Store NSFW level if available
    const nsfwLevel = model.preview_nsfw_level !== undefined ? model.preview_nsfw_level : 0;
    card.dataset.nsfwLevel = nsfwLevel;

    // Determine if the preview should be blurred based on NSFW level and user settings
    const matureBlurThreshold = getMatureBlurThreshold(state.settings);
    const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
    if (shouldBlur) {
        card.classList.add('nsfw-content');
    }

    if (model.skip_metadata_refresh) {
        card.classList.add('skip-refresh');
    }
    if (model.exclude) {
        card.classList.add('excluded-model');
    }

    // state.selectedModels resolves to the active page's set (selectedLoras
    // included) - do not narrow this back to selectedLoras/LORA-only.
    if (state.bulkMode && state.selectedModels.has(model.file_path)) {
        card.classList.add('selected');
    }

    // Get the appropriate preview versions map
    const previewVersionsKey = modelType;
    const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map();
    const version = previewVersions.get(model.file_path);
    const previewUrl = model.preview_url || '/loras_static/images/no-preview.png';
    const versionedPreviewUrl = version ? `${previewUrl}${previewUrl.includes('?') ? '&' : '?'}t=${version}` : previewUrl;

    // Determine NSFW warning text based on level with i18n support
    let nsfwText = translate('modelCard.nsfw.matureContent', {}, 'Mature Content');
    if (nsfwLevel >= NSFW_LEVELS.XXX) {
        nsfwText = translate('modelCard.nsfw.xxxRated', {}, 'XXX-rated Content');
    } else if (nsfwLevel >= NSFW_LEVELS.X) {
        nsfwText = translate('modelCard.nsfw.xRated', {}, 'X-rated Content');
    } else if (nsfwLevel >= NSFW_LEVELS.R) {
        nsfwText = translate('modelCard.nsfw.rRated', {}, 'R-rated Content');
    }

    // Check if autoplayOnHover is enabled for video previews
    const autoplayOnHover = state.global?.settings?.autoplay_on_hover || false;
    const isVideo = previewUrl.endsWith('.mp4') || previewUrl.endsWith('.webm');
    const videoAttrs = [
        'controls',
        'muted',
        'loop',
        'playsinline',
        'preload="none"',
        `data-src="${versionedPreviewUrl}"`
    ];

    if (!autoplayOnHover) {
        videoAttrs.push('data-autoplay="true"');
    }

    // Get favorite status from model data
    const isFavorite = model.favorite === true;
    if (hasUpdateAvailable) {
        card.classList.add('has-update');
    }

    // Generate action icons based on model type with i18n support
    const favoriteTitle = isFavorite ?
        translate('modelCard.actions.removeFromFavorites', {}, 'Remove from favorites') :
        translate('modelCard.actions.addToFavorites', {}, 'Add to favorites');
    const globeTitle = model.from_civitai ?
        translate('modelCard.actions.viewOnCivitai', {}, 'View on Civitai') :
        model.hf_url ?
            translate('modelCard.actions.viewOnHuggingFace', {}, 'View on Hugging Face') :
            translate('modelCard.actions.notAvailableFromCivitai', {}, 'Not available from Civitai');
    const globeEnabled = model.from_civitai || !!model.hf_url;
    let sendTitle;
    let copyTitle;
    if (modelType === MODEL_TYPES.LORA) {
        sendTitle = translate('modelCard.actions.sendToWorkflow', {}, 'Send to ComfyUI (Click: Append, Shift+Click: Replace)');
        copyTitle = translate('modelCard.actions.copyLoRASyntax', {}, 'Copy LoRA Syntax');
    } else if (modelType === MODEL_TYPES.CHECKPOINT) {
        sendTitle = translate('modelCard.actions.sendCheckpointToWorkflow', {}, 'Send to ComfyUI');
        copyTitle = translate('modelCard.actions.copyCheckpointName', {}, 'Copy checkpoint name');
    } else if (modelType === MODEL_TYPES.EMBEDDING) {
        sendTitle = translate('modelCard.actions.sendEmbeddingToWorkflow', {}, 'Send to ComfyUI');
        copyTitle = translate('modelCard.actions.copyEmbeddingName', {}, 'Copy embedding name');
    } else {
        sendTitle = translate('modelCard.actions.sendToWorkflow', {}, 'Send to ComfyUI');
        copyTitle = translate('modelCard.actions.copyLoRASyntax', {}, 'Copy value');
    }

    const updateBadgeLabel = translate('modelCard.badges.update', {}, 'Update');
    const updateBadgeTooltip = translate('modelCard.badges.updateAvailable', {}, 'Update available');
    const actionIcons = `
        <i class="${isFavorite ? 'fas fa-star favorite-active' : 'far fa-star'}" 
           title="${favoriteTitle}">
        </i>
        <i class="fas fa-globe" 
           title="${globeTitle}"
           ${!globeEnabled ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}>
        </i>
        <i class="fas fa-paper-plane" 
           title="${sendTitle}">
        </i>
        <i class="fas fa-copy" 
           title="${copyTitle}">
        </i>`;

    // Generate UI text with i18n support
    const toggleBlurTitle = translate('modelCard.actions.toggleBlur', {}, 'Toggle blur');
    const showButtonText = translate('modelCard.actions.show', {}, 'Show');
    const footerActionSetting = state.global.settings.model_card_footer_action || 'example_images';
    const footerActionTitle = footerActionSetting === 'replace_preview'
        ? translate('modelCard.actions.replacePreview', {}, 'Replace Preview')
        : translate('modelCard.actions.openExampleImages', {}, 'Open Example Images Folder');
    const footerActionIcon = footerActionSetting === 'replace_preview'
        ? 'fas fa-image'
        : 'fas fa-folder-open';

    const baseModelLabel = model.base_model || 'Unknown';
    const baseModelAbbreviation = getBaseModelAbbreviation(baseModelLabel);

    // Sub-type display (e.g., LoRA, LyCO, DoRA, CKPT, DM, EMB)
    const subType = model.sub_type || '';
    const subTypeAbbreviation = getSubTypeAbbreviation(subType);
    const fullSubTypeName = MODEL_SUBTYPE_DISPLAY_NAMES[subType?.toLowerCase()] || subType || '';

    card.innerHTML = `
        <div class="card-preview ${shouldBlur ? 'blurred' : ''}">
            ${isVideo ?
            `<video ${videoAttrs.join(' ')} style="pointer-events: none;"></video>` :
            `<img draggable="false" src="${versionedPreviewUrl}" alt="${model.model_name}" onerror="this.onerror=null; this.src='/loras_static/images/no-preview.png'">`
        }
            <div class="card-header">
                ${shouldBlur ?
            `<button class="toggle-blur-btn" title="${toggleBlurTitle}">
                      <i class="fas fa-eye"></i>
                  </button>` : ''}
                <div class="card-header-info">
                    <span class="base-model-label ${shouldBlur ? 'with-toggle' : ''}"
                          title="${fullSubTypeName ? fullSubTypeName + ' | ' : ''}${baseModelLabel}">
                        ${subTypeAbbreviation ? `<span class="model-sub-type">${subTypeAbbreviation}</span>` : ''}
                        ${subTypeAbbreviation ? `<span class="model-separator"></span>` : ''}
                        <span class="model-base-type">${baseModelAbbreviation}</span>
                    </span>
                    ${hasUpdateAvailable ? `
                        <span class="model-update-badge" title="${updateBadgeTooltip}">
                            <i class="fas fa-arrow-up"></i>
                        </span>
                    ` : ''}
                    ${model.skip_metadata_refresh ? `
                        <span class="model-skip-refresh-badge" title="${translate('modelCard.badges.skipRefresh', {}, 'Metadata refresh skipped')}">
                            <i class="fas fa-ban"></i>
                        </span>
                    ` : ''}
                    ${model.exclude ? `
                        <span class="model-excluded-badge" title="${translate('globalContextMenu.manageExcludedModels.label', {}, 'Excluded Models')}">
                            <i class="fas fa-eye-slash"></i>
                        </span>
                    ` : ''}
                </div>
                <div class="card-actions">
                    ${actionIcons}
                </div>
            </div>
            ${shouldBlur ? `
                <div class="nsfw-overlay">
                    <div class="nsfw-warning">
                        <p>${nsfwText}</p>
                        <button class="show-content-btn">${showButtonText}</button>
                    </div>
                </div>
            ` : ''}
            <div class="card-footer">
                <div class="model-info">
                    <span class="model-name" title="${getDisplayName(model).replace(/"/g, '&quot;')}">${getDisplayName(model)}</span>
                    <div class="version-row">
                        ${(() => {
                            const autoTags = model.auto_tags || [];
                            const hlTags = autoTags.filter(t => t === 'HIGH' || t === 'LOW');
                            const hasVersionName = model.civitai?.name;
                            // When group_by_model is active and model has multiple versions,
                            // show clickable version count instead of version name (and hide badges)
                            const isGroupByModel = state.global.settings.group_by_model;
                            const versionCount = model.version_count;
                            const showVersionCount = isGroupByModel && versionCount > 1;
                            if (!hlTags.length && !hasVersionName && !showVersionCount) return '';
                            const density = state.global.settings.display_density || 'default';
                            const shortLabels = density === 'medium' || density === 'compact';
                            // Don't show HIGH/LOW badges when showing version count (confusing in grouped mode)
                            const badges = !showVersionCount ? hlTags.map(t => {
                                const cls = t === 'HIGH' ? 'hl-badge hl-badge--high' : 'hl-badge hl-badge--low';
                                const label = shortLabels ? (t === 'HIGH' ? 'H' : 'L') : t;
                                const titleAttr = shortLabels ? ` title="${t}"` : '';
                                return `<span class="${cls}"${titleAttr}>${label}</span>`;
                            }).join('') : '';
                            let versionHtml = '';
                            if (showVersionCount) {
                                const countLabel = translate('modelCard.footer.versionCount', { count: versionCount }, `${versionCount} versions`);
                                versionHtml = `<span class="version-count-link" title="${translate('modelCard.footer.viewAllVersions', {}, 'View all local versions')}">${countLabel}</span>`;
                            } else if (hasVersionName) {
                                versionHtml = `<span class="version-name civitai-version">${model.civitai.name}</span>`;
                            }
                            return `<span class="badge-version-unit">${badges}${versionHtml}</span>`;
                        })()}
                        ${hasUsageCount ? `<span class="version-name" title="${translate('modelCard.usage.timesUsed', {}, 'Times used')}">${model.usage_count}×</span>` : ''}
                    </div>
                </div>
                <div class="card-actions">
                    <i class="${footerActionIcon}" 
                       title="${footerActionTitle}">
                    </i>
                </div>
            </div>
        </div>
    `;

    // Add video auto-play on hover functionality if needed
    const videoElement = card.querySelector('video');
    if (videoElement) {
        configureModelCardVideo(videoElement, autoplayOnHover);
    }

    // Dropping an image/video onto the card replaces the model preview via the
    // existing replace-preview endpoint (overwrites file on disk, refreshes card).
    // Internal card drags (move-to-folder) are tagged with a custom MIME type by
    // SidebarManager and must be ignored here entirely: no highlight, no upload.
    const isInternalCardDrag = (event) =>
        Boolean(event.dataTransfer?.types?.includes(MODEL_CARD_DRAG_MIME_TYPE));

    const preventDragDefaults = (event) => {
        event.preventDefault();
        event.stopPropagation();
    };

    ['dragenter', 'dragover'].forEach((eventName) => {
        card.addEventListener(eventName, (event) => {
            if (isInternalCardDrag(event)) return;
            preventDragDefaults(event);
            card.classList.add('drag-over');
        });
    });

    card.addEventListener('dragleave', (event) => {
        if (isInternalCardDrag(event)) return;
        preventDragDefaults(event);
        card.classList.remove('drag-over');
    });

    card.addEventListener('drop', (event) => {
        if (isInternalCardDrag(event)) return;
        preventDragDefaults(event);
        card.classList.remove('drag-over');

        const files = event.dataTransfer?.files;
        if (!files || files.length === 0) return;

        const file = files[0];
        // Keep in sync with the accept list of the preview file picker (image/* + video/mp4).
        if (!file.type.startsWith('image/') && file.type !== 'video/mp4') {
            showToast('toast.api.previewDropInvalid', { name: file.name || '' }, 'error');
            return;
        }

        const filePath = card.dataset.filepath;
        if (!filePath) return;

        // uploadPreview handles loading state, card refresh and error toasts internally.
        getModelApiClient().uploadPreview(filePath, file);
    });

    return card;
}

const VIDEO_LAZY_ROOT_MARGIN = '200px 0px';
const VIDEO_LOAD_INTERVAL_MS = 120;
const VIDEO_LOAD_MAX_CONCURRENCY = 2;
let videoLazyObserver = null;

const videoLoadQueue = [];
const queuedVideoElements = new Set();
let activeVideoLoads = 0;
let queueTimer = null;

const scheduleFrame = typeof requestAnimationFrame === 'function'
    ? requestAnimationFrame
    : (callback) => setTimeout(callback, 16);

function scheduleVideoQueueProcessing(delay = 0) {
    if (queueTimer !== null) {
        return;
    }

    queueTimer = setTimeout(() => {
        queueTimer = null;
        processVideoLoadQueue();
    }, delay);
}

function dequeueVideoElement(videoElement) {
    if (!queuedVideoElements.has(videoElement)) {
        return;
    }

    queuedVideoElements.delete(videoElement);
    const index = videoLoadQueue.indexOf(videoElement);
    if (index !== -1) {
        videoLoadQueue.splice(index, 1);
    }
}

function processVideoLoadQueue() {
    if (videoLoadQueue.length === 0) {
        return;
    }

    while (activeVideoLoads < VIDEO_LOAD_MAX_CONCURRENCY && videoLoadQueue.length > 0) {
        const videoElement = videoLoadQueue.shift();
        queuedVideoElements.delete(videoElement);

        if (!videoElement || !videoElement.isConnected || videoElement.dataset.loaded === 'true') {
            continue;
        }

        activeVideoLoads++;
        videoElement.dataset.loading = 'true';

        scheduleFrame(() => {
            try {
                loadVideoSource(videoElement);
            } finally {
                delete videoElement.dataset.loading;
                activeVideoLoads--;

                if (videoLoadQueue.length > 0) {
                    scheduleVideoQueueProcessing(VIDEO_LOAD_INTERVAL_MS);
                }
            }
        });
    }

    if (videoLoadQueue.length > 0 && queueTimer === null) {
        scheduleVideoQueueProcessing(VIDEO_LOAD_INTERVAL_MS);
    }
}

function enqueueVideoElement(videoElement) {
    if (!videoElement || videoElement.dataset.loaded === 'true' || videoElement.dataset.loading === 'true') {
        return;
    }

    if (!videoElement.isConnected) {
        return;
    }

    if (queuedVideoElements.has(videoElement)) {
        return;
    }

    queuedVideoElements.add(videoElement);
    videoLoadQueue.push(videoElement);
    scheduleVideoQueueProcessing();
}

function ensureVideoLazyObserver() {
    if (videoLazyObserver) {
        return videoLazyObserver;
    }

    videoLazyObserver = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                enqueueVideoElement(entry.target);
            }
        });
    }, {
        root: null,
        rootMargin: VIDEO_LAZY_ROOT_MARGIN,
        threshold: 0.01
    });

    return videoLazyObserver;
}

function cleanupHoverHandlers(videoElement) {
    const handlers = videoElement._hoverHandlers;
    if (!handlers) return;

    const { cardPreview, mouseEnter, mouseLeave } = handlers;
    if (cardPreview) {
        cardPreview.removeEventListener('mouseenter', mouseEnter);
        cardPreview.removeEventListener('mouseleave', mouseLeave);
    }

    delete videoElement._hoverHandlers;
}

function requestSafePlay(videoElement) {
    const playPromise = videoElement.play();
    if (playPromise && typeof playPromise.catch === 'function') {
        playPromise.catch(() => { });
    }
}

function loadVideoSource(videoElement) {
    if (!videoElement) {
        return false;
    }

    if (videoLazyObserver) {
        try {
            videoLazyObserver.unobserve(videoElement);
        } catch (error) {
            // Ignore observer errors (e.g., element already unobserved)
        }
    }

    if (videoElement.dataset.loaded === 'true' || !videoElement.isConnected) {
        return false;
    }

    const sourceElement = videoElement.querySelector('source');
    const dataSrc = videoElement.dataset.src || sourceElement?.dataset?.src;

    if (!dataSrc) {
        return false;
    }

    // Ensure src attributes are reset before applying
    videoElement.removeAttribute('src');
    if (sourceElement) {
        sourceElement.src = dataSrc;
    } else {
        videoElement.src = dataSrc;
    }

    videoElement.load();
    videoElement.dataset.loaded = 'true';

    if (videoElement.dataset.autoplay === 'true') {
        videoElement.setAttribute('autoplay', '');
        requestSafePlay(videoElement);
    }

    return true;
}

export function configureModelCardVideo(videoElement, autoplayOnHover) {
    if (!videoElement) return;

    dequeueVideoElement(videoElement);
    cleanupHoverHandlers(videoElement);

    const sourceElement = videoElement.querySelector('source');
    const existingSrc = videoElement.dataset.src || sourceElement?.dataset?.src || videoElement.currentSrc;

    if (existingSrc && !videoElement.dataset.src) {
        videoElement.dataset.src = existingSrc;
    }

    if (sourceElement && !sourceElement.dataset.src) {
        sourceElement.dataset.src = videoElement.dataset.src || sourceElement.src;
    }

    videoElement.removeAttribute('autoplay');
    videoElement.removeAttribute('src');
    videoElement.setAttribute('preload', 'none');
    videoElement.setAttribute('muted', '');
    videoElement.setAttribute('loop', '');
    videoElement.setAttribute('playsinline', '');
    videoElement.setAttribute('controls', '');
    videoElement.dataset.loaded = 'false';
    delete videoElement.dataset.loading;

    if (sourceElement) {
        sourceElement.removeAttribute('src');
        if (videoElement.dataset.src) {
            sourceElement.dataset.src = videoElement.dataset.src;
        }
    }

    if (!autoplayOnHover) {
        videoElement.dataset.autoplay = 'true';
    } else {
        delete videoElement.dataset.autoplay;
    }

    const observer = ensureVideoLazyObserver();
    observer.observe(videoElement);

    // Pause the video until it is either hovered or autoplay kicks in
    try {
        videoElement.pause();
    } catch (err) {
        // Ignore pause errors (e.g., if not loaded yet)
    }

    if (autoplayOnHover) {
        const cardPreview = videoElement.closest('.card-preview');
        if (cardPreview) {
            const mouseEnter = () => {
                dequeueVideoElement(videoElement);
                loadVideoSource(videoElement);
                requestSafePlay(videoElement);
            };
            const mouseLeave = () => {
                videoElement.pause();
                videoElement.currentTime = 0;
            };

            cardPreview.addEventListener('mouseenter', mouseEnter);
            cardPreview.addEventListener('mouseleave', mouseLeave);

            videoElement._hoverHandlers = { cardPreview, mouseEnter, mouseLeave };
        }
    }
}

// Add a method to update card appearance based on bulk mode (LoRA only)
export function updateCardsForBulkMode(isBulkMode) {
    // Update the state
    state.bulkMode = isBulkMode;

    document.body.classList.toggle('bulk-mode', isBulkMode);

    // Get all lora cards - this can now be from the DOM or through the virtual scroller
    const loraCards = document.querySelectorAll('.model-card');

    loraCards.forEach(card => {
        // Get all action containers for this card
        const actions = card.querySelectorAll('.card-actions');

        // Handle display property based on mode
        if (isBulkMode) {
            // Hide actions when entering bulk mode
            actions.forEach(actionGroup => {
                actionGroup.style.display = 'none';
            });
        } else {
            // Ensure actions are visible when exiting bulk mode
            actions.forEach(actionGroup => {
                // We need to reset to default display style which is flex
                actionGroup.style.display = 'flex';
            });
        }
    });

    // If using virtual scroller, we need to rerender after toggling bulk mode
    if (state.virtualScroller && typeof state.virtualScroller.scheduleRender === 'function') {
        state.virtualScroller.scheduleRender();
    }

    // Apply selection state to cards if entering bulk mode
    if (isBulkMode) {
        bulkManager.applySelectionState();
    }
}
