import React, { useEffect, useMemo, useState, useRef, useCallback } from 'react';
import { Upload, Mic, Settings2, X, Play, MoreHorizontal, Wand2, Download, RefreshCw, Trash2, Gamepad2, Pause, History, Lock, Unlock, Zap, Grid2X2, Image, Sliders, ChevronRight, ChevronDown, Maximize2, Info, Film, ArrowUp, Loader2, Sparkles } from 'lucide-react';
import { upscaleImage } from './enhance/upscaleApi';
import { resolveFileUrl } from './resolveFileUrl';
import { getCurrentUserId, registerUserScopedKey, userScopedKey, } from './lib/userScopedStorage';
// Register the gallery base key so AuthGate can namespace / clear it per user.
registerUserScopedKey('homepilot_imagine_items');
/**
 * Resolve the localStorage key the Imagine gallery should read/write for the
 * current authenticated user. Falls back to an anonymous bucket when no
 * user is active — this keeps SSR/boot paths safe and prevents any user's
 * gallery from being written to the un-scoped global slot.
 */
function imagineItemsKey() {
    const uid = getCurrentUserId();
    return uid
        ? userScopedKey('homepilot_imagine_items', uid)
        : 'homepilot_imagine_items:user:anon';
}
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
// Explicit generous deadline for generation requests. Cold-start
// model loads + busy ComfyUI queues can legitimately take several
// minutes; the browser's default fetch behaviour (either no
// timeout or a very long one) leaves the UI in limbo. 10 minutes
// matches the backend's COMFY_POLL_MAX_S so the two sides agree
// on "when does this request really count as failed".
const POSTJSON_DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
async function postJson(baseUrl, path, body, apiKey, opts) {
    const url = `${baseUrl.replace(/\/+$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
    // Compose a deadline-based AbortSignal with an optional caller
    // signal so a user-clicked Cancel also aborts. Deadline fires
    // only if the backend genuinely never responds within the
    // generous window above — matches best-practice long-running
    // API patterns (AWS Bedrock async, Replicate, OpenAI Images).
    const controller = new AbortController();
    const timeoutMs = opts?.timeoutMs ?? POSTJSON_DEFAULT_TIMEOUT_MS;
    const deadlineTimer = window.setTimeout(() => controller.abort(), timeoutMs);
    const callerSignal = opts?.signal;
    if (callerSignal) {
        if (callerSignal.aborted)
            controller.abort();
        else
            callerSignal.addEventListener('abort', () => controller.abort());
    }
    try {
        const res = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...(apiKey ? { 'x-api-key': apiKey } : {}),
            },
            credentials: 'include',
            body: JSON.stringify(body),
            signal: controller.signal,
        });
        if (!res.ok) {
            const text = await res.text().catch(() => '');
            throw new Error(`HTTP ${res.status} ${res.statusText}${text ? `: ${text}` : ''}`);
        }
        return (await res.json());
    }
    finally {
        window.clearTimeout(deadlineTimer);
    }
}
async function deleteJson(baseUrl, path, body, apiKey) {
    const url = `${baseUrl.replace(/\/+$/, '')}${path.startsWith('/') ? path : `/${path}`}`;
    const res = await fetch(url, {
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/json',
            ...(apiKey ? { 'x-api-key': apiKey } : {}),
        },
        credentials: 'include',
        body: JSON.stringify(body),
    });
    if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`HTTP ${res.status} ${res.statusText}${text ? `: ${text}` : ''}`);
    }
    return (await res.json());
}
function uid() {
    return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
const ASPECT_RATIOS = [
    { label: '1:1', previewW: 24, previewH: 24, genW: 1024, genH: 1024 },
    { label: '4:3', previewW: 32, previewH: 24, genW: 1152, genH: 864 },
    { label: '3:4', previewW: 24, previewH: 32, genW: 864, genH: 1152 },
    { label: '16:9', previewW: 42, previewH: 24, genW: 1344, genH: 768 },
    { label: '9:16', previewW: 24, previewH: 42, genW: 768, genH: 1344 },
];
// -----------------------------------------------------------------------------
// Component
// -----------------------------------------------------------------------------
export default function ImagineView(props) {
    const authKey = (props.apiKey || '').trim();
    const [prompt, setPrompt] = useState('');
    // Load items from localStorage on mount.
    // Scoped per authenticated user so switching accounts never surfaces
    // another user's gallery thumbnails (whose /files/ URLs would 403/404
    // and produce the ghost-card bug from issue: cross-account UI leak).
    const [items, setItems] = useState(() => {
        try {
            const lsKey = imagineItemsKey();
            let stored = localStorage.getItem(lsKey);
            // One-time migration: if the user has no scoped gallery yet but the
            // legacy global slot still holds data, adopt it into the scoped slot.
            // This preserves galleries for the currently-logged-in user when the
            // fix is first deployed; the global slot is then cleared so future
            // logins don't inherit anyone's data.
            if (!stored) {
                const legacy = localStorage.getItem('homepilot_imagine_items');
                if (legacy && getCurrentUserId()) {
                    localStorage.setItem(lsKey, legacy);
                    localStorage.removeItem('homepilot_imagine_items');
                    stored = legacy;
                }
            }
            if (stored) {
                const parsed = JSON.parse(stored);
                if (Array.isArray(parsed)) {
                    // Thresholds for handling stale processing items:
                    // - IN_FLIGHT_THRESHOLD: If processing item is older than this, generation likely
                    //   completed but state update was lost due to tab switch (component unmount).
                    //   Image generation typically takes 3-5 seconds, so 15s is safe.
                    // - STALE_THRESHOLD: If processing item is older than this, app was closed during
                    //   generation - mark as definitively failed.
                    const IN_FLIGHT_THRESHOLD = 15 * 1000; // 15 seconds - likely completed but lost track
                    const STALE_THRESHOLD = 10 * 60 * 1000; // 10 minutes - definitively failed
                    const now = Date.now();
                    return parsed.map((item) => {
                        // Migrate: old items without status get 'done' if they have url
                        if (!item.status) {
                            return { ...item, status: item.url ? 'done' : 'failed' };
                        }
                        // Handle processing items based on age
                        if (item.status === 'processing') {
                            const age = now - item.createdAt;
                            // Very old items (app was closed) - mark as failed
                            if (age > STALE_THRESHOLD) {
                                return { ...item, status: 'failed', error: 'Generation interrupted - please retry' };
                            }
                            // Medium-age items (tab was switched) - likely completed but we lost track
                            // Mark as failed since we can't verify the result without backend check
                            if (age > IN_FLIGHT_THRESHOLD) {
                                return {
                                    ...item,
                                    status: 'failed',
                                    error: 'Generation may have completed - check your gallery or retry'
                                };
                            }
                        }
                        // Keep recent processing items as-is (still in-flight)
                        return item;
                    });
                }
            }
        }
        catch (error) {
            console.error('Failed to load imagine items from localStorage:', error);
        }
        return [];
    });
    const [isGenerating, setIsGenerating] = useState(false);
    const [upscalingId, setUpscalingId] = useState(null);
    // Selection state for Lightbox (Grok-style detail view)
    const [selectedImage, setSelectedImage] = useState(null);
    const [showDetails, setShowDetails] = useState(false); // Immersive mode: details hidden by default
    const [lightboxPrompt, setLightboxPrompt] = useState(''); // Editable prompt in lightbox
    const [isRegenerating, setIsRegenerating] = useState(false); // Regenerating from lightbox
    const [regenProgress, setRegenProgress] = useState(null); // Progress 0-100 during regen
    const [regenAbortController, setRegenAbortController] = useState(null); // For cancellation
    // Fullscreen + navigation (additive)
    const lightboxRef = useRef(null);
    const [isFullscreen, setIsFullscreen] = useState(false);
    const [fullscreenStretch, setFullscreenStretch] = useState(false); // false = fit (contain), true = fill (cover/crop)
    // Track selected index for arrow navigation
    const selectedIndex = useMemo(() => {
        if (!selectedImage)
            return -1;
        return items.findIndex((it) => it.id === selectedImage.id);
    }, [items, selectedImage]);
    // Detect newly completed images for slideshow
    const prevItemsRef = useRef(items);
    const [aspect, setAspect] = useState('1:1');
    const [showAspectPanel, setShowAspectPanel] = useState(false);
    const [compatibleAspectRatios, setCompatibleAspectRatios] = useState([]);
    const [defaultAspectRatio, setDefaultAspectRatio] = useState('1:1');
    // Number of images to generate per request (1, 2, or 4 like Grok)
    const [numImages, setNumImages] = useState(1);
    // Game Mode state
    const [gameMode, setGameMode] = useState(false);
    const [useGlobalLLMForVariations, setUseGlobalLLMForVariations] = useState(false);
    const [gameSessionId, setGameSessionId] = useState(null);
    const [gameStrength, setGameStrength] = useState(0.65);
    const [spicyStrength, setSpicyStrength] = useState(0.3); // Spicy strength (only when nsfwMode + gameMode)
    const [gameLocks, setGameLocks] = useState({
        lock_world: true,
        lock_style: true,
        lock_subject_type: true,
        lock_main_character: false,
        lock_palette: false,
        lock_time_of_day: false,
    });
    const [isAutoGenerating, setIsAutoGenerating] = useState(false);
    const [showGamePanel, setShowGamePanel] = useState(false);
    const [gameVariations, setGameVariations] = useState([]);
    const [showVariationHistory, setShowVariationHistory] = useState(false);
    const autoGenerateRef = useRef(false);
    // Advanced Settings Panel state
    const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
    const [advancedMode, setAdvancedMode] = useState(false);
    const [customSteps, setCustomSteps] = useState(30);
    const [customCfg, setCustomCfg] = useState(5.5);
    const [customDenoise, setCustomDenoise] = useState(0.55);
    const [seedLock, setSeedLock] = useState(false);
    const [customSeed, setCustomSeed] = useState(0);
    const [useControlNet, setUseControlNet] = useState(false);
    const [cnStrength, setCnStrength] = useState(1.0);
    // Resolution override state (preset-based, additive — only dims change)
    const [resolutionTiers, setResolutionTiers] = useState([]);
    const [selectedResTier, setSelectedResTier] = useState(''); // empty = current preset (no override)
    const [resolutionMode, setResolutionMode] = useState('auto'); // auto = preset default, override = show grid
    const [presetResolutions, setPresetResolutions] = useState({}); // { aspect: { preset: {w,h} } }
    const [presetUiLabels, setPresetUiLabels] = useState({}); // { preset: { label } }
    // Reference Image state (for img2img similar generation)
    const referenceInputRef = useRef(null);
    const promptInputRef = useRef(null);
    const [referenceUrl, setReferenceUrl] = useState(null);
    const [referenceStrength, setReferenceStrength] = useState(0.35); // 0=very similar, 1=more creative
    const [isUploadingReference, setIsUploadingReference] = useState(false);
    // Ref for auto-scrolling to top when new images are added (Grok-style)
    const gridStartRef = useRef(null);
    // Save items to localStorage whenever they change (user-scoped slot).
    useEffect(() => {
        try {
            localStorage.setItem(imagineItemsKey(), JSON.stringify(items));
        }
        catch (error) {
            console.error('Failed to save imagine items to localStorage:', error);
        }
    }, [items]);
    // Fail-closed rendering: when a gallery image fails to load (403/404 from
    // cross-account or deleted asset URLs), we drop the item from the current
    // view AND persist the drop so the ghost card doesn't re-appear on next
    // mount. This complements server-side ownership checks on /files/.
    const [brokenIds, setBrokenIds] = useState(() => new Set());
    const markImageBroken = useCallback((id) => {
        setBrokenIds((prev) => {
            if (prev.has(id))
                return prev;
            const next = new Set(prev);
            next.add(id);
            return next;
        });
        setItems((prev) => prev.filter((it) => it.id !== id));
    }, []);
    // On mount, periodically check localStorage for items that completed while unmounted
    // This handles the race condition where API completes after component remounts
    useEffect(() => {
        const processingItems = items.filter(item => item.status === 'processing');
        if (processingItems.length === 0)
            return;
        // Check localStorage for updates and sync state
        const syncFromLocalStorage = () => {
            try {
                const stored = localStorage.getItem(imagineItemsKey());
                if (!stored)
                    return false;
                const storedItems = JSON.parse(stored);
                const storedMap = new Map(storedItems.map(item => [item.id, item]));
                let hasUpdates = false;
                setItems(prev => {
                    const updated = prev.map(item => {
                        if (item.status !== 'processing')
                            return item;
                        // Check if this item was completed in localStorage (by another instance)
                        const storedItem = storedMap.get(item.id);
                        if (storedItem && storedItem.status === 'done' && storedItem.url) {
                            console.log('[Imagine] Synced completed item from localStorage:', item.id);
                            hasUpdates = true;
                            return storedItem;
                        }
                        // Check if this processing item is now too old
                        const age = Date.now() - item.createdAt;
                        if (age > 15 * 1000) {
                            hasUpdates = true;
                            return {
                                ...item,
                                status: 'failed',
                                error: 'Generation may have completed - check your gallery or retry'
                            };
                        }
                        // Continue progress simulation
                        return {
                            ...item,
                            progress: Math.min(90, (item.progress || 0) + Math.random() * 5 + 1)
                        };
                    });
                    return updated;
                });
                return hasUpdates;
            }
            catch (e) {
                console.error('[Imagine] Failed to sync from localStorage:', e);
                return false;
            }
        };
        // Run sync check periodically
        const progressInterval = setInterval(() => {
            syncFromLocalStorage();
        }, 500);
        // Also run immediately on mount
        syncFromLocalStorage();
        return () => clearInterval(progressInterval);
        // Only run on mount (empty deps) - items will be stale but that's intentional
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    // Scroll to top when entering Imagine to avoid "empty space above" if previously scrolled
    useEffect(() => {
        gridStartRef.current?.scrollIntoView({ block: 'start' });
    }, []);
    // Initialize lightbox prompt when selecting an image
    useEffect(() => {
        if (selectedImage) {
            setLightboxPrompt(selectedImage.prompt);
        }
    }, [selectedImage]);
    // PATCH B — Track fullscreen state
    useEffect(() => {
        const onFsChange = () => {
            setIsFullscreen(Boolean(document.fullscreenElement));
        };
        document.addEventListener("fullscreenchange", onFsChange);
        return () => document.removeEventListener("fullscreenchange", onFsChange);
    }, []);
    // PATCH C — Keyboard navigation (ArrowLeft/Right + Esc)
    useEffect(() => {
        if (!selectedImage)
            return;
        const onKeyDown = (e) => {
            // Don't hijack typing in inputs
            const tag = e.target?.tagName?.toLowerCase();
            if (tag === "input" || tag === "textarea")
                return;
            if (e.key === "Escape") {
                setSelectedImage(null);
                setShowDetails(false);
                if (document.fullscreenElement) {
                    document.exitFullscreen().catch(() => { });
                }
                return;
            }
            if (e.key === "ArrowLeft") {
                e.preventDefault();
                const i = selectedIndex;
                if (i >= 0 && i < items.length - 1) {
                    // Items are newest-first; ArrowLeft = older
                    setSelectedImage(items[i + 1]);
                }
            }
            if (e.key === "ArrowRight") {
                e.preventDefault();
                const i = selectedIndex;
                if (i > 0) {
                    // ArrowRight = newer
                    setSelectedImage(items[i - 1]);
                }
            }
        };
        window.addEventListener("keydown", onKeyDown);
        return () => window.removeEventListener("keydown", onKeyDown);
    }, [selectedImage, selectedIndex, items]);
    // PATCH D — Slideshow auto-advance (Game Mode + Auto + Fullscreen)
    useEffect(() => {
        if (!selectedImage) {
            prevItemsRef.current = items;
            return;
        }
        if (!(gameMode && isAutoGenerating && isFullscreen)) {
            prevItemsRef.current = items;
            return;
        }
        const prev = prevItemsRef.current;
        prevItemsRef.current = items;
        // Find newest DONE image now vs before
        const newestNow = items.find((it) => it.status === "done" && it.url);
        if (!newestNow)
            return;
        const newestPrev = prev.find((it) => it.status === "done" && it.url);
        // If there was no previous done, or newest changed -> advance
        if (!newestPrev || newestPrev.id !== newestNow.id) {
            setSelectedImage(newestNow);
        }
    }, [items, selectedImage, gameMode, isAutoGenerating, isFullscreen]);
    // Helper function to get preview dimensions for aspect ratio
    const getPreviewDimension = (ratioId, dim) => {
        const previewMap = {
            '1:1': { width: 24, height: 24 },
            '4:3': { width: 32, height: 24 },
            '3:4': { width: 24, height: 32 },
            '16:9': { width: 42, height: 24 },
            '9:16': { width: 24, height: 42 },
        };
        return previewMap[ratioId]?.[dim] ?? 24;
    };
    // Fetch compatible aspect ratios when model changes
    useEffect(() => {
        const fetchImagePresets = async () => {
            try {
                const base = props.backendUrl.replace(/\/+$/, '');
                // Detect model architecture from model filename
                const model = props.modelImages || '';
                let arch = 'sdxl'; // default
                const lowerModel = model.toLowerCase();
                if (lowerModel.includes('flux') && lowerModel.includes('schnell')) {
                    arch = 'flux_schnell';
                }
                else if (lowerModel.includes('flux')) {
                    arch = 'flux_dev';
                }
                else if (lowerModel.includes('sdxl') || lowerModel.includes('xl') || lowerModel.includes('pony')) {
                    arch = 'sdxl';
                }
                else if (lowerModel.includes('sd15') || lowerModel.includes('dreamshaper') || lowerModel.includes('realistic')) {
                    arch = 'sd15';
                }
                const url = `${base}/image-presets?model=${arch}&preset=${props.imgPreset || 'med'}`;
                const res = await fetch(url, {
                    headers: authKey ? { 'x-api-key': authKey } : undefined,
                });
                if (res.ok) {
                    const data = await res.json();
                    if (data.compatible_aspect_ratios && Array.isArray(data.compatible_aspect_ratios)) {
                        const mappedRatios = data.compatible_aspect_ratios.map((ar) => ({
                            id: ar.id,
                            label: ar.label?.replace(/\s*\([^)]*\)/g, '') || ar.id,
                            previewW: getPreviewDimension(ar.id, 'width'),
                            previewH: getPreviewDimension(ar.id, 'height'),
                            dimensions: ar.dimensions,
                        }));
                        setCompatibleAspectRatios(mappedRatios);
                        // Set default aspect ratio from API
                        if (data.default_aspect_ratio) {
                            setDefaultAspectRatio(data.default_aspect_ratio);
                            // Only change current aspect if it's not in the compatible list
                            if (!mappedRatios.find(r => r.id === aspect)) {
                                setAspect(data.default_aspect_ratio);
                            }
                        }
                    }
                    // Store preset resolutions for resolution grid in Advanced Controls
                    if (data.preset_resolutions) {
                        setPresetResolutions(data.preset_resolutions);
                    }
                    if (data.preset_ui) {
                        setPresetUiLabels(data.preset_ui);
                    }
                }
            }
            catch (err) {
                console.error('[Imagine] Failed to fetch image presets:', err);
                // Fallback to default aspect ratios
                const fallbackRatios = ASPECT_RATIOS.map(r => ({
                    id: r.label,
                    label: r.label,
                    previewW: r.previewW,
                    previewH: r.previewH,
                    dimensions: { width: r.genW, height: r.genH },
                }));
                setCompatibleAspectRatios(fallbackRatios);
            }
        };
        fetchImagePresets();
    }, [props.modelImages, props.imgPreset, props.backendUrl, authKey]);
    // Recompute resolution tiers when aspect ratio changes
    // Uses preset_resolutions from the API (computed from model_config.get_model_settings)
    useEffect(() => {
        const arPresets = presetResolutions[aspect];
        if (!arPresets || Object.keys(arPresets).length === 0) {
            setResolutionTiers([]);
            return;
        }
        const tierOrder = ['low', 'med', 'high', 'ultra'];
        const mapped = tierOrder
            .filter((tid) => arPresets[tid]?.width && arPresets[tid]?.height)
            .map((tid) => ({
            id: tid,
            label: presetUiLabels[tid]?.label || tid,
            width: arPresets[tid].width,
            height: arPresets[tid].height,
        }));
        setResolutionTiers(mapped);
        // If current selection is no longer valid, reset to auto
        if (resolutionMode === 'override' && selectedResTier && !mapped.find((t) => t.id === selectedResTier)) {
            setSelectedResTier('');
            setResolutionMode('auto');
        }
    }, [aspect, presetResolutions, presetUiLabels]); // eslint-disable-line react-hooks/exhaustive-deps
    const aspectObj = useMemo(() => {
        // Prefer compatible ratios from API, fallback to static list
        if (compatibleAspectRatios.length > 0) {
            const found = compatibleAspectRatios.find((a) => a.id === aspect);
            if (found) {
                return {
                    label: found.id,
                    previewW: found.previewW,
                    previewH: found.previewH,
                    genW: found.dimensions?.width || 1024,
                    genH: found.dimensions?.height || 1024,
                };
            }
        }
        return ASPECT_RATIOS.find((a) => a.label === aspect) || ASPECT_RATIOS[0];
    }, [aspect, compatibleAspectRatios]);
    // Upload reference image handler
    const handleUploadReference = useCallback(async (file) => {
        setIsUploadingReference(true);
        try {
            const base = props.backendUrl.replace(/\/+$/, '');
            const url = `${base}/upload`;
            const fd = new FormData();
            fd.append('file', file);
            const res = await fetch(url, {
                method: 'POST',
                headers: authKey ? { 'x-api-key': authKey } : undefined,
                body: fd,
            });
            if (!res.ok) {
                const text = await res.text().catch(() => `HTTP ${res.status}`);
                throw new Error(text);
            }
            const data = await res.json();
            // Backend typically returns { url: "http://.../files/..." }
            const imageUrl = data?.url || data?.file_url || data?.media_url;
            if (!imageUrl) {
                throw new Error('Upload succeeded but no URL returned');
            }
            setReferenceUrl(imageUrl);
        }
        catch (err) {
            console.error('Reference upload failed:', err);
            alert(`Failed to upload reference: ${err.message || err}`);
        }
        finally {
            setIsUploadingReference(false);
        }
    }, [props.backendUrl, authKey]);
    const handleGenerate = useCallback(async (overridePrompt) => {
        const t = (overridePrompt || prompt).trim();
        // Allow empty prompt when reference image is uploaded
        if (!t && !referenceUrl)
            return;
        if (isGenerating)
            return;
        // Use default prompt for reference-only generation
        const effectivePrompt = t || 'similar image';
        setIsGenerating(true);
        setShowAspectPanel(false);
        setShowGamePanel(false);
        setShowAdvancedSettings(false);
        // Create placeholder items IMMEDIATELY (persists across tab switches)
        const placeholderIds = [];
        const now = Date.now();
        for (let i = 0; i < numImages; i++) {
            placeholderIds.push(`${now}-${Math.random().toString(16).slice(2)}-${i}`);
        }
        const placeholders = placeholderIds.map(id => ({
            id,
            status: 'processing',
            progress: 0,
            createdAt: now,
            prompt: effectivePrompt,
            width: props.imgWidth,
            height: props.imgHeight,
        }));
        // Add placeholders to items (will be persisted to localStorage)
        setItems((prev) => [...placeholders, ...prev]);
        // Only clear prompt if not in auto-generate mode
        if (!autoGenerateRef.current) {
            setPrompt('');
        }
        // Auto-scroll to show new placeholders
        setTimeout(() => {
            gridStartRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }, 100);
        // Simulate progress while waiting for API
        const progressInterval = setInterval(() => {
            setItems(prev => prev.map(item => placeholderIds.includes(item.id) && item.status === 'processing'
                ? { ...item, progress: Math.min(90, (item.progress || 0) + Math.random() * 8 + 2) }
                : item));
        }, 500);
        try {
            // IMPORTANT: Send aspect ratio to backend instead of hardcoded dimensions.
            // The backend's Dynamic Preset System will calculate correct dimensions
            // based on the model architecture (SD1.5 vs SDXL vs Flux).
            // Only send explicit width/height if user set them in settings.
            const hasExplicitDimensions = props.imgWidth && props.imgWidth > 0 && props.imgHeight && props.imgHeight > 0;
            // Resolution tier override: only apply when user explicitly chose Override mode
            const resTierOverride = resolutionMode === 'override' && selectedResTier
                ? resolutionTiers.find((t) => t.id === selectedResTier)
                : null;
            // Map 'comfyui' provider to 'ollama' for prompt refinement
            // ComfyUI is used automatically for actual image generation
            const llmProvider = props.providerImages === 'comfyui' ? 'ollama' : props.providerImages;
            // Determine the LLM settings for prompt refinement and Game Mode
            // Use chat provider settings if available, otherwise fall back to ollama defaults
            const chatProvider = props.providerChat || 'ollama';
            const chatBaseUrl = props.baseUrlChat || 'http://localhost:11434';
            const chatModel = props.modelChat || 'llama3:8b';
            const requestBody = {
                message: /^\s*(imagine|generate|create|draw|make)\b/i.test(effectivePrompt) ? effectivePrompt : `imagine ${effectivePrompt}`,
                mode: 'imagine',
                // Provider override fields for image generation
                provider: llmProvider,
                provider_base_url: props.baseUrlImages || undefined,
                provider_model: props.modelImages || undefined,
                // LLM settings for prompt refinement and Game Mode variation generation
                // These are separate from the image provider settings
                ollama_base_url: chatProvider === 'ollama' ? chatBaseUrl : undefined,
                ollama_model: chatProvider === 'ollama' ? chatModel : undefined,
                llm_base_url: chatProvider !== 'ollama' ? chatBaseUrl : undefined,
                llm_model: chatProvider !== 'ollama' ? chatModel : undefined,
                // Image generation params
                // Send aspect ratio for backend to calculate model-appropriate dimensions
                // Only send explicit width/height if user set them in settings (not from aspect picker)
                imgAspectRatio: aspect, // e.g., "16:9", "1:1", etc.
                // Resolution override: tier selection > explicit props > auto from preset
                imgWidth: resTierOverride ? resTierOverride.width : hasExplicitDimensions ? props.imgWidth : undefined,
                imgHeight: resTierOverride ? resTierOverride.height : hasExplicitDimensions ? props.imgHeight : undefined,
                imgResolutionOverride: resTierOverride ? true : undefined,
                // Use custom settings if advanced mode is enabled, otherwise use props
                imgSteps: advancedMode ? customSteps : props.imgSteps,
                imgCfg: advancedMode ? customCfg : props.imgCfg,
                imgSeed: advancedMode && seedLock ? customSeed : props.imgSeed,
                imgDenoise: advancedMode ? customDenoise : undefined,
                imgPreset: props.imgPreset || 'med', // Send preset for architecture-aware settings
                imgModel: props.modelImages,
                nsfwMode: props.nsfwMode,
                promptRefinement: props.promptRefinement ?? true,
                imgBatchSize: numImages,
                // ControlNet settings
                useControlNet: advancedMode ? useControlNet : undefined,
                cnStrength: advancedMode && useControlNet ? cnStrength : undefined,
                // Reference image for img2img (similar image generation)
                imgReference: referenceUrl ?? undefined,
                imgRefStrength: referenceUrl ? referenceStrength : undefined,
            };
            // Add Game Mode parameters if enabled
            if (gameMode) {
                requestBody.gameMode = true;
                requestBody.gameSessionId = gameSessionId;
                requestBody.gameStrength = gameStrength;
                requestBody.gameLocks = gameLocks;
                requestBody.gameUseGlobalLLMForVariations = useGlobalLLMForVariations;
                // Only send spicyStrength if nsfwMode is also enabled
                if (props.nsfwMode) {
                    requestBody.gameSpicyStrength = spicyStrength;
                }
            }
            // Log the aspect ratio being sent for debugging
            console.log('[Imagine] Generating with aspect ratio:', aspect, '| Request body imgAspectRatio:', requestBody.imgAspectRatio);
            const data = await postJson(props.backendUrl, '/chat', requestBody, authKey);
            const urls = data?.media?.images || [];
            if (urls.length === 0) {
                throw new Error(data?.message || data?.text || 'No images returned by backend.');
            }
            // Handle Game Mode response
            if (gameMode && data?.media?.game) {
                const gameData = data.media.game;
                if (gameData.session_id) {
                    setGameSessionId(gameData.session_id);
                }
                if (gameData.variation_prompt) {
                    setGameVariations((prev) => [
                        {
                            prompt: gameData.variation_prompt,
                            tags: gameData.tags || {},
                            timestamp: Date.now(),
                        },
                        ...prev,
                    ].slice(0, 50));
                }
            }
            const now = Date.now();
            // Use the actual refined/final prompt that was sent to the image model
            // This is the real prompt that generated the image, so users can reproduce it
            // Priority: final_prompt (refined) > variation_prompt (game mode) > original user input
            const finalPrompt = data?.media?.final_prompt
                || (gameMode && data?.media?.game?.variation_prompt)
                || t;
            clearInterval(progressInterval);
            // Extract generation parameters for reproducibility
            const seeds = data?.media?.seeds || (data?.media?.seed ? [data.media.seed] : []);
            const genWidth = data?.media?.width;
            const genHeight = data?.media?.height;
            const genSteps = data?.media?.steps;
            const genCfg = data?.media?.cfg;
            const genModel = data?.media?.model;
            // Build completed items
            const completedItemsMap = new Map();
            placeholderIds.forEach((placeholderId, index) => {
                const imageUrl = urls[index];
                const placeholder = placeholders[index];
                if (imageUrl) {
                    completedItemsMap.set(placeholderId, {
                        ...placeholder,
                        status: 'done',
                        progress: 100,
                        url: imageUrl,
                        prompt: finalPrompt,
                        seed: seeds[index] ?? seeds[0],
                        width: genWidth,
                        height: genHeight,
                        steps: genSteps,
                        cfg: genCfg,
                        model: genModel,
                    });
                }
                else {
                    completedItemsMap.set(placeholderId, {
                        ...placeholder,
                        status: 'failed',
                        error: 'No image returned',
                    });
                }
            });
            // CRITICAL: Directly persist to localStorage BEFORE setItems
            // This ensures the result is saved even if component unmounts between
            // API response and React's state update
            try {
                const lsKey = imagineItemsKey();
                const stored = localStorage.getItem(lsKey);
                const currentItems = stored ? JSON.parse(stored) : [];
                const updatedItems = currentItems.map(item => {
                    const completed = completedItemsMap.get(item.id);
                    return completed || item;
                }).slice(0, 100);
                localStorage.setItem(lsKey, JSON.stringify(updatedItems));
                console.log('[Imagine] Persisted completed images to localStorage:', Array.from(completedItemsMap.keys()));
            }
            catch (e) {
                console.error('[Imagine] Failed to persist to localStorage:', e);
            }
            // Update React state (may not process if component unmounted, but localStorage is already updated)
            setItems(prev => prev.map(item => {
                const completed = completedItemsMap.get(item.id);
                return completed || item;
            }).slice(0, 100));
        }
        catch (err) {
            clearInterval(progressInterval);
            console.error('Generation failed:', err);
            // Build failed items
            const failedItemsMap = new Map();
            placeholders.forEach(placeholder => {
                failedItemsMap.set(placeholder.id, {
                    ...placeholder,
                    status: 'failed',
                    error: err.message || 'Generation failed'
                });
            });
            // CRITICAL: Directly persist failure to localStorage
            try {
                const lsKey = imagineItemsKey();
                const stored = localStorage.getItem(lsKey);
                const currentItems = stored ? JSON.parse(stored) : [];
                const updatedItems = currentItems.map(item => {
                    const failed = failedItemsMap.get(item.id);
                    return failed || item;
                });
                localStorage.setItem(lsKey, JSON.stringify(updatedItems));
            }
            catch (e) {
                console.error('Failed to persist to localStorage:', e);
            }
            // Update React state
            setItems(prev => prev.map(item => {
                const failed = failedItemsMap.get(item.id);
                return failed || item;
            }));
            // Stop auto-generation on error
            autoGenerateRef.current = false;
            setIsAutoGenerating(false);
        }
        finally {
            setIsGenerating(false);
        }
    }, [prompt, isGenerating, aspect, props, authKey, gameMode, useGlobalLLMForVariations, gameSessionId, gameStrength, spicyStrength, gameLocks, numImages, advancedMode, customSteps, customCfg, customDenoise, seedLock, customSeed, useControlNet, cnStrength, referenceUrl, referenceStrength, selectedResTier, resolutionTiers]);
    // Auto-generation loop for Game Mode
    useEffect(() => {
        if (isAutoGenerating && gameMode && !isGenerating && prompt.trim()) {
            autoGenerateRef.current = true;
            const timeoutId = setTimeout(() => {
                if (autoGenerateRef.current) {
                    handleGenerate(prompt);
                }
            }, 500); // Small delay between generations
            return () => clearTimeout(timeoutId);
        }
    }, [isAutoGenerating, gameMode, isGenerating, prompt, handleGenerate]);
    const toggleAutoGenerate = () => {
        if (isAutoGenerating) {
            autoGenerateRef.current = false;
            setIsAutoGenerating(false);
        }
        else {
            if (!prompt.trim()) {
                alert('Please enter a base prompt first');
                return;
            }
            autoGenerateRef.current = true;
            setIsAutoGenerating(true);
            handleGenerate(prompt);
        }
    };
    const resetGameSession = () => {
        setGameSessionId(null);
        setGameVariations([]);
    };
    const handleDelete = async (item, e) => {
        if (e) {
            e.stopPropagation();
        }
        // Confirm deletion
        if (!confirm('Delete this image? This will remove it from your gallery and database.')) {
            return;
        }
        try {
            // Only delete from backend if item has a URL (completed items)
            // Failed/processing items only exist locally
            if (item.url) {
                await deleteJson(props.backendUrl, '/media/image', { image_url: item.url }, authKey);
            }
            // Remove from local state (and localStorage via useEffect)
            setItems((prev) => prev.filter((i) => i.id !== item.id));
            // Close lightbox if this image was selected
            if (selectedImage?.id === item.id) {
                setSelectedImage(null);
            }
        }
        catch (err) {
            alert(`Failed to delete image: ${err.message || err}`);
        }
    };
    // Grok-style in-place regeneration: keeps lightbox open, shows progress overlay
    const handleRegenerateInPlace = useCallback(async () => {
        if (!selectedImage || !lightboxPrompt.trim() || isRegenerating)
            return;
        const abortController = new AbortController();
        setRegenAbortController(abortController);
        setIsRegenerating(true);
        setRegenProgress(0);
        // Simulate progress while waiting for sync API
        const progressInterval = setInterval(() => {
            setRegenProgress(prev => {
                if (prev === null)
                    return 0;
                if (prev >= 90)
                    return prev;
                return Math.min(90, prev + Math.random() * 10 + 3);
            });
        }, 400);
        try {
            const requestBody = {
                message: lightboxPrompt,
                mode: 'imagine',
                numImages: 1,
                imgWidth: selectedImage.width || 1024,
                imgHeight: selectedImage.height || 1024,
                ...(advancedMode && {
                    imgSteps: customSteps,
                    imgCfg: customCfg,
                    ...(seedLock && { imgSeed: customSeed }),
                }),
                provider: props.providerImages === 'comfyui' ? 'ollama' : props.providerImages,
                provider_base_url: props.baseUrlImages || undefined,
                provider_model: props.modelImages || undefined,
                ollama_base_url: props.providerChat === 'ollama' ? props.baseUrlChat : undefined,
                ollama_model: props.providerChat === 'ollama' ? props.modelChat : undefined,
                nsfwMode: props.nsfwMode,
                promptRefinement: props.promptRefinement ?? true,
            };
            const data = await postJson(props.backendUrl, '/chat', requestBody, authKey);
            if (abortController.signal.aborted)
                return;
            if (!data.media?.images?.[0]) {
                throw new Error(data.message || data.text || 'No image returned');
            }
            setRegenProgress(100);
            const newItem = {
                id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
                status: 'done',
                url: data.media.images[0],
                createdAt: Date.now(),
                prompt: data.media.final_prompt || lightboxPrompt,
                seed: data.media.seed || data.media.seeds?.[0],
                width: data.media.width || selectedImage.width,
                height: data.media.height || selectedImage.height,
                steps: data.media.steps || (advancedMode ? customSteps : undefined),
                cfg: data.media.cfg || (advancedMode ? customCfg : undefined),
                model: data.media.model || props.modelImages,
            };
            // Update selected image in-place (Grok behavior)
            setSelectedImage(newItem);
            setLightboxPrompt(newItem.prompt);
            // Also prepend to items list
            setItems(prev => [newItem, ...prev]);
            await new Promise(r => setTimeout(r, 300));
        }
        catch (err) {
            if (abortController.signal.aborted)
                return;
            console.error('Regeneration failed:', err);
            alert(`Regeneration failed: ${err.message || err}`);
        }
        finally {
            clearInterval(progressInterval);
            setIsRegenerating(false);
            setRegenProgress(null);
            setRegenAbortController(null);
        }
    }, [selectedImage, lightboxPrompt, isRegenerating, advancedMode, customSteps, customCfg, seedLock, customSeed, props, authKey]);
    // Cancel in-place regeneration
    const handleCancelRegeneration = useCallback(() => {
        if (regenAbortController) {
            regenAbortController.abort();
            setIsRegenerating(false);
            setRegenProgress(null);
            setRegenAbortController(null);
        }
    }, [regenAbortController]);
    const handleUpscale = async (item, e) => {
        if (e) {
            e.stopPropagation();
        }
        if (upscalingId) {
            alert('Already upscaling an image. Please wait.');
            return;
        }
        try {
            if (!item.url) {
                alert('Cannot upscale: image URL not available');
                return;
            }
            setUpscalingId(item.id);
            const result = await upscaleImage({
                backendUrl: props.backendUrl,
                apiKey: authKey,
                imageUrl: item.url,
                scale: 2,
                model: '4x-UltraSharp.pth',
            });
            const upscaledUrl = result?.media?.images?.[0];
            if (upscaledUrl) {
                // Add upscaled image to gallery (non-destructive - keeps original)
                const newItem = {
                    id: `upscale-${Date.now()}-${Math.random().toString(16).slice(2)}`,
                    status: 'done',
                    url: upscaledUrl,
                    createdAt: Date.now(),
                    prompt: `[Upscaled 2x] ${item.prompt}`,
                    seed: item.seed,
                    width: (item.width ?? 1024) * 2,
                    height: (item.height ?? 1024) * 2,
                    steps: item.steps,
                    cfg: item.cfg,
                    model: item.model,
                };
                setItems((prev) => [newItem, ...prev]);
            }
            else {
                alert('Upscale completed but no image was returned.');
            }
        }
        catch (err) {
            alert(`Failed to upscale image: ${err.message || err}`);
        }
        finally {
            setUpscalingId(null);
        }
    };
    return (<div className="h-full w-full bg-black text-white font-sans overflow-hidden flex flex-col relative">
      {/* Header overlay */}
      <div className="absolute top-0 left-0 right-0 z-20 flex justify-between items-center px-6 py-4 bg-gradient-to-b from-black/80 to-transparent pointer-events-none">
        <div className="pointer-events-auto flex items-center gap-3">
          {/* Keep HomePilot brand mark vibe (subtle, not a different product logo) */}
          <div className="w-9 h-9 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
            <Wand2 size={16} className="text-white"/>
          </div>
          <div>
            <div className="text-sm font-semibold text-white leading-tight">HomePilot</div>
            <div className="text-xs text-white/50 leading-tight">Imagine</div>
          </div>
        </div>

        <div className="pointer-events-auto flex items-center gap-2">
          {/* Game Mode Toggle */}
          <button className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-semibold transition-all border ${gameMode
            ? 'bg-purple-500/20 border-purple-500/50 text-purple-300'
            : 'bg-white/5 hover:bg-white/10 border-white/10 text-white/70'}`} type="button" onClick={() => {
            setGameMode(!gameMode);
            if (!gameMode) {
                setShowGamePanel(true);
            }
        }}>
            <Gamepad2 size={16}/>
            <span>Game Mode {gameMode ? 'ON' : 'OFF'}</span>
          </button>

          {/* Variation History */}
          {gameMode && gameVariations.length > 0 && (<button className="flex items-center gap-2 bg-white/5 hover:bg-white/10 border border-white/10 px-4 py-2 rounded-full text-sm font-semibold transition-all" type="button" onClick={() => setShowVariationHistory(true)}>
              <History size={16} className="text-white/70"/>
              <span>{gameVariations.length}</span>
            </button>)}

          {/* Reference Upload Button */}
          <input ref={referenceInputRef} type="file" accept="image/png,image/jpeg,image/webp" className="hidden" onChange={(e) => {
            const f = e.target.files?.[0];
            if (f)
                handleUploadReference(f);
            e.currentTarget.value = '';
        }}/>
          <button className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-semibold transition-all border ${referenceUrl
            ? 'bg-purple-500/20 border-purple-500/50 text-purple-300'
            : 'bg-white/5 hover:bg-white/10 border-white/10 text-white/70'}`} type="button" onClick={() => referenceInputRef.current?.click()} disabled={isUploadingReference}>
            {isUploadingReference ? (<>
                <span className="animate-spin">⏳</span>
                <span>Uploading...</span>
              </>) : (<>
                <Upload size={16}/>
                <span>{referenceUrl ? 'Change reference' : 'Upload reference'}</span>
              </>)}
          </button>

          {/* Advanced Settings Toggle */}
          <button className={`p-2 rounded-full border transition-all ${showAdvancedSettings
            ? 'bg-white text-black border-white'
            : 'bg-white/5 hover:bg-white/10 border-white/10 text-white/70'}`} type="button" onClick={() => setShowAdvancedSettings(!showAdvancedSettings)} title="Advanced Settings">
            <Settings2 size={18}/>
          </button>
        </div>
      </div>

      {/* Game Mode Settings Panel */}
      {showGamePanel && gameMode && (<div className="absolute top-20 right-6 z-30 bg-black/95 border border-white/10 rounded-2xl p-5 shadow-2xl w-80 backdrop-blur-xl">
          <div className="flex justify-between items-center mb-4">
            <h3 className="text-sm font-bold text-white flex items-center gap-2">
              <Gamepad2 size={16} className="text-purple-400"/>
              Game Mode Settings
            </h3>
            <button type="button" onClick={() => setShowGamePanel(false)} className="text-white/50 hover:text-white">
              <X size={16}/>
            </button>
          </div>

          {/* Variation Strength */}
          <div className="mb-4">
            <label className="text-[11px] font-bold text-white/50 uppercase tracking-wider mb-2 block">
              Variation Strength: {gameStrength.toFixed(2)}
            </label>
            <div className="flex items-center gap-2">
              <span className="text-xs text-white/40">Subtle</span>
              <input type="range" min="0" max="1" step="0.05" value={gameStrength} onChange={(e) => setGameStrength(parseFloat(e.target.value))} className="flex-1 h-2 bg-white/10 rounded-full appearance-none cursor-pointer accent-purple-500"/>
              <span className="text-xs text-white/40">Wild</span>
            </div>
          </div>

          {/* Use Global Chat Model */}
          <div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
            <span className="text-sm text-white/80">Use Chat Model</span>
            <button onClick={() => setUseGlobalLLMForVariations(!useGlobalLLMForVariations)} className={`w-10 h-5 rounded-full transition-colors relative ${useGlobalLLMForVariations ? 'bg-purple-500' : 'bg-white/20'}`}>
              <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${useGlobalLLMForVariations ? 'translate-x-5' : 'translate-x-0.5'}`}/>
            </button>
          </div>

          {/* Spicy Strength - Only visible when nsfwMode is enabled */}
          {props.nsfwMode && (<div className="mb-4 p-3 rounded-xl bg-red-500/10 border border-red-500/20">
              <div className="flex justify-between items-center mb-2">
                <span className="text-xs text-red-300 font-semibold">🔥 Spicy Strength</span>
                <span className="text-xs text-white/60">{spicyStrength.toFixed(2)}</span>
              </div>
              <input type="range" min="0" max="1" step="0.1" value={spicyStrength} onChange={(e) => setSpicyStrength(parseFloat(e.target.value))} className="w-full h-2 bg-red-500/20 rounded-full appearance-none cursor-pointer accent-red-500"/>
              <div className="flex justify-between text-[10px] text-white/30 mt-1">
                <span>Tasteful</span>
                <span>Bold</span>
              </div>
            </div>)}

          {/* Lock Settings */}
          <div className="mb-4">
            <label className="text-[11px] font-bold text-white/50 uppercase tracking-wider mb-2 block">
              Consistency Locks
            </label>
            <div className="space-y-2">
              {[
                { key: 'lock_world', label: 'World/Setting' },
                { key: 'lock_style', label: 'Art Style' },
                { key: 'lock_subject_type', label: 'Subject Type' },
                { key: 'lock_main_character', label: 'Main Character' },
                { key: 'lock_palette', label: 'Color Palette' },
                { key: 'lock_time_of_day', label: 'Time of Day' },
            ].map(({ key, label }) => (<button key={key} type="button" className={`w-full flex items-center justify-between px-3 py-2 rounded-lg transition-colors ${gameLocks[key]
                    ? 'bg-purple-500/20 border border-purple-500/30'
                    : 'bg-white/5 border border-white/10 hover:bg-white/10'}`} onClick={() => setGameLocks((prev) => ({ ...prev, [key]: !prev[key] }))}>
                  <span className="text-xs text-white/80">{label}</span>
                  {gameLocks[key] ? (<Lock size={14} className="text-purple-400"/>) : (<Unlock size={14} className="text-white/40"/>)}
                </button>))}
            </div>
          </div>

          {/* Session Info */}
          {gameSessionId && (<div className="mb-4 p-3 bg-white/5 rounded-lg">
              <div className="text-[10px] text-white/40 mb-1">Session ID</div>
              <div className="text-xs text-white/60 font-mono truncate">{gameSessionId}</div>
              <button type="button" className="mt-2 text-xs text-red-400 hover:text-red-300" onClick={resetGameSession}>
                Reset Session
              </button>
            </div>)}

          <div className="text-[10px] text-white/40 leading-relaxed">
            Game Mode generates infinite variations of your prompt. Each generation creates a new variation while
            maintaining consistency based on your lock settings.
          </div>
        </div>)}

      {/* Advanced Settings Panel */}
      {showAdvancedSettings && (<div className="absolute top-20 right-6 z-30 bg-black/95 border border-white/10 rounded-2xl shadow-2xl w-80 backdrop-blur-xl overflow-hidden">
          <div className="p-5 border-b border-white/10 flex items-center justify-between">
            <h3 className="text-sm font-bold text-white flex items-center gap-2">
              <Settings2 size={16}/>
              PARAMETERS
            </h3>
            <button type="button" onClick={() => setShowAdvancedSettings(false)} className="text-white/50 hover:text-white">
              <X size={16}/>
            </button>
          </div>

          <div className="p-5 space-y-6 max-h-[70vh] overflow-y-auto">
            {/* Advanced Mode Toggle */}
            <button onClick={() => setAdvancedMode(!advancedMode)} className={`w-full flex items-center justify-between p-3 rounded-xl border transition-colors ${advancedMode
                ? 'bg-purple-500/20 border-purple-500/40 text-purple-300'
                : 'bg-white/5 border-white/10 text-white/60 hover:border-white/20'}`}>
              <span className="flex items-center gap-2 font-medium text-sm">
                <Sliders size={16}/>
                Advanced Controls
              </span>
              {advancedMode ? <ChevronDown size={16}/> : <ChevronRight size={16}/>}
            </button>

            {advancedMode && (<div className="space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
                {/* Resolution: Auto (Preset) vs Override — mirrors Animate.tsx */}
                {resolutionTiers.length > 0 && (<div className="space-y-2">
                    <div className="flex justify-between items-center text-xs">
                      <span className="uppercase tracking-wider text-white/40 font-semibold">Resolution</span>
                      {(() => {
                        const currentPresetId = props.imgPreset || 'med';
                        const activeTier = resolutionTiers.find((t) => t.id === (selectedResTier || currentPresetId));
                        return activeTier ? (<span className="text-purple-400/60 font-mono text-[10px]">
                            {activeTier.width}x{activeTier.height}
                          </span>) : null;
                    })()}
                    </div>
                    <div className="flex gap-1.5 mb-1.5">
                      <button onClick={() => { setResolutionMode('auto'); setSelectedResTier(''); }} className={`flex-1 py-1.5 px-2 rounded-lg text-xs font-medium transition-colors ${resolutionMode === 'auto'
                        ? 'bg-purple-500/30 text-purple-200 border border-purple-500/50'
                        : 'bg-white/5 text-white/70 border border-white/10 hover:bg-white/10'}`}>
                        Auto (Preset)
                      </button>
                      <button onClick={() => setResolutionMode('override')} className={`flex-1 py-1.5 px-2 rounded-lg text-xs font-medium transition-colors ${resolutionMode === 'override'
                        ? 'bg-purple-500/30 text-purple-200 border border-purple-500/50'
                        : 'bg-white/5 text-white/70 border border-white/10 hover:bg-white/10'}`}>
                        Override
                      </button>
                    </div>
                    {resolutionMode === 'override' && (<div className={`grid gap-1.5 ${resolutionTiers.length <= 3 ? 'grid-cols-3' : 'grid-cols-4'}`}>
                        {resolutionTiers.map((t) => {
                            const currentPresetId = props.imgPreset || 'med';
                            const isSelected = selectedResTier === t.id;
                            const isCurrentPreset = t.id === currentPresetId;
                            return (<button key={t.id} onClick={() => setSelectedResTier(t.id)} title={`${t.width}x${t.height}${isCurrentPreset ? ' (current preset default)' : ''}`} className={`relative py-1.5 px-2 rounded-lg text-xs font-medium transition-colors ${isSelected
                                    ? 'bg-purple-500/30 text-purple-200 border border-purple-500/50'
                                    : isCurrentPreset
                                        ? 'bg-white/8 text-white/80 border border-white/20 ring-1 ring-purple-500/25'
                                        : 'bg-white/5 text-white/70 border border-white/10 hover:bg-white/10'}`}>
                              <div className="font-mono text-[10px]">{t.width}x{t.height}</div>
                              <div className="text-[9px] text-white/40 capitalize">{t.id}</div>
                              {isCurrentPreset && !isSelected && (<div className="absolute -top-1 -right-1 w-1.5 h-1.5 bg-purple-400/60 rounded-full"/>)}
                            </button>);
                        })}
                      </div>)}
                    <p className="text-[10px] text-white/30 leading-relaxed">
                      Override resolution to test what works best on your GPU. Lower = faster, less VRAM.
                    </p>
                  </div>)}

                {/* Steps */}
                <div className="space-y-2">
                  <div className="flex justify-between text-xs">
                    <span className="uppercase tracking-wider text-white/40 font-semibold">Steps</span>
                    <span className="text-white/60">{customSteps}</span>
                  </div>
                  <input type="range" min={10} max={50} value={customSteps} onChange={(e) => setCustomSteps(Number(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
                </div>

                {/* CFG Scale */}
                <div className="space-y-2">
                  <div className="flex justify-between text-xs">
                    <span className="uppercase tracking-wider text-white/40 font-semibold">CFG Scale</span>
                    <span className="text-white/60">{customCfg.toFixed(1)}</span>
                  </div>
                  <input type="range" min={1} max={15} step={0.5} value={customCfg} onChange={(e) => setCustomCfg(Number(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
                </div>

                {/* Denoise Strength */}
                <div className="space-y-2">
                  <div className="flex justify-between text-xs">
                    <span className="uppercase tracking-wider text-white/40 font-semibold">Denoise Strength</span>
                    <span className="text-white/60">{customDenoise.toFixed(2)}</span>
                  </div>
                  <input type="range" min={0.1} max={1.0} step={0.05} value={customDenoise} onChange={(e) => setCustomDenoise(Number(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
                </div>

                {/* Lock Seed */}
                <div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
                  <span className="text-sm text-white/80">Lock Seed</span>
                  <button onClick={() => {
                    if (!seedLock)
                        setCustomSeed(Math.floor(Math.random() * 2147483647));
                    setSeedLock(!seedLock);
                }} className={`w-10 h-5 rounded-full transition-colors relative ${seedLock ? 'bg-purple-500' : 'bg-white/20'}`}>
                    <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${seedLock ? 'translate-x-5' : 'translate-x-0.5'}`}/>
                  </button>
                </div>
                {seedLock && (<input type="number" value={customSeed} onChange={(e) => setCustomSeed(Number(e.target.value))} className="w-full rounded-xl bg-white/5 border border-white/10 px-3 py-2 text-sm text-white font-mono focus:border-purple-500/50 focus:outline-none" placeholder="Seed value"/>)}

                {/* Use ControlNet */}
                <div className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/10">
                  <span className="text-sm text-white/80">Use ControlNet</span>
                  <button onClick={() => setUseControlNet(!useControlNet)} className={`w-10 h-5 rounded-full transition-colors relative ${useControlNet ? 'bg-purple-500' : 'bg-white/20'}`}>
                    <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${useControlNet ? 'translate-x-5' : 'translate-x-0.5'}`}/>
                  </button>
                </div>
                {useControlNet && (<div className="space-y-2">
                    <div className="flex justify-between text-xs">
                      <span className="uppercase tracking-wider text-white/40 font-semibold">CN Strength</span>
                      <span className="text-white/60">{cnStrength.toFixed(2)}</span>
                    </div>
                    <input type="range" min={0} max={2} step={0.05} value={cnStrength} onChange={(e) => setCnStrength(Number(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
                  </div>)}

                {/* Fullscreen Stretch Toggle */}
                <div className="flex items-center justify-between">
                  <span className="text-sm text-white/80">Fullscreen Stretch</span>
                  <button onClick={() => setFullscreenStretch(!fullscreenStretch)} className={`w-10 h-5 rounded-full transition-colors relative ${fullscreenStretch ? 'bg-purple-500' : 'bg-white/20'}`}>
                    <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${fullscreenStretch ? 'translate-x-5' : 'translate-x-0.5'}`}/>
                  </button>
                </div>
                <div className="text-[11px] text-white/40 -mt-3 leading-snug">
                  OFF: fit entire image on screen (may letterbox). ON: fill screen (may crop edges).
                </div>
              </div>)}

            <div className="p-4 rounded-xl bg-purple-900/10 border border-purple-500/20 text-xs text-purple-200/70 leading-relaxed">
              <span className="font-bold text-purple-400 block mb-1">PRO TIP</span>
              Enable Advanced Controls to fine-tune generation parameters. Use Lock Seed to regenerate with the same composition.
            </div>
          </div>
        </div>)}

      {/* Grid - Row-wise layout like Grok (fills left to right, then next row) */}
      <div className="flex-1 overflow-y-auto px-4 pb-48 pt-8 scrollbar-hide">
        <div className="max-w-[1600px] mx-auto grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 content-start">
          {/* Scroll anchor for auto-scroll to top (Grok-style: new images at top) */}
          <div ref={gridStartRef} className="col-span-full h-1"/>

          {/* Empty hint */}
          {items.length === 0 && !isGenerating ? (<div className="col-span-full rounded-2xl border border-white/10 bg-white/5 p-8 text-center">
              <div className="mx-auto mb-4 size-14 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center">
                <Sparkles size={24} className="text-purple-400/60"/>
              </div>
              <div className="text-lg font-semibold text-white/90 mb-2">Your gallery is empty</div>
              <div className="text-sm text-white/45 mb-4">
                Type a prompt below and hit Generate.
              </div>
              <button type="button" onClick={() => {
                promptInputRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
                promptInputRef.current?.focus();
            }} className="px-4 py-2 bg-purple-500/20 text-purple-200 rounded-xl border border-purple-500/30 hover:bg-purple-500/30 transition-colors">
                Start with a prompt
              </button>
            </div>) : null}

          {/* Images (including processing placeholders) - rendered in order (newest first at top-left) */}
          {items.filter((img) => !brokenIds.has(img.id)).map((img) => (<div key={img.id} onClick={() => img.status === 'done' && setSelectedImage(img)} className={`relative group rounded-2xl overflow-hidden bg-white/5 border border-white/10 transition-colors aspect-square ${img.status === 'done' ? 'hover:border-white/20 cursor-pointer' : ''}`}>
              {/* Image content - show based on status */}
              {img.status === 'done' && img.url ? (<img src={resolveFileUrl(img.url, props.backendUrl)} alt={img.prompt} className="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" loading="lazy" onError={() => markImageBroken(img.id)}/>) : (
            // Processing or Failed - show gradient placeholder
            <div className="absolute inset-0 w-full h-full bg-gradient-to-br from-purple-900/30 to-black"/>)}

              {/* Processing overlay - "Generating..." with purple spinner */}
              {img.status === 'processing' && (<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/50 backdrop-blur-sm">
                  <Loader2 size={32} className="text-purple-400 animate-spin mb-3"/>
                  <div className="text-white/90 text-sm font-medium">Generating...</div>
                  {typeof img.progress === 'number' && (<div className="mt-2 px-3 py-1 rounded-full bg-black/60 border border-white/10 text-white/90 text-xs">
                      {Math.round(img.progress)}%
                    </div>)}
                </div>)}

              {/* Failed overlay */}
              {img.status === 'failed' && (<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/60 backdrop-blur-sm">
                  <X size={32} className="text-red-400 mb-2"/>
                  <div className="text-red-400 text-sm font-medium">Generation failed</div>
                  <div className="text-white/50 text-xs mt-1 px-4 text-center line-clamp-2">
                    {img.error || 'Unknown error'}
                  </div>
                  <button className="mt-3 px-4 py-1.5 rounded-full bg-white/10 hover:bg-white/20 text-white text-xs font-medium transition-colors" onClick={(e) => {
                    e.stopPropagation();
                    handleDelete(img, e);
                }}>
                    Remove
                  </button>
                </div>)}

              {/* Card overlay (only for completed images) */}
              {img.status === 'done' && (<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-4">
                  <div className="flex gap-2 justify-end transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
                    <button className="bg-white/10 backdrop-blur-md hover:bg-white/20 p-2 rounded-full text-white transition-colors" type="button" title="Play" onClick={(e) => {
                    e.stopPropagation();
                }}>
                      <Play size={18} fill="currentColor"/>
                    </button>
                    <button className="bg-white/10 backdrop-blur-md hover:bg-white/20 p-2 rounded-full text-white transition-colors" type="button" title="Copy URL" onClick={(e) => {
                    e.stopPropagation();
                    if (img.url)
                        navigator.clipboard?.writeText(resolveFileUrl(img.url, props.backendUrl)).catch(() => { });
                }}>
                      <MoreHorizontal size={18}/>
                    </button>
                    <button className={`backdrop-blur-md p-2 rounded-full transition-colors ${upscalingId === img.id
                    ? 'bg-purple-500/40 text-purple-300 animate-pulse'
                    : 'bg-purple-500/20 hover:bg-purple-500/40 text-purple-300 hover:text-purple-200'}`} type="button" title="Upscale 2x" disabled={upscalingId !== null} onClick={(e) => handleUpscale(img, e)}>
                      <Maximize2 size={18}/>
                    </button>
                    <button className="bg-red-500/20 backdrop-blur-md hover:bg-red-500/40 p-2 rounded-full text-red-400 hover:text-red-300 transition-colors" type="button" title="Delete" onClick={(e) => handleDelete(img, e)}>
                      <Trash2 size={18}/>
                    </button>
                  </div>

                  <div className="mt-3 text-xs text-white/80 line-clamp-2">{img.prompt}</div>
                </div>)}
            </div>))}
        </div>
      </div>

      {/* Floating prompt bar */}
      <div className="absolute bottom-0 left-0 right-0 z-30 p-6 flex justify-center items-end bg-gradient-to-t from-black via-black/90 to-transparent h-48 pointer-events-none">
        <div className="w-full max-w-2xl relative pointer-events-auto">
          {/* Reference Image Preview Panel */}
          {referenceUrl && (<div className="absolute bottom-[110%] left-0 right-0 bg-black/95 border border-white/10 rounded-2xl p-4 shadow-2xl mb-2 backdrop-blur-xl">
              <div className="flex items-center gap-4">
                <img src={referenceUrl} className="h-20 w-20 rounded-xl object-cover border border-white/20 shadow-lg" alt="Reference"/>
                <div className="flex-1 min-w-0">
                  <div className="flex items-center justify-between mb-2">
                    <div className="text-sm font-semibold text-white flex items-center gap-2">
                      <Image size={14} className="text-purple-400"/>
                      Reference Active
                    </div>
                    <button type="button" className="p-1.5 rounded-lg bg-white/5 hover:bg-red-500/20 border border-white/10 hover:border-red-500/30 text-white/50 hover:text-red-400 transition-colors" onClick={() => setReferenceUrl(null)} title="Remove reference">
                      <X size={14}/>
                    </button>
                  </div>
                  <div className="space-y-2">
                    <div className="flex justify-between text-xs">
                      <span className="text-white/50">Similarity</span>
                      <span className="text-white/70 font-mono">
                        {referenceStrength < 0.3 ? 'Very Similar' : referenceStrength < 0.6 ? 'Balanced' : 'More Creative'}
                      </span>
                    </div>
                    <input type="range" min={0} max={1} step={0.05} value={referenceStrength} onChange={(e) => setReferenceStrength(Number(e.target.value))} className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-purple-400 [&::-webkit-slider-thumb]:rounded-full"/>
                    <div className="flex justify-between text-[10px] text-white/40">
                      <span>Similar</span>
                      <span>Creative</span>
                    </div>
                  </div>
                </div>
              </div>
              <div className="mt-3 text-[10px] text-white/40 leading-relaxed">
                Generated images will be similar to your reference. Adjust the slider to control how closely to follow the reference.
              </div>
            </div>)}

          {/* Aspect panel */}
          {showAspectPanel ? (<div className="absolute bottom-[110%] left-0 bg-black border border-white/10 rounded-xl p-3 shadow-2xl mb-2 flex flex-col gap-3">
              {/* Aspect Ratio Section */}
              <div>
                <div className="flex justify-between items-center text-xs text-white/50 uppercase font-semibold px-1 mb-2">
                  <span>Aspect Ratio</span>
                  <button type="button" onClick={() => setShowAspectPanel(false)}>
                    <X size={14}/>
                  </button>
                </div>
                <div className="flex gap-2">
                  {(compatibleAspectRatios.length > 0 ? compatibleAspectRatios : ASPECT_RATIOS.map(r => ({
                id: r.label,
                label: r.label,
                previewW: r.previewW,
                previewH: r.previewH,
                dimensions: { width: r.genW, height: r.genH },
            }))).map((r) => (<button key={r.id} onClick={() => {
                    console.log('[Imagine] Aspect ratio selected:', r.id, r.dimensions ? `(${r.dimensions.width}x${r.dimensions.height})` : '');
                    setAspect(r.id);
                }} className={`p-2 rounded-lg hover:bg-white/5 transition-colors group flex flex-col items-center gap-1 ${aspect === r.id ? 'bg-white/5 ring-1 ring-white/20' : ''}`} title={`${r.id}${r.dimensions ? ` (${r.dimensions.width}x${r.dimensions.height})` : ''}`} type="button">
                      <div className={`border-2 ${aspect === r.id ? 'border-white/70' : 'border-white/30 group-hover:border-white/50'} rounded-[2px]`} style={{ width: r.previewW, height: r.previewH }}/>
                      <span className="text-[10px] text-white/50">{r.id}</span>
                    </button>))}
                </div>
              </div>

              {/* Number of Images Section */}
              <div className="border-t border-white/10 pt-3">
                <div className="text-xs text-white/50 uppercase font-semibold px-1 mb-2">
                  Images per generation
                </div>
                <div className="flex gap-2">
                  {[1, 2, 4].map((n) => (<button key={n} onClick={() => setNumImages(n)} className={`flex-1 py-2 px-3 rounded-lg transition-colors flex items-center justify-center gap-2 ${numImages === n
                    ? 'bg-white/10 ring-1 ring-white/30 text-white'
                    : 'bg-white/5 hover:bg-white/10 text-white/60'}`} type="button">
                      {n === 1 ? (<Image size={14}/>) : (<Grid2X2 size={14}/>)}
                      <span className="text-sm font-medium">{n}</span>
                    </button>))}
                </div>
                <div className="text-[10px] text-white/40 px-1 mt-1.5">
                  {numImages === 1 ? 'Single full-size image' : `${numImages} thumbnails per generation`}
                </div>
              </div>
            </div>) : null}

          <div className="relative group">
            <div className={`absolute -inset-0.5 bg-gradient-to-r from-white/10 to-white/0 rounded-full opacity-0 transition duration-500 group-hover:opacity-100 blur ${isGenerating ? 'opacity-100 animate-pulse' : ''}`}></div>

            <div className="relative bg-black border border-white/10 rounded-[32px] p-2 pr-2 flex items-center shadow-2xl transition-all focus-within:border-white/20">
              <button className="p-3 text-white/50 hover:text-white transition-colors rounded-full hover:bg-white/5" type="button" title="Voice input (not implemented)">
                <Mic size={20}/>
              </button>

              <input ref={promptInputRef} type="text" value={prompt} onChange={(e) => setPrompt(e.target.value)} onKeyDown={(e) => (e.key === 'Enter' ? void handleGenerate() : undefined)} placeholder="Describe what you want to see…" className="flex-1 bg-transparent text-white placeholder-white/35 outline-none px-2 h-12 text-lg"/>

              <div className="flex items-center gap-2 pl-2">
                <button onClick={() => setShowAspectPanel((v) => !v)} className={`p-2 rounded-full transition-colors ${showAspectPanel ? 'text-white bg-white/5' : 'text-white/50 hover:text-white hover:bg-white/5'}`} type="button" title="Aspect & settings">
                  <Settings2 size={20}/>
                </button>

                {/* Game Mode: Auto Generate Button */}
                {gameMode && (<button onClick={toggleAutoGenerate} disabled={!prompt.trim()} className={`ml-1 h-10 px-4 rounded-full font-semibold text-sm transition-all flex items-center gap-2 ${isAutoGenerating
                ? 'bg-red-500 text-white hover:bg-red-600'
                : prompt.trim()
                    ? 'bg-purple-500 text-white hover:bg-purple-600'
                    : 'bg-white/10 text-white/40 cursor-not-allowed'}`} type="button">
                    {isAutoGenerating ? (<>
                        <Pause size={16}/>
                        Stop
                      </>) : (<>
                        <Zap size={16}/>
                        Auto
                      </>)}
                  </button>)}

                <button onClick={() => void handleGenerate()} disabled={(!prompt.trim() && !referenceUrl) || isGenerating || isAutoGenerating} className={`ml-1 h-10 px-6 rounded-full font-semibold text-sm transition-all flex items-center gap-2 ${(prompt.trim() || referenceUrl) && !isGenerating && !isAutoGenerating
            ? gameMode
                ? 'bg-purple-500 text-white hover:bg-purple-600 hover:scale-[1.02]'
                : 'bg-white text-black hover:bg-gray-200 hover:scale-[1.02]'
            : 'bg-white/10 text-white/40 cursor-not-allowed'}`} type="button">
                  {isGenerating ? (<span className="animate-pulse">Creating…</span>) : gameMode ? ('Variation') : ('Generate')}
                </button>
              </div>
            </div>

            <div className="mt-2 text-[11px] text-white/35 px-2">
              Using provider <span className="text-white/55 font-semibold">{props.providerImages}</span>
              {props.modelImages ? (<>
                  {' '}
                  · model <span className="text-white/55 font-semibold">{props.modelImages}</span>
                </>) : null}
            </div>
          </div>
        </div>
      </div>

      {/* Immersive Lightbox - Clean, Image-first Design */}
      {selectedImage && (<div ref={lightboxRef} className="fixed inset-0 z-50 flex flex-col bg-black animate-in fade-in duration-200" onClick={() => { setSelectedImage(null); setShowDetails(false); }}>
          {/* Floating Controls - Top Right (auto-hide in fullscreen, show on hover) */}
          <div className={`absolute top-4 right-4 z-50 flex items-center gap-2 transition-opacity duration-300 ${isFullscreen ? 'opacity-0 hover:opacity-100' : ''}`}>
            {/* Slideshow indicator — visible when Game Mode + Auto + Fullscreen */}
            {gameMode && isAutoGenerating && isFullscreen && (<div className="p-2.5 bg-purple-500/20 text-purple-200 border border-purple-500/30 rounded-full" title="Slideshow: auto-advancing to new images" onClick={(e) => e.stopPropagation()}>
                <Film size={18}/>
              </div>)}
            <button className={`p-2.5 rounded-full transition-all ${showDetails ? 'bg-white text-black' : 'bg-white/10 text-white/70 hover:bg-white/20 hover:text-white'}`} onClick={(e) => { e.stopPropagation(); setShowDetails(!showDetails); }} type="button" title="Toggle details">
              <Info size={18}/>
            </button>
            <button className="p-2.5 bg-white/10 text-white/70 hover:bg-white/20 hover:text-white rounded-full transition-all" onClick={(e) => {
                e.stopPropagation();
                // Fullscreen the lightbox container (not just the image)
                // so overlay controls, slideshow icon, and keyboard nav work inside fullscreen
                const el = lightboxRef.current;
                if (el?.requestFullscreen) {
                    el.requestFullscreen().catch(() => { });
                }
            }} type="button" title="View full size">
              <Maximize2 size={18}/>
            </button>
            <button className="p-2.5 bg-white/10 text-white/70 hover:bg-white/20 hover:text-white rounded-full transition-all" onClick={() => { setSelectedImage(null); setShowDetails(false); }} type="button" title="Close">
              <X size={18}/>
            </button>
          </div>

          {/* Main Content Area */}
          <div className="flex-1 flex min-h-0" onClick={(e) => e.stopPropagation()}>
            {/* Hero Image Container - LARGER, fills more space */}
            <div className="flex-1 flex items-center justify-center p-2 relative group min-h-0 overflow-hidden">
              {selectedImage.url ? (<img src={resolveFileUrl(selectedImage.url, props.backendUrl)} data-lightbox-media className={isFullscreen
                    ? fullscreenStretch
                        ? "w-full h-full object-cover"
                        : "w-full h-full object-contain"
                    : "max-h-[calc(100vh-140px)] max-w-full object-contain rounded-lg shadow-2xl"} alt="Selected"/>) : (<div className="flex items-center justify-center h-64 w-full max-w-lg bg-white/5 rounded-lg">
                  <Loader2 size={32} className="text-white/50 animate-spin"/>
                </div>)}

              {/* Chips Overlay - Fade in on hover */}
              <div className="absolute bottom-6 left-6 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
                {selectedImage.model && (<span className="px-3 py-1.5 bg-black/60 backdrop-blur-md text-white text-xs font-semibold rounded-full border border-white/10">
                    {selectedImage.model.split('.')[0].split('-')[0].toUpperCase()}
                  </span>)}
                {selectedImage.width && selectedImage.height && (<span className="px-3 py-1.5 bg-black/60 backdrop-blur-md text-white text-xs font-semibold rounded-full border border-white/10">
                    {selectedImage.width}×{selectedImage.height}
                  </span>)}
                {selectedImage.steps && (<span className="px-3 py-1.5 bg-black/60 backdrop-blur-md text-white text-xs font-semibold rounded-full border border-white/10">
                    {selectedImage.steps} steps
                  </span>)}
              </div>

              {/* Grok-style Regeneration Overlay - Blur + Dots + Progress */}
              {isRegenerating && (<div className="absolute inset-0 z-30 flex items-center justify-center rounded-lg overflow-hidden">
                  {/* Blur + Dim background */}
                  <div className="absolute inset-0 bg-black/40 backdrop-blur-sm"/>

                  {/* Dotted pattern overlay */}
                  <div className="absolute inset-0 opacity-40 pointer-events-none" style={{
                    backgroundImage: 'radial-gradient(circle, rgba(255,255,255,0.4) 1px, transparent 1px)',
                    backgroundSize: '24px 24px'
                }}/>

                  {/* Center controls */}
                  <div className="relative z-10 flex flex-col items-center gap-4">
                    {/* Progress indicator */}
                    {typeof regenProgress === 'number' && (<div className="px-5 py-2.5 rounded-full bg-black/70 border border-white/20 text-white font-medium text-lg shadow-xl">
                        {Math.round(regenProgress)}%
                      </div>)}

                    {/* Cancel button */}
                    <button className="px-5 py-2.5 rounded-full bg-black/70 border border-white/20 text-white/90 hover:bg-black/80 hover:text-white transition-colors font-medium shadow-xl" onClick={(e) => { e.stopPropagation(); handleCancelRegeneration(); }} type="button">
                      Cancel
                    </button>
                  </div>
                </div>)}
            </div>

            {/* Details Panel - Slide in from right */}
            {showDetails && (<div className="w-80 bg-[#0a0a0a] border-l border-white/10 flex flex-col animate-in slide-in-from-right duration-200">
                <div className="p-4 border-b border-white/10">
                  <h3 className="text-sm font-bold text-white flex items-center gap-2">
                    <Wand2 size={14} className="text-white/60"/>
                    Generation Details
                  </h3>
                </div>
                <div className="flex-1 overflow-y-auto p-4 space-y-4">
                  {/* Prompt - Only visible in details panel */}
                  <div>
                    <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-2 block">Prompt</label>
                    <p className="text-sm text-white/70 leading-relaxed">{selectedImage.prompt}</p>
                  </div>
                  {/* Seed */}
                  {selectedImage.seed && (<div>
                      <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Seed</label>
                      <div className="text-base text-white font-mono font-bold">{selectedImage.seed}</div>
                    </div>)}
                  {/* Resolution */}
                  {selectedImage.width && selectedImage.height && (<div>
                      <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Resolution</label>
                      <div className="text-sm text-white/80 font-mono">{selectedImage.width} × {selectedImage.height}</div>
                    </div>)}
                  {/* Steps & CFG */}
                  <div className="grid grid-cols-2 gap-3">
                    {selectedImage.steps && (<div>
                        <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Steps</label>
                        <div className="text-sm text-white/70 font-mono">{selectedImage.steps}</div>
                      </div>)}
                    {selectedImage.cfg && (<div>
                        <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">CFG</label>
                        <div className="text-sm text-white/70 font-mono">{selectedImage.cfg}</div>
                      </div>)}
                  </div>
                  {/* Date & Time */}
                  <div className="grid grid-cols-2 gap-3">
                    <div>
                      <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Date</label>
                      <div className="text-xs text-white/60 font-mono">{new Date(selectedImage.createdAt).toLocaleDateString()}</div>
                    </div>
                    <div>
                      <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Time</label>
                      <div className="text-xs text-white/60 font-mono">{new Date(selectedImage.createdAt).toLocaleTimeString()}</div>
                    </div>
                  </div>
                  {/* Model */}
                  {selectedImage.model && (<div>
                      <label className="text-[10px] font-bold text-white/40 uppercase tracking-wider mb-1 block">Model</label>
                      <div className="text-xs text-white/50 font-mono break-all">{selectedImage.model}</div>
                    </div>)}
                </div>
              </div>)}
          </div>

          {/* Grok-style Prompt Composer Bar — hidden in fullscreen for immersive view */}
          {!isFullscreen && (<div className="bg-[#0a0a0a] border-t border-white/10 px-4 py-3" onClick={(e) => e.stopPropagation()}>
            <div className="max-w-4xl mx-auto flex items-center gap-3">

              {/* Prompt Card Container */}
              <div className="flex-1 flex items-center gap-3 bg-[#1a1a1a] rounded-2xl px-4 py-2.5 border border-white/10">
                {/* Progress indicator in prompt bar during regeneration */}
                {isRegenerating && typeof regenProgress === 'number' && (<div className="flex items-center gap-2 flex-shrink-0">
                    <div className="px-2.5 py-1 rounded-full bg-white/10 text-white/80 text-xs font-medium">
                      {Math.round(regenProgress)}%
                    </div>
                    <ChevronDown size={14} className="text-white/40"/>
                  </div>)}

                {/* Editable Prompt Input */}
                <input type="text" value={lightboxPrompt} onChange={(e) => setLightboxPrompt(e.target.value)} onKeyDown={(e) => {
                    if (e.key === 'Enter' && lightboxPrompt.trim() && !isRegenerating) {
                        handleRegenerateInPlace();
                    }
                }} placeholder={isRegenerating ? "Generating image..." : "Edit prompt and regenerate..."} className="flex-1 bg-transparent text-white/90 text-sm placeholder-white/30 outline-none min-w-0" disabled={isRegenerating}/>

                {/* Redo Button (inside card) - Grok style */}
                <button onClick={isRegenerating ? handleCancelRegeneration : handleRegenerateInPlace} disabled={!lightboxPrompt.trim() && !isRegenerating} className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium transition-all flex-shrink-0 ${isRegenerating
                    ? 'bg-white/10 hover:bg-white/20 text-white'
                    : lightboxPrompt.trim()
                        ? 'bg-white/10 hover:bg-white/20 text-white'
                        : 'bg-white/5 text-white/30 cursor-not-allowed'}`} type="button" title={isRegenerating ? "Cancel" : "Redo"}>
                  Redo <ArrowUp size={14}/>
                </button>
              </div>

              {/* Action Icons (outside card) */}
              <div className="flex items-center gap-1 flex-shrink-0">
                <button className="p-2.5 text-white/60 hover:text-white hover:bg-white/10 rounded-full transition-colors" onClick={() => {
                    if (!selectedImage.url)
                        return;
                    const a = document.createElement('a');
                    a.href = resolveFileUrl(selectedImage.url, props.backendUrl);
                    a.download = `imagine-${selectedImage.id}.png`;
                    a.click();
                }} type="button" title="Download" disabled={!selectedImage.url}>
                  <Download size={18}/>
                </button>
                <button className="p-2.5 text-white/60 hover:text-purple-400 hover:bg-purple-500/10 rounded-full transition-colors" onClick={() => {
                    if (!selectedImage.url)
                        return;
                    // Store handoff payload for Animate tab (Grok-style)
                    localStorage.setItem('homepilot_animate_handoff', JSON.stringify({
                        imageUrl: selectedImage.url,
                        prompt: selectedImage.prompt || '',
                        createdAt: Date.now(),
                    }));
                    window.dispatchEvent(new CustomEvent('switch-to-animate'));
                    setSelectedImage(null);
                    setShowDetails(false);
                }} type="button" title="Animate this image" disabled={!selectedImage.url}>
                  <Film size={18}/>
                </button>
                <button className="p-2.5 text-white/60 hover:text-red-400 hover:bg-red-500/10 rounded-full transition-colors" onClick={() => handleDelete(selectedImage)} type="button" title="Delete">
                  <Trash2 size={18}/>
                </button>
              </div>

            </div>
          </div>)}
        </div>)}

      {/* Variation History Modal */}
      {showVariationHistory && (<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4 backdrop-blur-md" onClick={() => setShowVariationHistory(false)}>
          <div className="max-w-2xl w-full max-h-[80vh] bg-[#121212] border border-white/10 rounded-2xl overflow-hidden shadow-2xl" onClick={(e) => e.stopPropagation()}>
            <div className="p-5 border-b border-white/10 flex items-center justify-between">
              <h3 className="text-sm font-bold text-white flex items-center gap-2">
                <History size={16} className="text-purple-400"/>
                Variation History ({gameVariations.length})
              </h3>
              <button type="button" onClick={() => setShowVariationHistory(false)} className="text-white/50 hover:text-white">
                <X size={20}/>
              </button>
            </div>

            <div className="overflow-y-auto max-h-[60vh] p-4 space-y-3">
              {gameVariations.map((v, i) => (<div key={i} className="p-4 bg-white/5 rounded-xl border border-white/10 hover:border-white/20 transition-colors">
                  <div className="text-sm text-white/90 mb-2">{v.prompt}</div>
                  <div className="flex flex-wrap gap-2 mb-2">
                    {Object.entries(v.tags).map(([key, value]) => (<span key={key} className="px-2 py-1 bg-purple-500/20 text-purple-300 text-[10px] rounded-full">
                        {key}: {value}
                      </span>))}
                  </div>
                  <div className="flex items-center justify-between">
                    <span className="text-[10px] text-white/40">
                      {new Date(v.timestamp).toLocaleTimeString()}
                    </span>
                    <button type="button" className="text-xs text-purple-400 hover:text-purple-300 flex items-center gap-1" onClick={() => {
                    setPrompt(v.prompt);
                    setShowVariationHistory(false);
                }}>
                      <RefreshCw size={12}/>
                      Use as base
                    </button>
                  </div>
                </div>))}

              {gameVariations.length === 0 && (<div className="text-center text-white/40 py-8">
                  No variations generated yet. Start generating to see history.
                </div>)}
            </div>
          </div>
        </div>)}

      <style>{`
        .scrollbar-hide::-webkit-scrollbar { display: none; }
        .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
        .line-clamp-2 {
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          overflow: hidden;
        }
      `}</style>
    </div>);
}
