/**
 * Texter Captcha Solver Module
 * -------------------------------------------------
 * Runs in the PAGE context (injected via content.js).
 * Detects which site we're on and routes to the
 * correct solver API via:
 *   page → postMessage → content.js → background.js (fetch) → back
 *
 * NOW DYNAMIC: Site detection, selectors, and engine config
 * are fetched from the server and cached. Hardcoded defaults
 * serve as fallback until dynamic config is loaded.
 */

/* ── Hardcoded fallback site detection (used before dynamic config loads) ── */
const _FALLBACK_SITE_MAP = [
    { key: 'uidai', hostnames: ['uidai.gov.in'] },
    { key: 'wbreg', hostnames: ['wbregistration.gov.in'] },
    { key: 'banglarbhumi', hostnames: ['banglarbhumi.gov.in'] },
    { key: 'eci', hostnames: ['eci.gov.in', 'voters.eci.gov.in', 'nvsp.in'] },
    { key: 'examservices', hostnames: ['examinationservices.nic.in'] },
    { key: 'manabik', hostnames: ['jaibangla.wb.gov.in', 'socialsecurity.wb.gov.in'] },
    { key: 'emp', hostnames: ['employmentbankwb.gov.in'] },
    { key: 'wbsedcl', hostnames: ['portal.wbsedcl.in', 'wbsedcl.in'] },
    { key: 'wbpds', hostnames: ['wbpds.wb.gov.in', 'wbpds'] },
    { key: 'rupashree', hostnames: ['rupashree.wb.gov.in'] },
    { key: 'edistwb', hostnames: ['edistrict.wb.gov.in'] },
    { key: 'kanyashree', hostnames: ['wbkanyashree.gov.in', 'kanyashree'] },
    { key: 'wbadmission', hostnames: ['admissions.nic.in'] },
    { key: 'eshram', hostnames: ['eshram.gov.in', 'eshram'] },
    { key: 'irctc', hostnames: ['irctc.co.in'] },
    { key: 'pmkisan', hostnames: ['pmkisan.gov.in', 'pmkisan'] },
    { key: 'pmjay', hostnames: ['beneficiary.nha.gov.in', 'nha.gov.in'] },
    { key: 'wbprms', hostnames: ['wbprms.in', 'wbprms'] },
    { key: 'wbmdfc', hostnames: ['wbmdfc.org', 'wbmdfc', 'wbmdfcscholarship'] },
];

/* ── Dynamic config state ── */
let _dynamicConfig = null;  // Will be populated from server config
let _currentSite = null;
let _currentEngine = null;  // The full engine object from config

