// ─── Global state ────────────────────────────────────────────────────────────

let isAnimating = false;          // was: _0x2f6960 — controls favicon animation loop
let requestQueue = [];            // was: _0x5396d1 — pending prompt requests
let isProcessingQueue = false;    // was: _0x3ee3d7
let promptCounter = 0;            // was: _0x146ba2 — resets chat every 50 prompts
let webVersion = 1;               // was: _0x3aee1b — 1 = old UI, 2 = new UI
let documentTitle = 'Chat GPT Web';
let limitCheckInterval;           // was: _0x19026f

const FAVICON_RECEIVED = 'Favicons/Completed/chatGptReceived.png';
const FAVICON_CONNECTED = 'Favicons/OpenAi/chat_gpt_connected.png';

// Keep the tab title stable (ChatGPT overwrites it)
setInterval(() => { document.title = documentTitle; }, 300);


// ─── Favicon helpers ──────────────────────────────────────────────────────────

/**
 * Set favicon (legacy: appends a new <link> if none exists).
 * Sets isAnimating=false if not a gif/png.
 */
function setFaviconLegacy(url, mimeType = 'image/x-icon') {
    let rel = 'shortcut icon';
    if (mimeType === 'image/gif' || mimeType === 'image/png') {
        rel = 'icon';
    } else {
        isAnimating = false;
    }
    const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
    link.type = mimeType;
    link.rel = rel;
    link.href = url;
    document.getElementsByTagName('head')[0].appendChild(link);
}

/**
 * Set favicon (modern: updates ALL existing <link rel="icon"> elements).
 */
function setFavicon(url, mimeType = 'image/x-icon') {
    const rel = (mimeType === 'image/gif' || mimeType === 'image/png') ? 'icon' : 'shortcut icon';
    const links = document.querySelectorAll("link[rel*='icon']");
    if (links.length === 0) {
        const link = document.createElement('link');
        link.type = mimeType;
        link.rel = rel;
        link.href = url;
        document.getElementsByTagName('head')[0].appendChild(link);
        return;
    }
    for (const link of links) {
        link.type = mimeType;
        link.rel = rel;
        link.href = url;
    }
}

/** Animate favicon: spinning gear GIF (28 frames, 40ms each). */
function animateFaviconGear() {
    isAnimating = true;
    let frame = 0;
    let frameUrl = chrome.runtime.getURL(`Favicons/Gifs/Gear/frame_${frame}_delay-0.04s.gif`);
    const interval = setInterval(() => {
        if (isAnimating && frame < 28) {
            setFaviconLegacy(frameUrl, 'image/gif');
            frame++;
            frameUrl = chrome.runtime.getURL(`Favicons/Gifs/Gear/frame_${frame}_delay-0.04s.gif`);
        } else if (isAnimating && frame >= 28) {
            frame = 0;
            frameUrl = chrome.runtime.getURL(`Favicons/Gifs/Gear/frame_${frame}_delay-0.04s.gif`);
        } else {
            clearInterval(interval);
        }
    }, 40);
}

/** Animate favicon: task-bar style PNG sequence (27 frames). */
function animateFaviconTaskBar() {
    isAnimating = true;
    let frame = 0;
    let frameUrl = chrome.runtime.getURL(`Favicons/Task_Bar/${frame}.png`);
    const interval = setInterval(() => {
        if (isAnimating && frame < 27) {
            setFaviconLegacy(frameUrl, 'image/gif');
            frame++;
            frameUrl = chrome.runtime.getURL(`Favicons/Task_Bar/${frame}.png`);
        } else if (isAnimating && frame >= 27) {
            frame = 0;
            frameUrl = chrome.runtime.getURL(`Favicons/Task_Bar/${frame}.png`);
        } else {
            clearInterval(interval);
        }
    }, 40);
}

/**
 * Animate favicon: sniper-style GIF (27 frames, variable delays at specific
 * frames: [2, 8, 14, 20, 26, 27] get 50ms instead of 40ms).
 */
function animateFaviconSniper() {
    isAnimating = true;
    let frame = 0;
    const slowFrames = [2, 8, 14, 20, 26, 27];
    let delay = 40;
    let currentDelay = '0.04s';
    const interval = setInterval(() => {
        if (isAnimating) {
            currentDelay = slowFrames.includes(frame) ? '0.05s' : '0.04s';
            const filename = `frame_${String(frame).padStart(2, '0')}_delay-${currentDelay}.gif`;
            setFaviconLegacy(chrome.runtime.getURL(`Favicons/Sniper/${filename}`), 'image/gif');
            if (++frame >= 27) frame = 0;
            delay = currentDelay === '0.05s' ? 50 : 40;
        } else {
            clearInterval(interval);
        }
    }, delay);
}

