Infinite Craft - Auto Combiner V3 (Mobile Optimized)

Automates combining a target element with all others in Infinite Craft, remembering failed combinations to speed up runs. Mobile-friendly UI.

// ==UserScript==
// @name         Infinite Craft - Auto Combiner V3 (Mobile Optimized)
// @namespace    http://tampermonkey.net/
// @version      3
// @description  Automates combining a target element with all others in Infinite Craft, remembering failed combinations to speed up runs. Mobile-friendly UI.
// @author       YourName (or Generated)
// @match        https://neal.fun/infinite-craft/
// @grant        none
// @run-at       document-idle
// @license     MIT
// ==/UserScript==

(() => {
    // --- CONFIGURATION --- (Adjust delays if needed)
    const CONFIG = {
        // Selectors specific to Infinite Craft (FIXED)
        itemSelector: '.items .item', // FIXED: Was '.item', now '.items .item'
        gameContainerSelector: '.container', // Simplified

        // UI Element IDs & Classes
        panelId: 'auto-combo-panel',
        targetInputId: 'auto-combo-target-input',
        suggestionBoxId: 'auto-combo-suggestion-box',
        suggestionItemClass: 'auto-combo-suggestion-item',
        statusBoxId: 'auto-combo-status',
        startButtonId: 'auto-combo-start-button',
        stopButtonId: 'auto-combo-stop-button',
        clearFailedButtonId: 'auto-combo-clear-failed-button',
        speedSelectId: 'auto-combo-speed-select',
        debugMarkerClass: 'auto-combo-debug-marker',

        // Speed Presets (all values in ms)
        speedPresets: {
            slow: {
                interComboDelay: 150,
                postComboScanDelay: 800,
                dragStepDelay: 20,
                dragBetweenDelay: 250
            },
            normal: {
                interComboDelay: 100,
                postComboScanDelay: 650,
                dragStepDelay: 15,
                dragBetweenDelay: 200
            },
            fast: {
                interComboDelay: 50,
                postComboScanDelay: 500,
                dragStepDelay: 10,
                dragBetweenDelay: 150
            },
            turbo: {
                interComboDelay: 25,
                postComboScanDelay: 350,
                dragStepDelay: 5,
                dragBetweenDelay: 100
            }
        },

        // Default delays (will be overridden by speed selection)
        interComboDelay: 100,
        postComboScanDelay: 650,
        dragStepDelay: 15,
        dragBetweenDelay: 200,
        scanDebounceDelay: 300,
        suggestionHighlightDelay: 50,

        // Behavior
        suggestionLimit: 20,
        debugMarkerDuration: 1000,

        // Keys
        keyArrowUp: 'ArrowUp',
        keyArrowDown: 'ArrowDown',
        keyEnter: 'Enter',
        keyTab: 'Tab',

        // Storage
        storageKeyFailedCombos: 'infCraftAutoComboFailedCombosV2',
        storageKeySpeed: 'infCraftAutoComboSpeed',

        // Styling & Z-Index
        panelZIndex: 10010,
        suggestionZIndex: 10011,
        markerZIndex: 10012,
    };

    // --- CORE CLASS ---
    class AutoTargetCombo {
        constructor() {
            console.log('[AutoCombo] Initializing for Infinite Craft...');
            this.itemElementMap = new Map(); // Map<string, Element>
            this.isRunning = false;
            this.suggestionIndex = -1;
            this.suggestions = [];
            this.scanDebounceTimer = null;
            this.failedCombos = new Set();

            // UI References
            this.panel = null;
            this.targetInput = null;
            this.suggestionBox = null;
            this.statusBox = null;
            this.startButton = null;
            this.stopButton = null;
            this.clearFailedButton = null;
            this.speedSelect = null;
            this.currentSpeed = 'normal';

            // --- Initialization Steps ---
            try {
                this._injectStyles();
                this._setupUI();

                 // Check if essential UI elements were found after setup
                 if (!this.panel || !this.targetInput || !this.statusBox || !this.startButton || !this.stopButton || !this.clearFailedButton || !this.speedSelect) {
                    throw new Error("One or more critical UI elements missing after setup. Aborting.");
                 }

                this._setupEventListeners();
                this._loadFailedCombos(); // Load saved failures
                this._loadSpeed(); // Load saved speed setting
                this.observeDOM();
                this.scanItems(); // Perform initial scan
                this.logStatus('Ready.');
                console.log('[AutoCombo] Initialization complete.');
            } catch (error) {
                console.error('[AutoCombo] CRITICAL ERROR during initialization:', error);
                this.logStatus(`❌ INIT FAILED: ${error.message}`, 'status-error');
                // Clean up partial UI if needed
                if (this.panel && this.panel.parentNode) {
                    this.panel.parentNode.removeChild(this.panel);
                }
            }
        }

        // --- Initialization & Setup ---

        _injectStyles() {
            if (document.getElementById(`${CONFIG.panelId}-styles`)) return;
            const css = `
                #${CONFIG.panelId} {
                    position: fixed; top: 8px; left: 8px; z-index: ${CONFIG.panelZIndex};
                    background: rgba(250, 250, 250, 0.97); padding: 8px; border: 1px solid #aaa; border-radius: 6px;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 12px; width: 200px; color: #111;
                    box-shadow: 0 3px 10px rgba(0,0,0,0.25); display: flex; flex-direction: column; gap: 5px;
                    max-width: calc(100vw - 16px);
                }
                #${CONFIG.panelId} * { box-sizing: border-box; }
                #${CONFIG.panelId} div:first-child {
                    font-weight: bold; margin-bottom: 2px; text-align: center; color: #333; font-size: 13px; padding-bottom: 3px; border-bottom: 1px solid #ddd;
                 }
                #${CONFIG.panelId} input, #${CONFIG.panelId} button, #${CONFIG.panelId} select {
                     width: 100%; padding: 6px 8px; font-size: 12px;
                    border: 1px solid #ccc; border-radius: 4px;
                }
                 #${CONFIG.panelId} input { background-color: #fff; color: #000; }
                 #${CONFIG.panelId} button {
                    cursor: pointer; background-color: #f0f0f0; color: #333; transition: background-color 0.2s ease, transform 0.1s ease;
                    border: 1px solid #bbb; text-align: center; font-size: 11px; padding: 5px 6px;
                 }
                 #${CONFIG.panelId} button:hover { background-color: #e0e0e0; }
                 #${CONFIG.panelId} button:active { transform: scale(0.98); }

                 #${CONFIG.panelId} #${CONFIG.startButtonId} { background-color: #4CAF50; color: white; border-color: #3a8d3d;}
                 #${CONFIG.panelId} #${CONFIG.startButtonId}:hover { background-color: #45a049; }
                 #${CONFIG.panelId} #${CONFIG.stopButtonId} { background-color: #f44336; color: white; border-color: #c4302b;}
                 #${CONFIG.panelId} #${CONFIG.stopButtonId}:hover { background-color: #da190b; }
                 #${CONFIG.panelId} #${CONFIG.clearFailedButtonId} { background-color: #ff9800; color: white; border-color: #c67600;}
                 #${CONFIG.panelId} #${CONFIG.clearFailedButtonId}:hover { background-color: #f57c00; }

                 #${CONFIG.panelId} select {
                    background-color: #fff; color: #333; cursor: pointer; font-size: 11px; padding: 5px 6px;
                 }
                 #${CONFIG.panelId} select:hover { background-color: #f8f8f8; }

                #${CONFIG.suggestionBoxId} {
                    display: none; border: 1px solid #aaa; background: #fff;
                    position: absolute; max-height: 120px; overflow-y: auto;
                    z-index: ${CONFIG.suggestionZIndex}; box-shadow: 0 3px 6px rgba(0,0,0,0.2);
                    border-radius: 0 0 4px 4px; margin-top: -1px; font-size: 11px;
                }
                .${CONFIG.suggestionItemClass} {
                    padding: 5px 8px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #222;
                }
                .${CONFIG.suggestionItemClass}:hover { background-color: #f0f0f0; }
                .${CONFIG.suggestionItemClass}.highlighted { background-color: #007bff; color: white; }

                #${CONFIG.statusBoxId} {
                    margin-top: 3px; color: #333; font-weight: 500; font-size: 10px; text-align: center;
                    padding: 5px; background-color: #f9f9f9; border-radius: 3px; border: 1px solid #e5e5e5;
                    line-height: 1.3; min-height: 26px; display: flex; align-items: center; justify-content: center;
                 }
                 #${CONFIG.statusBoxId}.status-running { color: #007bff; }
                 #${CONFIG.statusBoxId}.status-stopped { color: #dc3545; }
                 #${CONFIG.statusBoxId}.status-success { color: #28a745; }
                 #${CONFIG.statusBoxId}.status-warning { color: #ffc107; text-shadow: 0 0 1px #aaa; }
                 #${CONFIG.statusBoxId}.status-error { color: #dc3545; font-weight: bold; }

                .${CONFIG.debugMarkerClass} {
                    position: absolute; width: 8px; height: 8px; border-radius: 50%;
                    z-index: ${CONFIG.markerZIndex}; pointer-events: none; opacity: 0.80;
                    box-shadow: 0 0 4px 1px rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.5);
                    transition: opacity 0.5s ease-out;
                }

                @media (max-width: 480px) {
                    #${CONFIG.panelId} {
                        width: 180px;
                        font-size: 11px;
                        padding: 6px;
                        gap: 4px;
                    }
                    #${CONFIG.panelId} input, #${CONFIG.panelId} button, #${CONFIG.panelId} select {
                        padding: 5px 6px;
                        font-size: 11px;
                    }
                    #${CONFIG.statusBoxId} {
                        font-size: 9px;
                        padding: 4px;
                        min-height: 24px;
                    }
                }
            `;
            const styleSheet = document.createElement("style");
            styleSheet.id = `${CONFIG.panelId}-styles`;
            styleSheet.type = "text/css";
            styleSheet.innerText = css;
            document.head.appendChild(styleSheet);
        }

        _setupUI() {
            const existingPanel = document.getElementById(CONFIG.panelId);
            if (existingPanel) existingPanel.remove();

            this.panel = document.createElement('div');
            this.panel.id = CONFIG.panelId;
            this.panel.innerHTML = `
                <div>✨ Auto Combiner</div>
                <input id="${CONFIG.targetInputId}" placeholder="Target Element" autocomplete="off">
                <div id="${CONFIG.suggestionBoxId}"></div>
                <select id="${CONFIG.speedSelectId}" title="Speed">
                    <option value="slow">🐌 Slow</option>
                    <option value="normal" selected>⚡ Normal</option>
                    <option value="fast">🚀 Fast</option>
                    <option value="turbo">💨 Turbo</option>
                </select>
                <button id="${CONFIG.startButtonId}">▶️ Start</button>
                <button id="${CONFIG.clearFailedButtonId}">🗑️ Clear Fails</button>
                <button id="${CONFIG.stopButtonId}">⛔ Stop</button>
                <div id="${CONFIG.statusBoxId}">Initializing...</div>
            `;
            document.body.appendChild(this.panel);

            // Get references using panel.querySelector
            this.targetInput = this.panel.querySelector(`#${CONFIG.targetInputId}`);
            this.suggestionBox = this.panel.querySelector(`#${CONFIG.suggestionBoxId}`);
            this.statusBox = this.panel.querySelector(`#${CONFIG.statusBoxId}`);
            this.startButton = this.panel.querySelector(`#${CONFIG.startButtonId}`);
            this.stopButton = this.panel.querySelector(`#${CONFIG.stopButtonId}`);
            this.clearFailedButton = this.panel.querySelector(`#${CONFIG.clearFailedButtonId}`);
            this.speedSelect = this.panel.querySelector(`#${CONFIG.speedSelectId}`);

             console.log('[AutoCombo] UI Element References:', {
                panel: !!this.panel, targetInput: !!this.targetInput, suggestionBox: !!this.suggestionBox,
                statusBox: !!this.statusBox, startButton: !!this.startButton, stopButton: !!this.stopButton,
                clearFailedButton: !!this.clearFailedButton, speedSelect: !!this.speedSelect
             });
             if (!this.targetInput || !this.statusBox || !this.startButton || !this.stopButton || !this.clearFailedButton || !this.speedSelect) {
                 throw new Error("One or more required UI elements not found within the panel.");
             }
        }

        _setupEventListeners() {
            if (!this.targetInput || !this.startButton || !this.stopButton || !this.clearFailedButton || !this.speedSelect) {
                throw new Error("Cannot setup listeners: Required UI elements missing.");
            }

            this.targetInput.addEventListener('input', () => this._updateSuggestions());
            this.targetInput.addEventListener('keydown', e => this._handleSuggestionKey(e));
            this.targetInput.addEventListener('focus', () => this._updateSuggestions());

            document.addEventListener('click', (e) => {
                 if (this.panel && !this.panel.contains(e.target) && this.suggestionBox && !this.suggestionBox.contains(e.target)) {
                    if (this.suggestionBox.style.display === 'block') this.suggestionBox.style.display = 'none';
                }
            }, true);

            this.startButton.onclick = () => this.startAutoCombo();
            this.stopButton.onclick = () => this.stop();
            this.clearFailedButton.onclick = () => this._clearFailedCombos();
            this.speedSelect.onchange = () => this._handleSpeedChange();
        }

        _loadFailedCombos() {
            const savedCombos = localStorage.getItem(CONFIG.storageKeyFailedCombos);
            let loadedCount = 0;
            if (savedCombos) {
                try {
                    const parsedCombos = JSON.parse(savedCombos);
                    if (Array.isArray(parsedCombos)) {
                        const validCombos = parsedCombos.filter(item => typeof item === 'string');
                        this.failedCombos = new Set(validCombos);
                        loadedCount = this.failedCombos.size;
                        if (loadedCount > 0) {
                             this.logStatus(`📚 Loaded ${loadedCount} fails`, 'status-success');
                        }
                    } else {
                         localStorage.removeItem(CONFIG.storageKeyFailedCombos);
                         this.failedCombos = new Set();
                    }
                } catch (e) {
                    console.error('[AutoCombo] Error parsing failed combos:', e);
                    localStorage.removeItem(CONFIG.storageKeyFailedCombos);
                    this.failedCombos = new Set();
                }
            } else {
                 this.failedCombos = new Set();
            }
             console.log(`[AutoCombo] Failed combos loaded: ${loadedCount}`);
        }

        _saveFailedCombos() {
            if (this.failedCombos.size === 0) {
                localStorage.removeItem(CONFIG.storageKeyFailedCombos);
                return;
            }
            try {
                localStorage.setItem(CONFIG.storageKeyFailedCombos, JSON.stringify(Array.from(this.failedCombos)));
            } catch (e) {
                console.error('[AutoCombo] Error saving failed combos:', e);
                this.logStatus('❌ Save error!', 'status-error');
            }
        }

        _clearFailedCombos() {
            const count = this.failedCombos.size;
            this.failedCombos.clear();
            localStorage.removeItem(CONFIG.storageKeyFailedCombos);
            this.logStatus(`🗑️ Cleared ${count} fails`, 'status-success');
             console.log(`[AutoCombo] Cleared ${count} failed combos.`);
        }

        _loadSpeed() {
            const savedSpeed = localStorage.getItem(CONFIG.storageKeySpeed);
            if (savedSpeed && CONFIG.speedPresets[savedSpeed]) {
                this.currentSpeed = savedSpeed;
            } else {
                this.currentSpeed = 'normal';
            }
            if (this.speedSelect) {
                this.speedSelect.value = this.currentSpeed;
            }
            this._applySpeed();
            console.log(`[AutoCombo] Speed loaded: ${this.currentSpeed}`);
        }

        _handleSpeedChange() {
            if (!this.speedSelect) return;
            this.currentSpeed = this.speedSelect.value;
            localStorage.setItem(CONFIG.storageKeySpeed, this.currentSpeed);
            this._applySpeed();
            this.logStatus(`⚙️ ${this.currentSpeed.toUpperCase()}`, 'status-success');
            console.log(`[AutoCombo] Speed changed to: ${this.currentSpeed}`);
        }

        _applySpeed() {
            const preset = CONFIG.speedPresets[this.currentSpeed];
            if (!preset) {
                console.warn(`[AutoCombo] Invalid speed preset: ${this.currentSpeed}, using normal`);
                this.currentSpeed = 'normal';
                return this._applySpeed();
            }
            CONFIG.interComboDelay = preset.interComboDelay;
            CONFIG.postComboScanDelay = preset.postComboScanDelay;
            CONFIG.dragStepDelay = preset.dragStepDelay;
            CONFIG.dragBetweenDelay = preset.dragBetweenDelay;
        }

        // --- Core Logic ---

        scanItems() {
            clearTimeout(this.scanDebounceTimer);
            this.scanDebounceTimer = null;

            const items = document.querySelectorAll(CONFIG.itemSelector);
            let changed = false;
            const currentNames = new Set();
            const oldSize = this.itemElementMap.size;

            for (const el of items) {
                if (!el) continue;
                // Use data-item-text attribute for clean element name
                const name = el.getAttribute('data-item-text');
                if (name && typeof name === 'string') {
                    currentNames.add(name);
                    if (!this.itemElementMap.has(name) || this.itemElementMap.get(name) !== el) {
                        this.itemElementMap.set(name, el);
                        changed = true;
                    }
                }
            }

            const currentKeys = Array.from(this.itemElementMap.keys());
            for (const name of currentKeys) {
                if (!currentNames.has(name)) {
                    this.itemElementMap.delete(name);
                    changed = true;
                }
            }

            if (changed && !this.isRunning) {
                const newSize = this.itemElementMap.size;
                const diff = newSize - oldSize;
                let logMsg = `🔍 ${newSize} items`;
                if (diff > 0) logMsg += ` (+${diff})`; else if (diff < 0) logMsg += ` (${diff})`;
                this.logStatus(logMsg);
                console.log(`[AutoCombo] ${logMsg}`);
                 if (document.activeElement === this.targetInput) {
                     this._updateSuggestions();
                 }
            }
            return changed;
        }

        observeDOM() {
             const targetNode = document.querySelector(CONFIG.gameContainerSelector);
             if (!targetNode) {
                  console.error("[AutoCombo] Cannot observe DOM: Target node not found:", CONFIG.gameContainerSelector);
                  this.logStatus(`❌ Observer error!`, "status-error");
                  return;
             }

            const observer = new MutationObserver((mutationsList) => {
                 let potentiallyRelevantChange = false;
                 for (const mutation of mutationsList) {
                     if (mutation.type === 'childList') {
                         const checkNodes = (nodes) => {
                            if (!nodes) return false;
                            for(const node of nodes) {
                                if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches(CONFIG.itemSelector)) return true;
                            }
                            return false;
                         }
                         if (checkNodes(mutation.addedNodes) || checkNodes(mutation.removedNodes)) {
                            potentiallyRelevantChange = true;
                            break;
                         }
                     }
                 }

                if (potentiallyRelevantChange) {
                    clearTimeout(this.scanDebounceTimer);
                    this.scanDebounceTimer = setTimeout(() => {
                        this.scanItems();
                    }, CONFIG.scanDebounceDelay);
                }
            });

            observer.observe(targetNode, {
                childList: true,
                subtree: true,
             });
             console.log("[AutoCombo] DOM Observer started on:", targetNode);
        }

        stop() {
            if (!this.isRunning) return;
            this.isRunning = false;
            clearTimeout(this.scanDebounceTimer);
            this.logStatus('⛔ Stopped', 'status-stopped');
            console.log('[AutoCombo] Stop requested.');
        }

        async startAutoCombo() {
            if (this.isRunning) {
                this.logStatus('⚠️ Already running', 'status-warning'); return;
            }

            const targetName = this.targetInput.value.trim();
            if (!targetName) {
                this.logStatus('⚠️ Enter Target', 'status-warning'); this.targetInput.focus(); return;
            }

            this.scanItems();
            let targetElement = this.getElement(targetName);
            if (!targetElement || !document.body.contains(targetElement)) {
                this.logStatus(`⚠️ "${targetName}" not found`, 'status-warning'); this.targetInput.focus(); return;
            }

            const itemsToProcess = Array.from(this.itemElementMap.keys()).filter(name => name !== targetName);
            if (itemsToProcess.length === 0) {
                this.logStatus(`ℹ️ No items to combine`); return;
            }

            this.isRunning = true;
            this.logStatus(`🚀 Starting... (${itemsToProcess.length})`, 'status-running');
            console.log(`[AutoCombo] Starting combinations for "${targetName}". Items: ${itemsToProcess.length}. Fails: ${this.failedCombos.size}`);

            let processedCount = 0, attemptedCount = 0, successCount = 0, skippedCount = 0;
            const totalPotentialCombos = itemsToProcess.length;

            for (const itemName of itemsToProcess) {
                if (!this.isRunning) break;

                processedCount++;
                const progress = `${processedCount}/${totalPotentialCombos}`;

                const comboKey = this._getComboKey(targetName, itemName);
                if (this.failedCombos.has(comboKey)) {
                    if (processedCount % 20 === 0 || processedCount === totalPotentialCombos) {
                        this.logStatus(`⏭️ Skipping... ${progress}`, 'status-running');
                    }
                     console.log(`[AutoCombo] ${progress} Skipping known fail: ${targetName} + ${itemName}`);
                     skippedCount++;
                    await new Promise(res => setTimeout(res, 2));
                    continue;
                }

                targetElement = this.getElement(targetName);
                if (!targetElement || !document.body.contains(targetElement)) {
                    this.logStatus(`⛔ Target lost!`, 'status-error');
                    console.error(`[AutoCombo] Target element "${targetName}" disappeared mid-process.`);
                    this.isRunning = false; break;
                }

                const sourceElement = this.getElement(itemName);
                if (!sourceElement || !document.body.contains(sourceElement)) {
                    this.logStatus(`⚠️ Skip ${progress}`, 'status-warning');
                    console.warn(`[AutoCombo] ${progress} Skipping "${itemName}": Element not found/removed.`);
                    continue;
                }

                this.logStatus(`⏳ ${progress}`, 'status-running');
                console.log(`[AutoCombo] ${progress} Attempting: ${targetName} + ${itemName}`);
                attemptedCount++;

                const itemsBeforeCombo = new Set(this.itemElementMap.keys());

                try {
                    await this.simulateCombo(sourceElement, targetElement);
                    if (!this.isRunning) break;

                    await new Promise(res => setTimeout(res, CONFIG.postComboScanDelay));
                    if (!this.isRunning) break;

                    this.scanItems();
                    const itemsAfterCombo = new Set(this.itemElementMap.keys());

                    let newItemFound = null;
                    for (const itemAfter of itemsAfterCombo) {
                        if (!itemsBeforeCombo.has(itemAfter)) { newItemFound = itemAfter; break; }
                    }

                    if (newItemFound) {
                        successCount++;
                        this.logStatus(`✨ ${newItemFound}!`, 'status-success');
                        console.log(`[AutoCombo] SUCCESS! New: ${newItemFound} from ${targetName} + ${itemName}`);
                    } else {
                        const targetStillExists = itemsAfterCombo.has(targetName);
                        const sourceStillExists = itemsAfterCombo.has(itemName);
                        this.logStatus(`❌ Fail ${progress}`, 'status-running');
                        console.log(`[AutoCombo] FAILURE: No new item from ${targetName} + ${itemName}. T:${targetStillExists}, S:${sourceStillExists}`);
                        this.failedCombos.add(comboKey);
                        this._saveFailedCombos();
                    }

                    await new Promise(res => setTimeout(res, CONFIG.interComboDelay));

                } catch (error) {
                    this.logStatus(`❌ Error ${progress}`, 'status-error');
                    console.error(`[AutoCombo] Error during combo for "${itemName}" + "${targetName}":`, error);
                }
            }

            if (this.isRunning) {
                this.isRunning = false;
                const summary = `✅ Done! New: ${successCount}`;
                this.logStatus(summary, 'status-success');
                console.log(`[AutoCombo] Done. Tried: ${attemptedCount}, New: ${successCount}, Skipped: ${skippedCount}.`);
            } else {
                const summary = `⛔ Stopped. New: ${successCount}`;
                this.logStatus(summary, 'status-stopped');
                console.log(`[AutoCombo] Stopped. Tried: ${attemptedCount}, New: ${successCount}, Skipped: ${skippedCount}.`);
            }
        }


        // --- Simulation ---

        async simulateCombo(sourceElement, targetElement) {
            if (!this.isRunning) return;

            // Get center of the canvas/screen for combining
            const centerX = Math.floor(window.innerWidth / 2);
            const centerY = Math.floor(window.innerHeight / 2);

            // Drag target element to center first (offset upward)
            await this.simulateDrag(targetElement, centerX, centerY - 30, 'rgba(255, 100, 50, 0.7)');
            
            // Small delay between drags (using speed setting)
            await new Promise(res => setTimeout(res, CONFIG.dragBetweenDelay));
            if (!this.isRunning) return;

            // Drag source element to center (offset downward) to combine
            await this.simulateDrag(sourceElement, centerX, centerY + 2, 'rgba(50, 150, 255, 0.7)');
        }

        async simulateDrag(element, dropX, dropY, markerColor) {
            if (!this.isRunning || !element || !document.body.contains(element)) {
                 console.warn("[AutoCombo] simulateDrag: Element invalid or drag stopped.");
                 return;
            }

            const rect = element.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) {
                 throw new Error(`Dragged elem "${element.getAttribute('data-item-text') || 'unknown'}" has no size.`);
            }

            // Starting position (center of element)
            const startX = rect.left + rect.width / 2;
            const startY = rect.top + rect.height / 2;

            // Show debug markers
            this.showDebugMarker(startX, startY, markerColor);
            this.showDebugMarker(dropX, dropY, markerColor);

            try {
                // Mouse Down on element
                element.dispatchEvent(new MouseEvent('mousedown', {
                    bubbles: true,
                    clientX: startX,
                    clientY: startY
                }));
                
                await new Promise(res => setTimeout(res, CONFIG.dragStepDelay));
                if (!this.isRunning) return;

                // Mouse Move to drop position
                document.dispatchEvent(new MouseEvent('mousemove', {
                    bubbles: true,
                    clientX: dropX,
                    clientY: dropY
                }));
                
                await new Promise(res => setTimeout(res, CONFIG.dragStepDelay));
                if (!this.isRunning) return;

                // Mouse Up at drop position
                document.dispatchEvent(new MouseEvent('mouseup', {
                    bubbles: true,
                    clientX: dropX,
                    clientY: dropY
                }));
                
                console.log(`[AutoCombo] Dragged ${element.getAttribute('data-item-text') || 'unknown'} from (${Math.round(startX)}, ${Math.round(startY)}) to (${Math.round(dropX)}, ${Math.round(dropY)})`);

            } catch (error) {
                 console.error('[AutoCombo] Error during drag simulation step:', error);
                 throw new Error(`Drag sim failed: ${error.message}`);
            }
        }

        // --- UI & Suggestions ---

        _updateSuggestions() {
            if (!this.targetInput || !this.suggestionBox) return;
            const query = this.targetInput.value.toLowerCase();
            if (!query) {
                this.suggestions = []; this.suggestionBox.style.display = 'none'; return;
            }
            const currentItems = Array.from(this.itemElementMap.keys());
            this.suggestions = currentItems
                .filter(name => name.toLowerCase().includes(query))
                .sort((a, b) => {
                    const aI = a.toLowerCase().indexOf(query), bI = b.toLowerCase().indexOf(query);
                    if (aI !== bI) return aI - bI; return a.localeCompare(b);
                })
                .slice(0, CONFIG.suggestionLimit);
            this.suggestionIndex = -1;
            this._updateSuggestionUI();
        }

        _updateSuggestionUI() {
            if (!this.targetInput || !this.suggestionBox) return;
            this.suggestionBox.innerHTML = '';
            if (!this.suggestions.length) { this.suggestionBox.style.display = 'none'; return; }

            const inputRect = this.targetInput.getBoundingClientRect();
            Object.assign(this.suggestionBox.style, {
                position: 'absolute', display: 'block',
                top: `${inputRect.bottom + window.scrollY}px`, left: `${inputRect.left + window.scrollX}px`,
                width: `${inputRect.width}px`, maxHeight: '120px', overflowY: 'auto', zIndex: CONFIG.suggestionZIndex,
            });

            this.suggestions.forEach((name, index) => {
                const div = document.createElement('div');
                div.textContent = name; div.className = CONFIG.suggestionItemClass; div.title = name;
                div.addEventListener('mousedown', (e) => { e.preventDefault(); this._handleSuggestionSelection(name); });
                this.suggestionBox.appendChild(div);
            });
            this._updateSuggestionHighlight();
        }

        _handleSuggestionKey(e) {
            if (!this.suggestionBox || this.suggestionBox.style.display !== 'block' || !this.suggestions.length) {
                if (e.key === CONFIG.keyEnter) { e.preventDefault(); this.startAutoCombo(); } return;
            }
            const numSuggestions = this.suggestions.length;
            switch (e.key) {
                case CONFIG.keyArrowDown: case CONFIG.keyTab:
                    e.preventDefault(); this.suggestionIndex = (this.suggestionIndex + 1) % numSuggestions; this._updateSuggestionHighlight(); break;
                case CONFIG.keyArrowUp:
                    e.preventDefault(); this.suggestionIndex = (this.suggestionIndex - 1 + numSuggestions) % numSuggestions; this._updateSuggestionHighlight(); break;
                case CONFIG.keyEnter:
                    e.preventDefault();
                    if (this.suggestionIndex >= 0) this._handleSuggestionSelection(this.suggestions[this.suggestionIndex]);
                    else { this.suggestionBox.style.display = 'none'; this.startAutoCombo(); } break;
                case 'Escape':
                    e.preventDefault(); this.suggestionBox.style.display = 'none'; break;
            }
        }

        _updateSuggestionHighlight() {
            if (!this.suggestionBox) return;
            Array.from(this.suggestionBox.children).forEach((child, i) => child.classList.toggle('highlighted', i === this.suggestionIndex));
            this._scrollSuggestionIntoView();
        }

        _scrollSuggestionIntoView() {
             if (!this.suggestionBox) return;
             const highlightedItem = this.suggestionBox.querySelector(`.${CONFIG.suggestionItemClass}.highlighted`);
             if (highlightedItem) {
                 setTimeout(() => highlightedItem.scrollIntoView?.({ block: 'nearest' }), CONFIG.suggestionHighlightDelay);
             }
        }

        _handleSuggestionSelection(name) {
            if (!this.targetInput || !this.suggestionBox) return;
            this.targetInput.value = name; this.suggestionBox.style.display = 'none';
            this.suggestions = []; this.targetInput.focus();
        }

        // --- Utilities ---

        getElement(name) { return this.itemElementMap.get(name) || null; }

        showDebugMarker(x, y, color = 'red', duration = CONFIG.debugMarkerDuration) {
            const dot = document.createElement('div');
            dot.className = CONFIG.debugMarkerClass;
            Object.assign(dot.style, {
                top: `${y - 4}px`, left: `${x - 4}px`, backgroundColor: color, position: 'absolute', opacity: '0.8'
            });
            document.body.appendChild(dot);
            setTimeout(() => {
                dot.style.opacity = '0';
                setTimeout(() => dot.remove(), 500);
            }, duration - 500);
        }

        logStatus(msg, type = 'info') {
             if (!this.statusBox) { console.log('[AutoCombo Status]', msg); return; }
            this.statusBox.textContent = msg;
            this.statusBox.className = `${CONFIG.statusBoxId}`;
            if (type !== 'info') this.statusBox.classList.add(`status-${type}`);
            if (type !== 'info' && type !== 'status-running' || !this.isRunning) {
                 console.log(`[AutoCombo Status - ${type}]`, msg);
            }
        }

        _getComboKey(name1, name2) { return [name1, name2].sort().join('||'); }
    }

    // --- Initialization ---
    if (window.infCraftAutoComboInstance) {
        console.warn("[AutoCombo] Instance already running. Aborting new init.");
    } else {
        console.log("[AutoCombo] Creating new instance...");
        window.infCraftAutoComboInstance = new AutoTargetCombo();
    }

})();