// Hardcoded fallback selectors (used before dynamic config loads)
const _FALLBACK_CAPTCHA_IMG_SEL = '.captcha-div img, img.captcha-img, img[alt="Captcha Image here"], img[alt="Captcha"], img[alt="captcha"], img.captchaImg, img[alt*="CAPTCHA"], img[src^="data:application/image"], img#ItemPreview, img#ctl00_CPH_Img_Capcha, img#captchaImg, #ctl00_ContentPlaceHolder1_captchaimage, #ctl00_ContentPlaceHolder1_imgCaptcha, span.refereshrecapcha img, img[src*="captcha/flat"], img[name="imageName"], img[src*="CaptchaSecurityImages"], img[src*="CaptchaSecurityImages.php"], img[src*="/Captcha/"], img#ctl00_CPH_Bank_Details_Img_Capcha, img[src*="CImage.aspx"], img#ctl00_ContentPlaceHolder1_Image2, img#image-captcha, img[src*="captcha.php"], img#ctl00_ContentPlaceHolder1_ImgCaptchaFinal, img#ctl00_ContentPlaceHolder1_ImgCaptchaOTP, img#ContentPlaceHolder1_capchaimg, img#ContentPlaceHolder1_Image1, input#captcha[captcha], .captcha-container, #image_captcha img, .captcha img, img[src*="mdfcfiles/captcha_image/"], img[src*="captcha_image"]';
const _FALLBACK_CAPTCHA_INPUT_SEL = 'input[formcontrolname="captcha"], input[formcontrolname="captchValue"], input#captcha, input#captch, input[name="captcha"], input[name="security_code"], input[aria-label="Enter Captcha"], input#ctl00_CPH_txtCapcha, input#ctl00_CPH_txtSecurityCode, input#txtInput, #ctl00_ContentPlaceHolder1_txtsecpin, #ctl00_ContentPlaceHolder1_TxtSecPin, input[id*="InputField2"], input#ctl00_CPH_Bank_Details_txtCapcha, input#ctl00_ContentPlaceHolder1_txtimgcode, input#ctl00_ContentPlaceHolder1_txtCaptcha, input#ctl00_ContentPlaceHolder1_TxtSecPinFinal, input#ctl00_ContentPlaceHolder1_TxtSecPinOTP, input#ContentPlaceHolder1_txtcaptcha, input#ContentPlaceHolder1_TextCapcha_Reg, input#ContentPlaceHolder1_txtcaptcha_Front, input[formcontrolname="captchaNumber"], #capt, #captch, #cpat,input[name="captca"], img[src*="captcha_image"] ~ input ';
const _FALLBACK_REFRESH_SEL = '[aria-label="Click to refresh Captcha"], [aria-label="Captcha Refresh"], img[alt="refrsh captcha"], img[alt="refresh"], img[alt="Refresh"], img#refresh, img[src*="RefreshIcon"], img[src*="refresh1.png"], img[src*="icon-refresh.png"], img[src*="captcha-refresh.png"], svg[aria-label="Refresh captcha"], input[title="Refresh Captcha"], a[onclick*="refreshCaptchaimg"], a[onclick*="reloadImage()"], a[onclick*="reloadImage"], a[href*="track-applicant-public"], a[href*="track-applicant"], #ctl00_ContentPlaceHolder1_bttRegCaptcha, #ctl00_ContentPlaceHolder1_bttRegCaptcha, button#refresh, a[id*="LinkToAction"], input#ctl00_CPH_Bank_Details_btnImage, input#ctl00_ContentPlaceHolder1_Imagerefresh, #refresh-captcha, .fa-rotate, #change-image, .reset_captcha, a#ctl00_ContentPlaceHolder1_imgRefreshCaptcha, input#ctl00_ContentPlaceHolder1_imgRefreshCaptchaOTP, input#ContentPlaceHolder1_refreshcaptch, img[src*="refresh.png"], input#ContentPlaceHolder1_refreshcaptchReg, .bi-arrow-clockwise, .fa-refresh, i.fa-refresh, button:has(.fa-refresh),#captcharefresh';

/** Detect current site using dynamic config or fallback */
function detectCurrentSite(engines) {
    const host = window.location.hostname;
    const engineList = engines || _FALLBACK_SITE_MAP;

    for (const engine of engineList) {
        const hostnames = engine.hostnames || [];
        for (const pattern of hostnames) {
            if (host.includes(pattern)) {
                return engine;
            }
        }
    }
    return null; // unknown site
}

/** Build the CSS selector string: per-site overrides + universal selectors */
function buildSelector(type) {
    // type: 'captchaImg', 'captchaInput', 'refresh'
    let parts = [];

    // 1. Per-site overrides (highest priority — prepended)
    if (_currentEngine) {
        const siteSelectors = type === 'captchaImg' ? _currentEngine.captchaImgSelectors
            : type === 'captchaInput' ? _currentEngine.captchaInputSelectors
            : _currentEngine.refreshSelectors;
        if (siteSelectors && Array.isArray(siteSelectors) && siteSelectors.length > 0) {
            parts.push(...siteSelectors);
        }
    }

    // 2. Universal selectors from dynamic config
    if (_dynamicConfig && _dynamicConfig.universalSelectors) {
        const universal = _dynamicConfig.universalSelectors[type];
        if (universal && Array.isArray(universal) && universal.length > 0) {
            parts.push(...universal);
        }
    }

    // 3. If no dynamic selectors available, use hardcoded fallback
    if (parts.length === 0) {
        if (type === 'captchaImg') return _FALLBACK_CAPTCHA_IMG_SEL;
        if (type === 'captchaInput') return _FALLBACK_CAPTCHA_INPUT_SEL;
        if (type === 'refresh') return _FALLBACK_REFRESH_SEL;
    }

    // Remove duplicates and join
    return [...new Set(parts)].join(', ');
}