/** Stop all favicon animations after a 2-second grace period. */
async function stopFaviconAnimation() {
    isAnimating = false;
    await new Promise(resolve => setTimeout(resolve, 2000));
}


// ─── Page-stability detection ─────────────────────────────────────────────────

/** Resolve after the page's 'load' event fires and the browser becomes idle. */
function waitForPageLoad() {
    return new Promise(resolve => {
        window.addEventListener('load', () => {
            requestIdleCallback(() => resolve());
        });
    });
}

/** Resolve on next idle callback. */
function waitForIdle() {
    return new Promise(resolve => {
        requestIdleCallback(() => resolve());
    });
}

/**
 * Wait until the DOM hasn't mutated for `timeout` ms (default 1000ms).
 * Resolves once the page is stable.
 */
function waitForDOMStability(timeout = 1000) {
    return new Promise((resolve, reject) => {
        let observer;
        let lastMutation = Date.now();
        let observing = false;

        function checkStability() {
            if (Date.now() - lastMutation > timeout) {
                if (observing) observer.disconnect();
                resolve();
            } else {
                setTimeout(checkStability, timeout);
            }
        }

        const onMutation = () => { lastMutation = Date.now(); };
        const observerConfig = { attributes: true, childList: true, subtree: true };

        if (document.readyState === 'complete') {
            observer = new MutationObserver(onMutation);
            observer.observe(document.body, observerConfig);
            observing = true;
            setTimeout(checkStability, timeout);
        } else {
            window.onload = () => {
                observer = new MutationObserver(onMutation);
                observer.observe(document.body, observerConfig);
                observing = true;
                setTimeout(checkStability, timeout);
            };
        }
    });
}

/** Wait for DOM stability then notify the background page. */
async function notifyPageStable() {
    console.log('waiting for page to load and become stable');
    await waitForDOMStability();
    console.log('page loaded and stable, notifying background');
    chrome.runtime.sendMessage({ type: 'page-loaded-and-stable' });
}


// ─── UI version detection ─────────────────────────────────────────────────────

console.log('chat.js loaded');

/**
 * Detect ChatGPT UI version:
 *   v1 – old UI with "New Chat" span text
 *   v2 – new UI without it
 */
async function detectWebVersion() {
    const spans = document.querySelectorAll('span');
    for (const span of spans) {
        if (span.innerText === 'New Chat') {
            logToBackground('Version 1 detected');
            return 1;
        }
    }
    logToBackground('Version 2 detected');
    return 2;
}


// ─── Initialization ───────────────────────────────────────────────────────────

/** Inject the "Connect" button and auto-click it to register with the background page. */
async function initialize() {
    await waitForDOMStability();
    await waitForElement('#prompt-textarea', 100000);

    let connectBtn = createConnectButton();
    webVersion = await detectWebVersion();
    console.log('chat.js: webVersion:', webVersion);

    let anchorElement = null;
    if (webVersion === 1) anchorElement = await waitForElementWithText('span', 'New Chat', 100000);
    if (webVersion === 2) anchorElement = await waitForElementWithText('div', 'ChatGPT', 100000);

    anchorElement.parentElement.appendChild(connectBtn);
    connectBtn = await waitForElementWithText('Button', 'Connect', 100000);
    connectBtn.click();
}
initialize();


// ─── Button factories ─────────────────────────────────────────────────────────

/** Create a hidden "Send Prompt" debug button. */
function createSendPromptButton() {
    const btn = document.createElement('button');
    btn.id = 'send_prompt';
    btn.innerHTML = 'Send Prompt';
    btn.onclick = async (e) => {
        e.preventDefault();
        e.stopPropagation();
        const prompt = document.getElementById('prompt-textarea').value;
        const response = await askChatGPT(prompt);
        console.log('response:', response);
    };
    return btn;
}

/** Create the "Connect" button that registers this tab with the background page. */
function createConnectButton() {
    const btn = document.createElement('button');
    btn.id = 'initialize_chat_gpt_web';
    btn.innerHTML = 'Connect';
    btn.classList.add('initialize-btn');
    btn.onclick = async (e) => {
        e.preventDefault();
        e.stopPropagation();
        btn.innerHTML = 'Connecting..';
        const response = await new Promise((resolve) => {
            chrome.runtime.sendMessage({ type: 'add_chat_gpt_web_connection' }, (result) => {
                console.log('result:', result);
                resolve(result.response);
            });
        });
        console.log('response:', response);
        btn.innerHTML = 'Connected';
        btn.classList.add('connected');
        console.log('chat.js: button.onclick');
    };
    return btn;
}


// ─── Message listener ─────────────────────────────────────────────────────────

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('chat.js onMessage received:', message);

    if (message.message === 'ask_chat_gpt_web') {
        console.log('ask_chat_gpt_web received', message);
        requestQueue.push({ request: message, sendResponse });
        if (!isProcessingQueue) processQueue();
        return true;
    }

    if (message.message === 'check_alive') {
        console.log('check_alive received');
        sendResponse({ alive: true });
        return true;
    }

    if (message.message === 'get_last_message') {
        console.log('get_last_message received');
        const msg = getLastMessage();
        console.log('chat.js: last_message:', msg);
        sendResponse({ message: msg });
        return true;
    }

    return true;
});


// ─── Queue processor ──────────────────────────────────────────────────────────

/** Process queued prompt requests one at a time. */
async function processQueue() {
    if (requestQueue.length === 0) {
        isProcessingQueue = false;
        return;
    }
    isProcessingQueue = true;

    const item = requestQueue.shift();
    if (item.request.options.startNewChat) {
        await startNewChat();
        setTitleStatus('New Chat Started from processQueue');
    }

    try {
        setTitleStatus('Processing Message');
        const response = await askChatGPT(item.request.prompt);
        setTitleStatus(`Message Processed: ${item.request.prompt}, ${response.length}`);
        item.sendResponse(response);

        // Reset chat every 50 prompts to avoid context overflow
        if (++promptCounter % 50 === 0) await startNewChat();
    } catch (err) {
        console.error('Error processing message:', err);
    }

    processQueue();
}


// ─── New chat ─────────────────────────────────────────────────────────────────

/** Click "New Chat" and wait for the textarea to re-appear. */
async function startNewChat() {
    let newChatElement = null;
    if (webVersion === 1) newChatElement = await waitForElementWithText('span', 'New Chat', 100000);
    if (webVersion === 2) newChatElement = await waitForElement('button.text-token-text-primary');
    newChatElement.click();

    const textarea = await waitForElement('#prompt-textarea', 100000);
    console.log('chat.js: startNewChat: textArea:', textarea);
    await new Promise(resolve => setTimeout(resolve, 1000));
    keepPageActive();
    return true;
}


// ─── Core: ask ChatGPT ────────────────────────────────────────────────────────

/**
 * Main automation function.
 * Types `prompt` into the textarea, clicks send, waits for the response
 * to finish streaming, then returns the response text.
 */