/** Get the special handler for current engine */
function getSpecialHandler() {
    if (_currentEngine && _currentEngine.specialHandler) {
        return _currentEngine.specialHandler;
    }
    // Fallback: detect from key
    if (_currentSite === 'pmjay') return 'pmjay_attr';
    if (_currentSite === 'wbprms') return 'wbprms_text';
    if (_currentSite === 'eshram') return 'eshram_intercept';
    return null;
}

/** Check if current engine is a fast site */
function isFastSite() {
    if (_currentEngine && typeof _currentEngine.isFastSite !== 'undefined') {
        return _currentEngine.isFastSite;
    }
    // Fallback
    return ['irctc', 'eshram', 'pmjay', 'wbprms'].includes(_currentSite);
}

// ── Initial site detection (immediate, using fallback) ──
const _fallbackEngine = detectCurrentSite(null);
if (_fallbackEngine) {
    _currentSite = _fallbackEngine.key;
    _currentEngine = _fallbackEngine;
}

/** Load dynamic config from background and re-detect site */
function loadDynamicConfig() {
    return new Promise((resolve) => {
        const requestId = "__config_" + Date.now();
        _captchaPendingCallbacks[requestId] = (config) => {
            if (config && config.engines) {
                _dynamicConfig = config;
                const engine = detectCurrentSite(config.engines);
                if (engine) {
                    _currentSite = engine.key;
                    _currentEngine = engine;
                    console.log(`[CaptchaSolver] Dynamic config loaded — site: ${_currentSite} (${config.engines.length} engines)`);
                } else {
                    console.log(`[CaptchaSolver] Dynamic config loaded — site not matched (${config.engines.length} engines)`);
                }
            }
            resolve();
        };
        window.postMessage({ type: "__GET_SOLVER_CONFIG__", requestId }, "*");
        setTimeout(() => {
            if (_captchaPendingCallbacks[requestId]) {
                delete _captchaPendingCallbacks[requestId];
                console.log("[CaptchaSolver] Config fetch timed out — using fallback");
                resolve();
            }
        }, 5000);
    });
}

console.log(`[CaptchaSolver] Site detected (fallback): ${_currentSite}`);

/* ─────────────────────────────────────────────────
   1.  Message relay: send captcha to background for solving
   ───────────────────────────────────────────────── */

const _captchaPendingCallbacks = {};   // requestId → resolve
let _captchaReqCounter = 0;

// Listen for solver responses from the content script
window.addEventListener("message", function (event) {
    if (event.source !== window) return;
    if (event.data && event.data.type === "__SOLVE_CAPTCHA_RESPONSE__") {
        const cb = _captchaPendingCallbacks[event.data.requestId];
        if (cb) {
            cb(event.data.result);
            delete _captchaPendingCallbacks[event.data.requestId];
        }
    }
    if (event.data && event.data.type === "__FETCH_IMAGE_RESPONSE__") {
        const cb = _captchaPendingCallbacks[event.data.requestId];
        if (cb) {
            cb(event.data.result);
            delete _captchaPendingCallbacks[event.data.requestId];
        }
    }
    if (event.data && event.data.type === "__SOLVER_CONFIG_RESPONSE__") {
        const cb = _captchaPendingCallbacks[event.data.requestId];
        if (cb) {
            cb(event.data.config);
            delete _captchaPendingCallbacks[event.data.requestId];
        }
    }
});

let _imgFetchReqCounter = 0;
function fetchImageBase64(url) {
    return new Promise((resolve) => {
        const requestId = "__fetch_" + (++_imgFetchReqCounter) + "_" + Date.now();
        _captchaPendingCallbacks[requestId] = resolve;
        window.postMessage({ type: "__FETCH_IMAGE_REQUEST__", url, requestId }, "*");
        setTimeout(() => {
            if (_captchaPendingCallbacks[requestId]) {
                delete _captchaPendingCallbacks[requestId];
                resolve({ success: false, error: "Fetch timeout" });
            }
        }, 10000);
    });
}