async function askChatGPT(prompt) {
    try { clearInterval(limitCheckInterval); } catch (_) { }

    const previousTitle = document.title;
    document.title = 'Asking Chat GPT Web';
    setTitleStatus('Asking Chat GPT Web');
    logToBackground('Asking Chat GPT Web with prompt: ' + prompt);

    // If there's already an error on screen, wait and retry
    if (hasErrorOnScreen()) {
        logToBackground('Error Detected');
        logToBackground('Waiting 10 seconds');
        await new Promise(resolve => setTimeout(resolve, 10000));
        await startNewChat();
        return askChatGPT(prompt);
    }

    // Poll every second for "limit reached" error
    limitCheckInterval = setInterval(() => {
        if (isLimitReached()) {
            console.log('Exiting startProcess due to limit reached.');
            clearInterval(limitCheckInterval);
            return null;
        }
    }, 1000);

    const textarea = document.getElementById('prompt-textarea');
    setFavicon(chrome.runtime.getURL(FAVICON_CONNECTED));

    let postRequestPromise = null;
    try {
        await typeIntoTextarea(textarea, prompt);
        postRequestPromise = waitForPostRequest();
        setTitleStatus('Entered Prompt');
        console.log('chat.js: Entered Prompt');
    } catch {
        clearInterval(limitCheckInterval);
        await startNewChat();
        return askChatGPT(prompt);
    }

    // Click send
    const stopGeneratingPromise = waitForStopGeneratingButton();
    const sendButton = textarea.parentElement.querySelector("button[data-testid='send-button']");
    await waitUntilEnabled(sendButton);
    sendButton.click();
    console.log('chat.js: ask_button.click() done');
    logToBackground('ask_button.click() done');
    document.title = 'Prompt Sent';
    setTitleStatus('Prompt Sent');

    // These intervals auto-click certain buttons if they appear (fallback handling)
    const clickResponse1Interval = clickButtonWhenVisible('button', 'Response 1');
    const clickRegenerateInterval = clickButtonWhenVisible('button.m-auto', 'Regenerate');
    const clickDefaultModelInterval = clickButtonWhenVisible('button', 'Use default model');

    logToBackground('Waiting for Regenerate Button');
    await stopGeneratingPromise;
    setTitleStatus('Stop Generating Button Found');

    // Version-specific: wait for the response to fully appear
    if (webVersion === 1) {
        const regenBtn = await waitForRegenerateButtonV1();
        regenBtn.focus();
        setTitleStatus('Regenerate Button Found');
    }
    if (webVersion === 2) {
        const disabledSend = await waitForDisabledSendButton();
        setTitleStatus('Disabled Send Button Found');
        const regenBtn = await waitForRegenerateButtonV2();
        setTitleStatus('Regenerate Button Found');
        disabledSend.focus();
        regenBtn.focus();
    }

    await postRequestPromise;
    logToBackground('Post Status Done');
    console.log('chat.js: postStatus done');
    setTitleStatus('Regenerate Button Found');

    clearInterval(clickResponse1Interval);
    clearInterval(clickRegenerateInterval);
    clearInterval(clickDefaultModelInterval);
    clearInterval(limitCheckInterval);
    logToBackground('Cleared all intervals');

    // Handle "Too Many Requests" error
    const tooManyRequestsEl = document.querySelector("div[role='presentation'] .text-red-500");
    if (tooManyRequestsEl) {
        logToBackground('Too Many Requests');
        logToBackground('Waiting 10 seconds');
        return askChatGPT(prompt);
    }

    setTitleStatus('Getting Last Message');
    logToBackground('Getting Last Message');

    const secondLastMessage = await getSecondLastUserMessage();
    console.log('secondLastMessage:', secondLastMessage);
    logToBackground('Second Last Message: ' + secondLastMessage);
    setTitleStatus('Last Message Received');

    // Verify the echoed user message matches what we sent
    if (!trimmedMatch(prompt, secondLastMessage)) {
        logToBackground('Prompt and Second Last Message are not same even after trimming');
        setTitleStatus('Prompt and Second Last Message are not same');
        return askChatGPT(prompt);
    }

    logToBackground('Prompt and Second Last Message are same after trimming');
    setTitleStatus('Second Last Message Length: ' + secondLastMessage.length);
    logToBackground('Waiting for Chat Box to be Idle');
    await waitForChatIdle();
    logToBackground('Chat Box is Idle');
    logToBackground('Waiting for Last Message');

    let lastMessage = null;
    try {
        lastMessage = getLastMessage();
        logToBackground('Last Message: ' + lastMessage);
        setTitleStatus('Last Message Length: ' + lastMessage.length);
    } catch (err) {
        logToBackground('Error getting last message: ' + err);
        await new Promise(resolve => setTimeout(resolve, 10000));
        lastMessage = getLastMessage();
    }

    console.log('chat.js: last_message:', lastMessage);
    document.title = 'Response Received';
    setTimeout(() => { document.title = previousTitle; }, 3000);
    setFavicon(chrome.runtime.getURL(FAVICON_RECEIVED));
    return lastMessage;
}


// ─── DOM helpers ──────────────────────────────────────────────────────────────

/** Log character-level differences between two strings (debug utility). */
function diffStrings(a, b) {
    const diffs = [];
    for (let i = 0; i < Math.max(a.length, b.length); i++) {
        if (a[i] !== b[i]) {
            diffs.push(i);
            console.log(`Difference at position ${i}: '${a[i]}' vs '${b[i]}'`);
        }
    }
    if (diffs.length === 0) console.log('No differences found.');
    else console.log('Total differences: ' + diffs.length);
}

/** Update the tab title (displayed as a status indicator). */
async function setTitleStatus(status) {
    documentTitle = status;
}

/** Remove all whitespace from a string (used to compare prompts). */
function stripWhitespace(str) {
    return str.replace(/\s+/g, '');
}

/** Return true if two strings are equal after stripping all whitespace. */
function trimmedMatch(a, b) {
    return stripWhitespace(a) === stripWhitespace(b);
}

/** Simulate typing by firing input/change events on a contenteditable/textarea. */
function simulateTyping(element, text) {
    element.focus();
    const inputEvent = new InputEvent('input', {
        bubbles: true,
        inputType: 'insertText',
        data: text,
    });
    element.dispatchEvent(inputEvent);
    const changeEvent = new Event('change', { bubbles: true });
    element.dispatchEvent(changeEvent);
}

/** Wait until a button is no longer disabled, then resolve. */
async function waitUntilEnabled(button) {
    return new Promise(resolve => {
        if (!button.disabled) { resolve(); return; }
        const observer = new MutationObserver(mutations => {
            mutations.forEach(m => {
                if (m.type === 'attributes' && !button.disabled) {
                    observer.disconnect();
                    resolve();
                }
            });
        });
        observer.observe(button, { attributes: true });
    });
}

/** Set the textarea value and fire synthetic events so React picks up the change. */
async function typeIntoTextarea(textarea, text) {
    textarea.value = text;
    simulateTyping(textarea, text);
}

/** Extract the last assistant message's text from the DOM. */
function getLastMessage() {
    const lastMarkdown = getLastMarkdownElement();
    let text = '';
    lastMarkdown.childNodes.forEach(node => {
        if (node.nodeType === Node.TEXT_NODE) text += node.nodeValue;
        else if (node.nodeType === Node.ELEMENT_NODE) text += node.innerText + '\n';
    });
    return text.trim();
}

/** Return the last `.markdown` element inside the conversation. */
function getLastMarkdownElement() {
    const groups = document.querySelector("div[role='presentation'] .flex.flex-col.text-sm")
        .querySelectorAll('.group');
    return groups[groups.length - 1].querySelector('.markdown');
}

/**
 * Get the second-to-last turn's user message text.
 * Used to verify the prompt was accepted correctly.
 */
async function getSecondLastUserMessage() {
    const container = document.querySelector("div[role='presentation'] .flex.flex-col.text-sm");
    await waitForElement('.group .markdown', 100000);
    const groups = container.querySelectorAll('.group');
    const secondLast = groups[groups.length - 2]
        .querySelector("div[data-message-author-role='user']")
        .querySelector('div');
    let text = '';
    secondLast.childNodes.forEach(node => {
        if (node.nodeType === Node.TEXT_NODE) text += node.nodeValue;
        else if (node.nodeType === Node.ELEMENT_NODE) text += node.innerText + '\n';
    });
    return text.trim();
}


// ─── Wait helpers ─────────────────────────────────────────────────────────────

/** v1: wait for the "Regenerate" button by text content. */
async function waitForRegenerateButtonV1() {
    const btn = await waitForElementWithText('button', 'Regenerate', 10000);
    console.log('chat.js: waitForRegenerateButton:', btn);
    await new Promise(resolve => setTimeout(resolve, 1000));
    return btn;
}

/** v2: wait for the send button to become disabled (response is streaming). */
async function waitForDisabledSendButton() {
    const btn = await waitForElement('button[data-testid="send-button"]', 60000);
    console.log('chat.js: waitForDisabledSendButton:', btn);
    return btn;
}

/** v2: poll for the per-turn hover button that appears when generation ends. */
function waitForRegenerateButtonV2() {
    return new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
            clearInterval(poll);
            reject('Timed out waiting for the regenerate button');
        }, 30000);
        const poll = setInterval(() => {
            const turns = document.querySelectorAll("div[data-testid*='conversation-turn-']");
            const lastTurn = turns[turns.length - 1];
            const btn = lastTurn ? lastTurn.querySelector('button.md\\:group-hover\\:visible') : null;
            if (btn) {
                console.log('Found regenerateButton', btn);
                clearInterval(poll);
                clearTimeout(timeout);
                resolve(btn);
            } else {
                console.log('Regenerate button not found, checking again...');
            }
        }, 1000);
    });
}

/** Wait for the "Stop generating" button to appear (signals streaming started). */
async function waitForStopGeneratingButton() {
    let btn;
    try {
        if (webVersion === 1) btn = await waitForElementWithText('button', 'Stop generating', 10000);
        if (webVersion === 2) btn = await waitForElement('button[aria-label="Stop generating"]', 60000);
    } catch (err) {
        console.log('chat.js: waitForStopGeneratingButton: error:', err);
    }
    console.log('chat.js: waitForStopGeneratingButton:', btn);
    return btn;
}

/**
 * Wait for a watched element's text content to stop changing for `timeout` ms.
 * Useful for detecting when streaming has finished.
 */
function waitForTextStable(element, timeout = 5000) {
    return new Promise(resolve => {
        let prevText;
        const observer = new MutationObserver(() => {
            const current = element.textContent;
            if (current === prevText) { resolve(); observer.disconnect(); }
            prevText = current;
        });
        observer.observe(element, { childList: true, subtree: true });
        setTimeout(() => { resolve(); observer.disconnect(); }, timeout);
    });
}

/**
 * Poll every 100ms for an element matching `selector` whose .innerText === `text`.
 * Rejects after `timeout` ms.
 */
async function waitForElementWithText(selector, text, timeout = 60000) {
    return new Promise((resolve, reject) => {
        const poll = setInterval(() => {