/**
 * Solve a base64 captcha image via the background service worker.
 * @param {string} base64Image - base64 string (data-URI prefix stripped automatically)
 * @returns {Promise<{success: boolean, text: string, confidence: number, error?: string}>}
 */
function solveCaptcha(base64Image) {
    return new Promise((resolve) => {
        const requestId = "__captcha_" + (++_captchaReqCounter) + "_" + Date.now();
        _captchaPendingCallbacks[requestId] = resolve;

        window.postMessage({
            type: "__SOLVE_CAPTCHA_REQUEST__",
            captcha: base64Image,
            solverType: _currentSite,
            hostname: window.location.hostname,
            requestId: requestId,
        }, "*");

        // Timeout after 15s
        setTimeout(() => {
            if (_captchaPendingCallbacks[requestId]) {
                delete _captchaPendingCallbacks[requestId];
                resolve({ success: false, text: "", confidence: 0, error: "Solver timeout" });
            }
        }, 15000);
    });
}

/* ─────────────────────────────────────────────────
   2.  Toast notification (top-right corner)
   ───────────────────────────────────────────────── */

function showToast(message, type = "success") {
    // Create container if needed
    let container = document.getElementById("__captcha-toast-container");
    if (!container) {
        container = document.createElement("div");
        container.id = "__captcha-toast-container";
        container.style.cssText = `
            position: fixed; top: 16px; right: 16px; z-index: 999999;
            display: flex; flex-direction: column; gap: 8px;
            pointer-events: none; font-family: 'Segoe UI', sans-serif;
        `;
        document.body.appendChild(container);
    }

    const toast = document.createElement("div");
    const bg = type === "success" ? "#0d9" : type === "error" ? "#f44" : "#fa0";
    toast.style.cssText = `
        background: ${bg}; color: #fff; padding: 10px 18px;
        border-radius: 8px; font-size: 14px; font-weight: 600;
        box-shadow: 0 4px 16px rgba(0,0,0,.25);
        opacity: 0; transform: translateX(60px);
        transition: opacity .3s, transform .3s;
        pointer-events: auto;
    `;
    toast.textContent = message;
    container.appendChild(toast);

    // Animate in
    requestAnimationFrame(() => {
        toast.style.opacity = "1";
        toast.style.transform = "translateX(0)";
    });

    // Fade out after 4s
    setTimeout(() => {
        toast.style.opacity = "0";
        toast.style.transform = "translateX(60px)";
        setTimeout(() => toast.remove(), 400);
    }, 4000);
}

/* ─────────────────────────────────────────────────
   3.  Settings: query from storage via relay
   ───────────────────────────────────────────────── */

let _solverSettings = { autoSolve: true, autoRefresh: false, excludedSites: [], engines: {} };
const _settingsPendingCbs = {};
let _settingsReqCounter = 0;

// Listen for settings response from content script
window.addEventListener("message", function (event) {
    if (event.source !== window) return;
    if (event.data && event.data.type === "__SOLVER_SETTINGS_RESPONSE__") {
        const cb = _settingsPendingCbs[event.data.requestId];
        if (cb) {
            cb(event.data.settings);
            delete _settingsPendingCbs[event.data.requestId];
        }
    }
});

function fetchSettings() {
    return new Promise((resolve) => {
        const requestId = "__settings_" + (++_settingsReqCounter) + "_" + Date.now();
        _settingsPendingCbs[requestId] = resolve;
        window.postMessage({ type: "__GET_SOLVER_SETTINGS__", requestId }, "*");
        setTimeout(() => {
            if (_settingsPendingCbs[requestId]) {
                delete _settingsPendingCbs[requestId];
                resolve(null);
            }
        }, 3000);
    });
}

async function refreshSettings() {
    const s = await fetchSettings();
    if (s) {
        _solverSettings = {
            autoSolve: s.autoSolve !== false,
            autoRefresh: s.autoRefresh === true,
            excludedSites: s.excludedSites || [],
            engines: s.engines || {}
        };
    }
    // Reset auth-failed flag on settings refresh (user may have logged in)
    if (typeof window.__captchaAuthFailed !== 'undefined') {
        window.__captchaAuthFailed = false;
    }
}

// Load settings on init
refreshSettings();
// Refresh settings periodically (every 10s) in case user changes them
setInterval(refreshSettings, 10000);

// Load dynamic config on init
loadDynamicConfig();
// Refresh dynamic config periodically (every 30 min)
setInterval(loadDynamicConfig, 30 * 60 * 1000);

/* ─────────────────────────────────────────────────
   4.  Auto-detection: watch page for captchas and
       solve them automatically as they appear
   ───────────────────────────────────────────────── */

(function initAutoSolver() {
    // Dynamic selector getters (rebuild on each access to pick up config changes)
    function getCaptchaImgSel() { return buildSelector('captchaImg'); }
    function getCaptchaInputSel() { return buildSelector('captchaInput'); }
    function getRefreshSel() { return buildSelector('refresh'); }

    let _solving = false;
    let _authFailed = false;  // stops retry loop when not logged in
    window.__captchaAuthFailed = false; // mirror for external access
    let _lastSolvedSrc = "";
    let _lastSolvedImgUrl = "";  // track the actual image URL to prevent re-solve
    let _debounceTimer = null;

    function showZeroCreditsAlert() {
        let container = document.getElementById("__captcha-zero-credits-alert");
        if (!container) {
            container = document.createElement("div");
            container.id = "__captcha-zero-credits-alert";
            container.style.cssText = `
                position: fixed; top: 16px; right: 16px; z-index: 999999;
                background: #f44336; color: #fff; padding: 14px 20px;
                border-radius: 8px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 15px; font-weight: bold; box-shadow: 0 4px 16px rgba(0,0,0,0.3);
                border: 2px solid #b71c1c; display: flex; align-items: center; gap: 12px;
                animation: slideInRight 0.3s ease-out;
            `;
            container.innerHTML = `
                <span style="font-size: 24px;">🚫</span>
                <div>
                    <div>Zero Credits Remaining!</div>
                    <div style="font-size: 12px; font-weight: normal; margin-top: 4px;">Please purchase credits in the extension to continue solving.</div>
                </div>
            `;
            if (!document.getElementById('__captcha-keyframes')) {
                const style = document.createElement('style');
                style.id = '__captcha-keyframes';
                style.textContent = `
                    @keyframes slideInRight {
                        from { transform: translateX(100%); opacity: 0; }
                        to { transform: translateX(0); opacity: 1; }
                    }
                `;
                document.head.appendChild(style);
            }
            document.body.appendChild(container);

            // Auto-hide after 3 seconds
            setTimeout(() => {
                if (container && container.parentNode) {
                    container.parentNode.removeChild(container);
                }
            }, 3000);
        }
    }

    /** Debounced wrapper — collapses multiple triggers into one solve */
    function debouncedSolve(delay = 600) {
        clearTimeout(_debounceTimer);
        _debounceTimer = setTimeout(attemptAutoSolve, delay);
    }

    /** Check if the captcha input is currently empty */
    function isInputEmpty() {
        const inputEl = document.querySelector(getCaptchaInputSel());
        return !inputEl || !inputEl.value || inputEl.value.trim() === "";
    }

    /** Fill the captcha input with React/Angular-compatible value setting */
    function fillInput(text) {
        const inputEl = document.querySelector(getCaptchaInputSel());
        if (!inputEl) return false;

        let nativeInputValueSetter = Object.getOwnPropertyDescriptor(
            window.HTMLInputElement.prototype, "value"
        ).set;

        if (nativeInputValueSetter) {
            nativeInputValueSetter.call(inputEl, text);
        } else {
            inputEl.value = text;
        }

        const event = new Event("input", { bubbles: true });
        inputEl.dispatchEvent(event);
        inputEl.dispatchEvent(new Event("change", { bubbles: true }));

        // Also fire a blur event as Angular forms often validate on blur
        inputEl.dispatchEvent(new Event("blur", { bubbles: true }));

        return true;
    }

    /** Check if auto-solve should run on this site */
    function shouldAutoSolve() {
        if (!_currentSite) {
            return false; // unknown site — skip
        }
        if (!_solverSettings.autoSolve) {
            return false;
        }
        const host = window.location.hostname;
        if (_solverSettings.excludedSites.includes(host)) {
            return false;
        }
        // Check if the engine for this site is enabled
        const engines = _solverSettings.engines || {};
        if (engines[_currentSite] === false) {
            return false;
        }
        return true;
    }

    async function attemptAutoSolve() {
        if (_solving) return;
        if (window.__captchaAuthFailed) return;  // don't retry until settings refresh / login
        if (!shouldAutoSolve()) return;

        const specialHandler = getSpecialHandler();

        // Let the interceptors handle these natively, skip visual scraping
        if (specialHandler === 'eshram_intercept') return;

        // Skip if input is already filled (e.g., by hidden captcha script)
        if (!isInputEmpty()) return;

        const CAPTCHA_IMG_SEL = getCaptchaImgSel();
        const imgEl = document.querySelector(CAPTCHA_IMG_SEL);
        if (!imgEl) return;

        // Handle special captcha extraction modes
        let src = "";
        if (specialHandler === 'pmjay_attr' && imgEl.hasAttribute('captcha')) {
            src = "data:image/jpeg;base64," + imgEl.getAttribute('captcha');
        } else if (specialHandler === 'wbprms_text') {
            // Send the text content as the "captcha" instead of an image
            src = imgEl.textContent || imgEl.innerText;
        } else {
            src = imgEl.src || "";
        }

        // Skip if we already solved this exact image URL and input is filled
        if (src === _lastSolvedImgUrl && !isInputEmpty()) return;

        // Get base64: either already embedded or convert from URL via canvas
        let base64Src = src;
        if (specialHandler !== 'wbprms_text' && !src.startsWith("data:")) {
            try {
                const canvas = document.createElement("canvas");
                canvas.width = imgEl.naturalWidth || imgEl.width;
                canvas.height = imgEl.naturalHeight || imgEl.height;
                if (canvas.width === 0 || canvas.height === 0) return;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(imgEl, 0, 0);
                base64Src = canvas.toDataURL("image/png");
            } catch (e) {
                const fetchRes = await fetchImageBase64(src);
                if (fetchRes.success && fetchRes.base64) {
                    base64Src = fetchRes.base64;
                } else {
                    console.error("[CaptchaSolver] Background fetch failed:", fetchRes.error);
                    return;
                }
            }
        } else if (specialHandler !== 'wbprms_text' && !src.startsWith("data:image") && !src.startsWith("data:application/image")) {
            return;
        }

        // Skip only if we already solved THIS exact image AND the input has a value
        if (base64Src === _lastSolvedSrc && !isInputEmpty()) return;

        _solving = true;
        _lastSolvedSrc = base64Src;
        _lastSolvedImgUrl = src;

        console.log("[CaptchaSolver] 🔍 Captcha detected — solving…");
        const startTime = performance.now();

        try {
            const result = await solveCaptcha(base64Src);
            const elapsed = Math.round(performance.now() - startTime);

            if (result.success && result.text) {
                if (fillInput(result.text)) {
                    const conf = (result.confidence * 100).toFixed(1);
                    console.log(
                        `[CaptchaSolver] ✅ Auto-filled: "${result.text}" ` +
                        `(confidence ${conf}%) in ${elapsed}ms`
                    );
                    showToast(`✅ Solved: ${result.text}  in ${elapsed}ms`, "success");
                }
            } else {
                const errMsg = result.error || "empty text";
                console.warn("[CaptchaSolver] ❌ Solve failed:", errMsg);
                // Don't retry for auth/config errors — just stop silently
                if (errMsg.includes('No API key') || errMsg.includes('HTTP 4') || errMsg === 'Engine disabled' || errMsg === 'Solver timeout' || errMsg.includes('No credits remaining') || errMsg.includes('credits')) {
                    console.log("[CaptchaSolver] " + errMsg + " — stopped.");
                    _lastSolvedSrc = "";
                    _lastSolvedImgUrl = "";
                    if (errMsg.includes('No API key') || errMsg.includes('credits')) {
                        _authFailed = true;
                        window.__captchaAuthFailed = true;
                    }
                    if (errMsg.includes('credits')) {
                        showZeroCreditsAlert();
                    }
                    return;
                }
                showToast(`❌ Failed — refreshing…`, "error");
                _lastSolvedSrc = "";
                _lastSolvedImgUrl = "";
                refreshAndRetry();
            }
        } catch (err) {
            console.error("[CaptchaSolver] Auto-solve error:", err);
            _lastSolvedSrc = "";
            _lastSolvedImgUrl = "";
            refreshAndRetry();
        } finally {
            _solving = false;
        }

        // If input is STILL empty after solving, refresh & retry
        if (isInputEmpty()) {
            refreshAndRetry();
        }
    }

    /** Safely click an element */
    function safeClick(el) {
        if (!el) return;
        if (typeof el.click === 'function') {
            el.click();
        } else {
            el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
        }
    }

    /** Click the refresh button to get a new captcha, then re-solve */
    function refreshAndRetry() {
        if (!_solverSettings.autoRefresh) {
            console.log("[CaptchaSolver] Auto-refresh disabled — not retrying.");
            return;
        }
        const refreshEl = document.querySelector(getRefreshSel());
        if (refreshEl) {
            console.log("[CaptchaSolver] 🔄 Refreshing captcha for a new one…");
            _lastSolvedSrc = "";
            _lastSolvedImgUrl = "";
            safeClick(refreshEl);
            debouncedSolve(2000);
        } else {
            console.log("[CaptchaSolver] 🔄 No refresh button — retrying in 3s…");
            _lastSolvedSrc = "";
            _lastSolvedImgUrl = "";
            debouncedSolve(3000);
        }
    }

    function hookRefreshButton() {
        const refreshEls = document.querySelectorAll(getRefreshSel());
        refreshEls.forEach(refreshEl => {
            if (refreshEl && !refreshEl.__captchaSolverHooked) {
                refreshEl.__captchaSolverHooked = true;
                refreshEl.addEventListener("click", () => {
                    console.log("[CaptchaSolver] 🔄 Refresh clicked — waiting for new captcha…");
                    _lastSolvedSrc = "";
                    _lastSolvedImgUrl = "";

                    // Clear the input field when refresh is clicked
                    fillInput("");

                    // Fast-track sites
                    const fast = isFastSite();
                    debouncedSolve(fast ? 300 : 1500);
                });
                console.log("[CaptchaSolver] Hooked refresh button");
            }
        });
    }

    const observer = new MutationObserver((mutations) => {
        const fast = isFastSite();
        const delayLoad = fast ? 50 : 800;
        const delayUpdate = fast ? 100 : 600;

        const CAPTCHA_IMG_SEL = getCaptchaImgSel();

        for (const m of mutations) {
            if (m.type === "childList") {
                const hasCaptcha = document.querySelector(CAPTCHA_IMG_SEL);
                if (hasCaptcha) {
                    hookRefreshButton();
                    if (fast) {
                        attemptAutoSolve();
                    } else {
                        debouncedSolve(delayLoad);
                    }
                }
            }
            if (m.type === "attributes" && m.target.matches && m.target.matches(CAPTCHA_IMG_SEL)) {
                _lastSolvedSrc = "";
                _lastSolvedImgUrl = "";
                if (fast) {
                    attemptAutoSolve();
                } else {
                    debouncedSolve(delayUpdate);
                }
            }
        }
    });

    // Polling safety net: every 5s check if captcha exists but input is empty
    setInterval(() => {
        const hasCaptcha = document.querySelector(getCaptchaImgSel());
        if (hasCaptcha && isInputEmpty()) {
            _lastSolvedSrc = "";
            _lastSolvedImgUrl = "";
            debouncedSolve(500);
        }
    }, 5000);

    // Expose for manual trigger from popup
    window.__captchaSolverForceRun = function () {
        _lastSolvedSrc = "";
        _lastSolvedImgUrl = "";
        attemptAutoSolve();
    };

    function startObserving() {
        observer.observe(document.body || document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            characterData: true,
            attributeFilter: ["src", "captcha"],
        });
        console.log("[CaptchaSolver] 👁️ Auto-solver watching for captchas…");

        hookRefreshButton();
        debouncedSolve(500);
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", startObserving);
    } else {
        startObserving();
    }
})();
