// ============================================================
// ebay_description.js — SilentSniper eBay Description Content Script
// Deobfuscated by Claude
// ============================================================

console.log('ebay_description.js');

// ─── HTTP Helpers ─────────────────────────────────────────────────────────────

/**
 * Send a fetchData message to the background service worker.
 * Resolves with response.data (does NOT reject on error).
 */
async function fetchData(url, data) {
    return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: 'fetchData', url, data }, function (response) {
            console.log('data sent to fetch', data);
            console.log('fetchData response', response);
            resolve(response.data);
        });
    });
}

/**
 * Same as fetchData but REJECTS the promise if response.error is set.
 */
async function fetchDataOrThrow(url, data) {
    return new Promise((resolve, reject) => {
        chrome.runtime.sendMessage({ type: 'fetchData', url, data }, function (response) {
            console.log('data sent to fetch', data);
            console.log('fetchData response', response);
            if (response.error) reject(response.error);
            resolve(response.data);
        });
    });
}

// ─── Core Description Injection ──────────────────────────────────────────────

/**
 * Build a description from the basic HTML template and inject it into eBay's
 * description editor (no AI involved).
 *
 * @param {Object} productData  - Product object with custom_title, descriptionText,
 *                                bullet_points, bullet_points_html, mpn, brand, sku
 */
async function addBasicDescriptionToEbay(productData) {
    return new Promise(async (resolve, reject) => {
        documentTitle = 'Ebay: Adding Description...';

        // Load the main HTML template
        const templateUrl = chrome.runtime.getURL('Ebay_Description_Templates/Template_with_head.html');
        let html = await fetch(templateUrl).then(r => r.text());

        // Fill placeholders then sanitise
        html = fillTemplatePlaceholders(html, productData);      // [TITLE], [DESCRIPTION], [BULLETS]
        html = removeMPNFromDescription(html, productData.mpn);
        html = removeBrandFromDescription(html, productData.brand);

        // Append shipping/handling block and call-to-action
        const shippingUrl = chrome.runtime.getURL('Ebay_Description_Templates/Shipping_Handling.html');
        const shippingHtml = await fetch(shippingUrl).then(r => r.text());

        html = html + await fetchCallToActionHtml() + shippingHtml;

        // Push into eBay's rich-text editor
        await setEbayDescriptionHtml(html);
        documentTitle = 'Ebay: Description Added';

        // Trigger eBay's network save
        await triggerDescriptionNetworkRequest();
        await waitForDescriptionNetworkResponse();
        resolve();
    });
}

/**
 * Build and RETURN the basic template HTML string (without injecting it).
 * Used when the caller needs the HTML for further processing.
 *
 * @param {Object} productData
 * @returns {string} Processed HTML string
 */
async function getBasicDescriptionHtml(productData) {
    console.log('getBasicDescription');

    const templateUrl = chrome.runtime.getURL('Ebay_Description_Templates/Template_with_head.html');
    let html = await fetch(templateUrl).then(r => r.text());

    console.log('getBasicDescription template');
    html = fillTemplatePlaceholders(html, productData);

    console.log('removeFlaggedKeywords template');
    html = removeMPNFromDescription(html, productData.mpn);

    console.log('removeMPNFromDescription template');
    html = removeBrandFromDescription(html, productData.brand);

    console.log('removeBrandFromDescription template');

    // Encode SKU as base64 and embed it
    try {
        const encodedSku = btoa(productData.sku);
        html = html.replace('{{SKU}}', encodedSku);
    } catch (err) {
        console.log('error', err);
    }

    return html;
}

// ─── OpenAI / ChatGPT Description Generation ──────────────────────────────────

/**
 * Generate an AI description using the legacy OpenAI completions endpoint
 * (via background script), then inject it into eBay.
 *
 * @param {Object} productData
 */
async function generateAndAddOpenAiDescription(productData) {
    // Load prompt template from extension assets
    const promptTemplate = await fetchPrompt('text_prompts/description_prompt.txt');
    const bulletText = productData.bullet_points.join('\n') + '\n' + productData.descriptionText;
    const title = productData.custom_title;

    let prompt = promptTemplate
        .replace('{{productTitle}}', title)
        .replace('{{productDescription}}', bulletText);

    documentTitle = 'OpenAI: Generating Description...';
    const response = await receiveOpenAiResponseFromBackgroundScript(prompt);

    if (response.error == null) {
        let descriptionText = response.choices[0].text.trim().replace(/\n/g, ' ');
        documentTitle = 'OpenAI: Description Generated';

        // Sanitise
        descriptionText = removeMPNFromDescription(descriptionText, productData.mpn);
        descriptionText = removeBrandFromDescription(descriptionText, productData.brand);

        // Append shipping/handling and call-to-action
        const shippingUrl = chrome.runtime.getURL('Ebay_Description_Templates/Shipping_Handling.html');
        const shippingHtml = await fetch(shippingUrl).then(r => r.text());
        descriptionText = descriptionText + await fetchCallToActionHtml() + shippingHtml;

        // Wrap in styled div then inject
        descriptionText = wrapInFontDiv(descriptionText);
        documentTitle = 'OpenAI: Adding Description to Ebay';

        await setEbayDescriptionHtml(descriptionText);
        documentTitle = 'OpenAI: Description Added to Ebay';

        await triggerDescriptionNetworkRequest();
        await waitForDescriptionNetworkResponse();

    } else {
        console.log('response.error', response.error);
        const errorType = response.error.type;

        // Retry on server errors
        if (errorType.includes('server_error')) {
            console.log("type.includes('server_error')", errorType.includes('server_error'));
            await generateAndAddOpenAiDescription(productData);
            return;
        }
    }
}

/**
 * Inject a pre-built description HTML string directly into eBay (no generation step).
 *
 * @param {string} descriptionHtml
 */
async function addPrebuiltDescriptionToEbay(descriptionHtml) {
    documentTitle = 'OpenAI: Adding Description to Ebay';
    await setEbayDescriptionHtml(descriptionHtml);
    documentTitle = 'OpenAI: Description Added to Ebay';
    await triggerDescriptionNetworkRequest();
    await waitForDescriptionNetworkResponse();
}

/**
 * Generate a description using the SilentSniper title-builder API endpoint,
 * then RETURN the final HTML string (does not inject).
 *
 * @param {Object} productData
 * @returns {string} Wrapped HTML string
 */
async function generateDescriptionFromTitleBuilderApi(productData) {
    const bulletText = productData.bullet_points.join('\n') + '\n' + productData.descriptionText;
    const title = productData.custom_title;
    const apiUrl = 'https://ebaysnipertitlebuilder.ngrok.io/api/TitleBuilder/GenerateDescription';

    let apiResponse = await fetchData(apiUrl, {
        request_type: 'generate_description',
        product_description: bulletText,
        product_title: title
    });

    let description = apiResponse.optimizedDescription;

    // Retry once if response is empty/null
    if (description == null || description === '' || description === ' ') {
        apiResponse = await fetchData(apiUrl, {
            request_type: 'generate_description',
            product_description: bulletText,
            product_title: title
        });
        description = apiResponse.optimizedDescription;
    }

    documentTitle = 'OpenAI: Description Generated';

    // Sanitise
    description = removeMPNFromDescription(description, productData.mpn);
    description = removeBrandFromDescription(description, productData.brand);

    // Append shipping/handling and call-to-action
    const shippingUrl = chrome.runtime.getURL('Ebay_Description_Templates/Shipping_Handling.html');
    const shippingHtml = await fetch(shippingUrl).then(r => r.text());
    description = description + await fetchCallToActionHtml() + shippingHtml;

    return wrapInFontDiv(description);
}

// ─── Template Helpers ─────────────────────────────────────────────────────────

/**
 * Substitute [TITLE], [DESCRIPTION], and [BULLETS] placeholders in the HTML
 * template with actual product data.
 *
 * @param {string} html
 * @param {Object} productData
 * @returns {string}
 */
function fillTemplatePlaceholders(html, productData) {
    // Replace title
    html = html.replace(/\[TITLE\]/, productData.custom_title);

    // Replace description (newlines → <br>)
    let description = productData.descriptionText.replace(/\n/g, '<br>');
    html = html.replace(/\[DESCRIPTION\]/, description);

    // Replace bullets (use HTML version if available, otherwise strip placeholder)
    html = productData.bullet_points_html
        ? html.replace(/\[BULLETS\]/, productData.bullet_points_html)
        : html.replace(/\[BULLETS\]/, '');

    // Clean up double <br> artifacts from empty sections
    html = html.replace(/<br>\n  <br>\n\n  \n\n  <br>\n  <br>\n\n/g, '<br>\n  <br>\n\n');
    html = html.replace(/\n\n  <br>\n  <br>\n\n  \n\n  <br>\n  <br>\n\n/g, '\n<br><br>\n');

    return html;
}

/**
 * Wrap an HTML string in a styled font div (Arial 16px black).
 *
 * @param {string} html
 * @param {string} fontFamily
 * @param {string} fontSize
 * @param {string} color
 * @returns {string}
 */
function wrapInFontDiv(html, fontFamily = 'Arial', fontSize = '16px', color = 'black') {
    return `<div style="font-family: ${fontFamily}; font-size: ${fontSize}; color: ${color};">${html}</div>`;
}

// ─── eBay Editor Interaction ───────────────────────────────────────────────────

/**
 * Set innerHTML of eBay's rich-text description editor iframe and trigger
 * a change event so eBay's React/Angular recognises the update.
 *
 * @param {string} html
 */
function setEbayDescriptionHtml(html) {
    return new Promise(async (resolve, reject) => {
        const editorFrame = document.getElementById('se-rte-frame__summary');
        scrollIntoView(editorFrame);

        const frameDoc = editorFrame.contentDocument || editorFrame.contentWindow.document;
        const contentEditable = frameDoc.body.querySelector('.se-rte-editor__rich');

        contentEditable
            ? console.log('contentEditable exists')
            : console.log('contentEditable does not exist');

        // Write HTML into the editor
        document.createElement('div').innerHTML = html; // sanitise check
        contentEditable.innerHTML = html;

        await waitSomeTime(1000);
        contentEditable.focus();

        // Dispatch change event so eBay picks up the new value
        const changeEvent = new Event('change', { bubbles: true });
        contentEditable.dispatchEvent(changeEvent);

        // Visual confirmation border
        editorFrame.style.border = '3px solid #DFFF00';
        resolve();
    });
}

/**
 * Fetch the call-to-action HTML partial from extension assets.
 *
 * @returns {Promise<string>}
 */
async function fetchCallToActionHtml() {
    const url = chrome.runtime.getURL('Call_To_Action_HTML/call_to_action.Html');
    return await fetch(url).then(r => r.text());
}

/**
 * Debug helper: monitor events on eBay's rich-text editor and test change dispatch.
 */
function debugEditorEvents() {
    const editorFrame = document.getElementById('se-rte-frame__summary');
    const frameDoc = editorFrame.contentDocument || editorFrame.contentWindow.document;
    const contentEditable = frameDoc.body.querySelector('.se-rte-editor__rich');

    monitorEvents(contentEditable);
    contentEditable.focus();

    // Various event dispatch attempts (for debugging eBay's change detection)
    let event = new CustomEvent('change');
    contentEditable.dispatchEvent(event);

    event = document.createEvent('HTMLEvents');
    event.initEvent('change', true, false);
    contentEditable.dispatchEvent(event);

    contentEditable.innerHTML = 'tee123';
    contentEditable.focus();

    event = new Event('change', { bubbles: true });
    document.dispatchEvent(event);

    event = new Event('change', { bubbles: true });
    contentEditable.dispatchEvent(event);

    contentEditable.focus();
    contentEditable.blur();
    frameDoc.body.focus();
    document.body.click();

    event = new CustomEvent('click');
    document.body.dispatchEvent(event);

    event = document.createEvent('HTMLEvents');
    event.initEvent('change', true, false);
    document.body.dispatchEvent(event);
}

/**
 * Debug helper: count postMessage events received by the window.
 */
function countPostMessages() {
    let postMessageCount = 0;
    window.addEventListener('message', function (event) {
        console.log('event: ', event);
        postMessageCount++;
        console.log('postMessageCount: ', postMessageCount);
    }, false);
}

/**
 * Focus eBay's title field to trigger a network save of the description.
 */
function triggerDescriptionNetworkRequest() {
    return new Promise((resolve, reject) => {
        documentTitle = 'Trigger Description Network Request';
        document.querySelector(".smry.summary__title input[name='title']").focus();
        resolve();
    });
}

// ─── Text Sanitisation ────────────────────────────────────────────────────────

/**
 * Remove all strings in the mpnList array (case-insensitive) from html.
 *
 * @param {string} html
 * @param {string[]} mpnList  - Array of MPN strings to strip
 * @returns {string}
 */
function removeMPNFromDescription(html, mpnList) {
    if (!mpnList) return html;
    if (!Array.isArray(mpnList)) return html;
    if (mpnList.length > 0) {
        for (let i = 0; i < mpnList.length; i++) {
            const term = mpnList[i].toLowerCase();
            html = insensitiveReplaceAll(html, term, '');
        }
    }
    return html;
}

/**
 * Remove the brand name (and its hyphenated variant) from html.
 *
 * @param {string} html
 * @param {string} brand
 * @returns {string}
 */
function removeBrandFromDescription(html, brand) {
    if (!brand) return html;
    html = insensitiveReplaceAll(html, brand, '');
    const hyphenatedBrand = brand.replace(/\s+/g, '-');
    return insensitiveReplaceAll(html, hyphenatedBrand, '');
}

/**
 * Strip flagged/restricted keywords from description HTML.
 * Removes Amazon/eBay references, warranty/refund language, contact info,
 * URLs, email addresses, phone numbers, and other eBay policy violations.
 *
 * @param {string} html
 * @returns {string}
 */
function removeFlaggedKeywords(html) {
    // Protocol normalisation and security cleanup
    html = html.replace(/http\:/gi, 'https:');
    html = html.replace(/<a(.*)href=("|')([^'"]+)("|')(.*)>/gi, "<a$1href=''$5>");
    html = html.replace(/<img(.*)src=("|')([^'"]+)("|')(.*)>/gi, "<img$1src=''$5>");
    html = html.replace(/<script.*?>[\s\S]*?<\/script>/gi, '');

    // Flagged phrases / competitor names
    html = insensitiveReplaceAll(html, 'full refund', 'resolution');
    html = insensitiveReplaceAll(html, 'amazon.ca', '');
    html = insensitiveReplaceAll(html, '.ca', '');
    html = insensitiveReplaceAll(html, 'Read more', '');
    html = insensitiveReplaceAll(html, 'amazon', '');
    html = insensitiveReplaceAll(html, 'ebay', '');
    html = insensitiveReplaceAll(html, 'warranty', 'satisfaction');
    html = insensitiveReplaceAll(html, 'warran-ty', 'satisfaction');
    html = insensitiveReplaceAll(html, 'refund', 'satisfaction');
    html = insensitiveReplaceAll(html, 'Email Address', '');
    html = insensitiveReplaceAll(html, 'Email', '');
    html = insensitiveReplaceAll(html, 'Address', '');
    html = insensitiveReplaceAll(html, 'phone', '');
    html = insensitiveReplaceAll(html, 'GUARANTEE', 'SATISFACTION GUARANTEE');
    html = insensitiveReplaceAll(html, 'MONEY BACK', '');
    html = insensitiveReplaceAll(html, 'worry-free', '');
    html = insensitiveReplaceAll(html, 'contact us', 'contact me');
    html = insensitiveReplaceAll(html, 'year', '');
    html = insensitiveReplaceAll(html, 'limited', '');
    html = insensitiveReplaceAll(html, 'lifetime', '');
    html = insensitiveReplaceAll(html, '24-MONTH', '');
    html = insensitiveReplaceAll(html, 'MONTH', '');
    html = insensitiveReplaceAll(html, '30-day', '');
    html = insensitiveReplaceAll(html, 'money-back', 'satisfaction');
    html = insensitiveReplaceAll(html, '30-day', '');
    html = insensitiveReplaceAll(html, '3- free', '');
    html = insensitiveReplaceAll(html, 'replacement', '');
    html = insensitiveReplaceAll(html, '120-day', '');
    html = insensitiveReplaceAll(html, 'LINK', '');
    html = insensitiveReplaceAll(html, 'BROWSER', '');
    html = insensitiveReplaceAll(html, 'E-Book', '');
    html = insensitiveReplaceAll(html, 'E-Mail', '');

    // Strip URLs
    html = html.replace(/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, '');

    // Strip email addresses (three passes with different flags for robustness)
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/gim, '');
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/g, '');
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/gi, '');

    // Strip phone numbers
    html = html.replace(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/g, '');

    // Strip generic URLs (string-based pattern — kept as-is from original)
    html = html.replace('\\b((?:[a-z][\\w-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'\".,<>?«»\u201c\u201d\u2018\u2019]))', '');

    // Replace 'brand' with 'product'
    html = insensitiveReplaceAll(html, 'brand', 'product');

    return html;
}

// ─── OpenAI Description Generation (Alternative Endpoints) ────────────────────

/**
 * Generate description HTML via the legacy Cloud Run OpenAI wrapper (polling loop).
 * Retries up to 99 times with a 5-second delay between attempts.
 *
 * @param {Object} productData
 * @returns {string} Description HTML
 */
async function getDescriptionHtmlFromOpenAi(productData) {
    let descriptionHtml = null;
    let bulletText;

    try {
        bulletText = productData.bullet_points.join('\n');
    } catch (err) { }

    const fullDescription = bulletText + '\n' + productData.descriptionText;
    const title = productData.custom_title;
    let counter = 0;

    while (counter < 99 && (descriptionHtml == null || descriptionHtml === '' || descriptionHtml === 'undefined')) {
        const promptJson = await fetch(chrome.runtime.getURL('prompts_json/description_gen.json'))
            .then(r => r.json());

        promptJson.user_input = title + '\n' + fullDescription;

        try {
            const rawResponse = await fetchData('https://openai-function-call-djybcnnsgq-uc.a.run.app', promptJson);
            console.log('getDescriptionHtmlFromOpenAi data', rawResponse);

            const parsed = JSON.parse(rawResponse);
            descriptionHtml = parsed.optimized_description;
            console.log('getDescriptionHtmlFromOpenAi descriptionHtml', descriptionHtml);

            if (descriptionHtml == null || descriptionHtml === '' || descriptionHtml === 'undefined') {
                await new Promise(r => setTimeout(r, 5000));
            }
        } catch (err) {
            console.log('error', err);
        }

        counter++;
        console.log('getDescriptionHtmlFromOpenAi counter', counter);
        await new Promise(r => setTimeout(r, 5000));
    }

    if (descriptionHtml == null || descriptionHtml === '' || descriptionHtml === 'undefined') {
        descriptionHtml = '';
    }

    console.log('getDescriptionHtmlFromOpenAi descriptionHtml', descriptionHtml);
    documentTitle = 'OpenAI: Description Generated';

    try { descriptionHtml = removeMPNFromDescription(descriptionHtml, productData.mpn); } catch (e) { }
    try { descriptionHtml = removeBrandFromDescription(descriptionHtml, productData.brand); } catch (e) { }

    return descriptionHtml;
}

/**
 * Generate description HTML via ChatGPT (askChatGptForDescription helper).
 *
 * @param {Object} productData
 * @returns {string} Description HTML
 */
async function getDescriptionHtmlFromChatGpt(productData) {
    let descriptionHtml = null;
    let bulletText;

    try {
        bulletText = productData.bullet_points.join('\n');
    } catch (err) { }

    const fullDescription = bulletText + '\n' + productData.descriptionText;
    const title = productData.custom_title;

    descriptionHtml = await askChatGptForDescription(title, fullDescription);
    console.log('getDescriptionHtmlFromChatGpt descriptionHtml', descriptionHtml);
    console.log('getDescriptionHtmlFromOpenAi descriptionHtml', descriptionHtml);

    documentTitle = 'OpenAI: Description Generated';

    try { descriptionHtml = removeMPNFromDescription(descriptionHtml, productData.mpn); } catch (e) { }
    try { descriptionHtml = removeBrandFromDescription(descriptionHtml, productData.brand); } catch (e) { }

    return descriptionHtml;
}

// ─── 2023 Description Templates ───────────────────────────────────────────────

/**
 * Build a description from the 2023 multi-section template (v2).
 * Fetches Contact, Shipping, Payment, Return, and Feedback HTML partials,
 * injects them into Main_Template_2.html, and embeds product images.
 *
 * @param {string} descriptionHtml  - Core product description HTML
 * @param {Object} productData      - Product object (custom_title, sku)
 * @returns {string} Final complete HTML
 */
async function createDescriptionFromTemplate2023_v2(descriptionHtml, productData) {
    console.log('createDescriptionFromTemplate2023');

    const templateBase = 'Ebay_Description_Templates/Description_Template_2023/';

    // Load all template partials in parallel
    const [mainTemplate, contactInfo, paymentInfo, returnInfo, feedbackInfo] = await Promise.all([
        fetch(chrome.runtime.getURL(templateBase + '/Main_Template/Main_Template_2.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Contact_Info/Contact_Info.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Payment_Info/Payment_Info.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Return_Info/Return_Info.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Feedback_Info/Feedback_Info.html')).then(r => r.text()),
    ]);

    const shippingInfo = await getShippingInfoHtml();

    // Inject all sections into the main template
    let html = mainTemplate
        .replace('{{Product_Description}}', descriptionHtml)
        .replace('{{Contact_Info}}', contactInfo)
        .replace('{{Shipping_Info}}', shippingInfo)
        .replace('{{Payment_Info}}', paymentInfo)
        .replace('{{Return_Info}}', returnInfo)
        .replace('{{Feedback_Info}}', feedbackInfo);

    // Inject image URLs
    const imageUrls = await getEbayListingImageUrls();

    html = html
        .replace('{{Feedback_Image_URL}}', 'https://assets.cashforyourmac.com/image/upload/v1/Static/logo/ebay-rating.svg')
        .replace('{{Shipping_Image_URL}}', 'https://res.cloudinary.com/hmdenahha/image/upload/v1486463259/shipping_zpsehr4l7lo_vs2j70.jpg')
        .replace('{{Return_Image_URL}}', 'https://res.cloudinary.com/hmdenahha/image/upload/v1486462923/returns_zpsxr9puwik_mwvvon.jpg')
        .replace('{{Payment_Image_URL}}', 'https://www.ecommercebytes.com/wp-content/uploads/2020/05/eBay_Managed_Payments.jpg')
        .replace('{{Product_Image_URL}}', imageUrls[0])
        .replace('{{Product_Title}}', productData.custom_title)
        .replace('{{Store_Thank_You_Message}}', 'Thank you for supporting our small family business!');

    // Inject image carousel
    try {
        const carouselHtml = buildImageCarousel(imageUrls).outerHTML;
        html = html.replace('{{Product_Carousel}}', carouselHtml);
    } catch (err) {
        html = html.replace('{{Product_Carousel}}', '');
    }

    // Encode SKU as base64
    const encodedSku = btoa(productData.sku);
    return html.replace('{{SKU}}', encodedSku);
}

/**
 * Build a description from the 2023 multi-section template (v3) with
 * domain-specific localisation (en/de/fr/es/it).
 *
 * @param {string} descriptionHtml  - Core product description HTML
 * @param {Object} productData      - Product object (custom_title, sku)
 * @returns {string} Final complete HTML
 */
async function createDescriptionFromTemplate2023_v3(descriptionHtml, productData) {
    const { domain } = await chrome.storage.local.get('domain');
    const templateBase = 'Ebay_Description_Templates/Description_Template_2023/';

    // Load domain-specific partials (fallback is handled server-side by filename convention)
    const [mainTemplate, contactInfo, paymentInfo, returnInfo, feedbackInfo] = await Promise.all([
        fetch(chrome.runtime.getURL(templateBase + '/Main_Template/Main_Template_3.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Contact_Info/Contact_Info-' + domain + '.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Payment_Info/Payment_Info-' + domain + '.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Return_Info/Return_Info-' + domain + '.html')).then(r => r.text()),
        fetch(chrome.runtime.getURL(templateBase + '/Feedback_Info/Feedback_Info-' + domain + '.html')).then(r => r.text()),
    ]);

    const shippingInfo = await getShippingInfoHtml();

    let html = mainTemplate
        .replace('{{Product_Description}}', descriptionHtml)
        .replace('{{Contact_Info}}', contactInfo)
        .replace('{{Shipping_Info}}', shippingInfo)
        .replace('{{Payment_Info}}', paymentInfo)
        .replace('{{Return_Info}}', returnInfo)
        .replace('{{Feedback_Info}}', feedbackInfo);

    const imageUrls = await getEbayListingImageUrls();

    html = html
        .replace('{{Feedback_Image_URL}}', 'https://assets.cashforyourmac.com/image/upload/v1/Static/logo/ebay-rating.svg')
        .replace('{{Shipping_Image_URL}}', 'https://res.cloudinary.com/hmdenahha/image/upload/v1486463259/shipping_zpsehr4l7lo_vs2j70.jpg')
        .replace('{{Return_Image_URL}}', 'https://res.cloudinary.com/hmdenahha/image/upload/v1486462923/returns_zpsxr9puwik_mwvvon.jpg')
        .replace('{{Payment_Image_URL}}', 'https://www.ecommercebytes.com/wp-content/uploads/2020/05/eBay_Managed_Payments.jpg')
        .replace('{{Product_Image_URL}}', imageUrls[0])
        .replace('{{Product_Title}}', productData.custom_title);

    // ── Localised strings ──────────────────────────────────────────────────────
    let thankYouMessage = 'Buy it now and enjoy a pleasant shopping experience!';
    if (domain === 'de') thankYouMessage = 'Kaufen Sie jetzt und genießen Sie ein angenehmes Einkaufserlebnis!';
    else if (domain === 'fr') thankYouMessage = "Achetez maintenant et profitez d'une expérience de magasinage agréable!";
    else if (domain === 'es') thankYouMessage = '¡Cómpralo ahora y disfruta de una agradable experiencia de compra!';
    else if (domain === 'it') thankYouMessage = "Acquista ora e goditi un'esperienza di shopping piacevole!";

    const { disableThankYouMessage } = await chrome.storage.local.get('disableThankYouMessage');

    html = disableThankYouMessage
        ? html.replace('{{Store_Thank_You_Message}}', '')
        : html.replace('{{Store_Thank_You_Message}}', thankYouMessage);

    let shippingHeader = 'Shipping';
    if (domain === 'de') shippingHeader = 'Versand';
    else if (domain === 'fr') shippingHeader = 'Expédition';
    else if (domain === 'es') shippingHeader = 'Envío';
    else if (domain === 'it') shippingHeader = 'Spedizione';

    let returnHeader = 'Return';
    if (domain === 'de') returnHeader = 'Rücknahmegarantie';
    else if (domain === 'fr') returnHeader = 'Revenir';
    else if (domain === 'es') returnHeader = 'Regreso';
    else if (domain === 'it') returnHeader = 'Ritorno';

    html = html
        .replace('{{Shipping_Header}}', shippingHeader)
        .replace('{{Return_Header}}', returnHeader);

    let paymentHeader = 'Payment';
    if (domain === 'de') paymentHeader = 'Zahlungsinformationen';
    else if (domain === 'fr') paymentHeader = 'Paiement';
    else if (domain === 'es') paymentHeader = 'Pago';
    else if (domain === 'it') paymentHeader = 'Pagamento';   // bug in original: assigned to `payment` var

    let feedbackHeader = 'Feedback';
    if (domain === 'de') feedbackHeader = 'Feedback';
    else if (domain === 'fr') feedbackHeader = "Retour d'information";
    else if (domain === 'es') feedbackHeader = 'Realimentación';
    else if (domain === 'it') feedbackHeader = 'Risposta';

    let contactUsHeader = 'Contact Us';
    if (domain === 'de') contactUsHeader = 'Kontaktieren Sie uns';
    else if (domain === 'fr') contactUsHeader = 'Contactez nous';
    else if (domain === 'es') contactUsHeader = 'Contáctenos';
    else if (domain === 'it') contactUsHeader = 'Contattaci';

    html = html
        .replace('{{Payment_Header}}', paymentHeader)
        .replace('{{Feedback_Header}}', feedbackHeader)
        .replace('{{Contact_Us_Header}}', contactUsHeader);

    // ── Optional: strip accordion sections ────────────────────────────────────
    const { removeSectionsFromDescriptionTemplate } = await chrome.storage.local.get('removeSectionsFromDescriptionTemplate');

    if (removeSectionsFromDescriptionTemplate) {
        const doc = new DOMParser().parseFromString(html, 'text/html');

        doc.querySelectorAll('.accordion').forEach(el => el.remove());
        doc.querySelectorAll('.panel').forEach(el => el.remove());

        html = '<!DOCTYPE html>\n' + doc.documentElement.outerHTML;

        // Append hidden SKU footer element
        const skuFooter = document.createElement('div');
        skuFooter.className = 'footerss';
        skuFooter.setAttribute('data-label', '{{SKU}}');
        skuFooter.style.display = 'none';
        html += skuFooter.outerHTML;
    }

    // ── Image carousel ────────────────────────────────────────────────────────
    try {
        const carouselHtml = buildImageCarousel(imageUrls).outerHTML;
        html = html.replace('{{Product_Carousel}}', carouselHtml);
    } catch (err) {
        html = html.replace('{{Product_Carousel}}', '');
    }

    const encodedSku = btoa(productData.sku);
    return html.replace('{{SKU}}', encodedSku);
}

/**
 * Replace an existing carousel in a description HTML string with a freshly
 * built one using the current listing's uploaded images.
 *
 * @param {string} descriptionHtml
 * @returns {string} Updated HTML
 */
async function replaceCarouselInDescription(descriptionHtml) {
    const imageUrls = await getEbayListingImageUrls();
    const doc = new DOMParser().parseFromString(descriptionHtml, 'text/html');
    console.log('doc', doc);

    const existingCarousel = doc.querySelector('.carousel');
    let carouselParent;

    if (existingCarousel) {
        carouselParent = existingCarousel.parentElement;
        existingCarousel.remove();
    }

    if (imageUrls.length > 0 && carouselParent) {
        const newCarousel = buildImageCarousel(imageUrls);
        carouselParent.appendChild(newCarousel);
    }

    return doc.body.innerHTML;
}

// ─── Asset Helpers ────────────────────────────────────────────────────────────

/**
 * Get the domain-appropriate shipping info HTML partial.
 *
 * @returns {Promise<string>}
 */
async function getShippingInfoHtml() {
    const templateBase = 'Ebay_Description_Templates/Description_Template_2023/';
    const { domain } = await chrome.storage.local.get('domain');

    let shippingHtml;
    try {
        shippingHtml = await fetch(
            chrome.runtime.getURL(templateBase + '/Shipping_Info/Shipping_Info.' + domain + '.html')
        ).then(r => r.text());
    } catch (err) {
        shippingHtml = await fetch(
            chrome.runtime.getURL(templateBase + '/Shipping_Info/Shipping_Info.default.html')
        ).then(r => r.text());
    }

    return shippingHtml;
}

/**
 * Scrape full-resolution image URLs from eBay's listing photo uploader thumbnails.
 *
 * @returns {Promise<string[]>} Array of full-resolution eBay image URLs
 */
async function getEbayListingImageUrls() {
    let thumbnails = document.querySelectorAll('.uploader-thumbnails__image');
    if (!thumbnails.length) {
        thumbnails = document.querySelectorAll('.uploader-thumbnails-ux__image');
    }

    const imageUrls = [];

    for (let i = 0; i < thumbnails.length; i++) {
        console.log('Image ' + i);
        console.log('Image Thumbnail Button', thumbnails[i]);

        const style = thumbnails[i].getAttribute('style');
        if (!style) continue;

        let thumbnailUrl;
        if (style.includes("url('")) {
            thumbnailUrl = style.split("url('")[1].split("')")[0];
        }
        console.log('thumbnail image', thumbnailUrl);

        // Extract the image ID portion and build full-resolution URL
        const pathPattern = /(?=z\/).*(?<=\/)/;
        const match = thumbnailUrl.match(pathPattern);

        try {
            let imagePath = match[0].substring(1, match[0].length - 1);
            imagePath = imagePath.replace('z/', '');
            const fullResUrl = 'https://i.ebayimg.com/images/g' + imagePath + '/s-l1600.jpg';
            imageUrls.push(fullResUrl);
        } catch (err) {
            console.log('Error: ' + err);
        }
    }

    return imageUrls;
}

// ─── Image Carousel Builder ───────────────────────────────────────────────────

/**
 * Build a pure-CSS radio-button image carousel element from an array of image URLs.
 *
 * @param {string[]} imageUrls
 * @returns {HTMLElement} The carousel div element
 */
function buildImageCarousel(imageUrls) {
    const carousel = document.createElement('div');
    carousel.className = 'carousel';

    const slideList = document.createElement('ul');
    slideList.className = 'slides';

    imageUrls.forEach((imageUrl, index) => {
        const totalImages = imageUrls.length;
        const slideId = 'img-' + (index + 1);

        // Radio input for CSS-only navigation
        const radioInput = document.createElement('input');
        radioInput.type = 'radio';
        radioInput.name = 'radio-buttons';
        radioInput.id = slideId;
        if (index === 0) radioInput.setAttribute('checked', 'checked');
        slideList.appendChild(radioInput);

        const prevIndex = ((index - 1 + totalImages) % totalImages) + 1;
        const nextIndex = (index + 1) % totalImages + 1;

        const slide = document.createElement('li');
        slide.className = 'slide-container';
        slide.innerHTML = `
        <div class="slide-image">
          <img src="${imageUrl}" alt="Slide ${index + 1}">
        </div>
        <div class="carousel-controls">
          <label for="img-${prevIndex}" class="prev-slide">
            <span>&lsaquo;</span>
          </label>
          <label for="img-${nextIndex}" class="next-slide">
            <span>&rsaquo;</span>
          </label>
        </div>
      `;
        slideList.appendChild(slide);
    });

    carousel.appendChild(slideList);
    return carousel;
}

// ─── Structured Output / GPT-4.1-nano ─────────────────────────────────────────

/** Placeholder — reserved for future use */
async function reservedPlaceholder() { }

/**
 * Generate description HTML using SilentSniper's Firebase structured_output
 * Cloud Function (GPT-4.1-nano).
 *
 * @param {string} userMessage  - Text to convert into a product description
 * @returns {string} Generated description HTML (or error string)
 */
async function generateDescriptionWithStructuredOutput(userMessage) {
    const { descriptionPrompt } = await chrome.storage.local.get(['descriptionPrompt']);
    const systemMessage = descriptionPrompt
        || 'You are an expert copywriter. Turn the user\u2019s text into an appealing product description. Keep it concise and professional.';

    const { email } = await chrome.storage.local.get(['email']);

    const requestPayload = {
        system_message: systemMessage,
        user_message: userMessage,
        openai_model: 'gpt-4.1-nano',
        model_definition: { description_html: 'str' },
        email
    };

    try {
        const response = await fetchData(
            ' https://us-central1-silentsnipe-ce4ca.cloudfunctions.net/structured_output',
            requestPayload
        );
        return response.success
            ? response.result.description_html
            : 'Error: ' + response.errorMessage;
    } catch (err) {
        return 'Error: ' + err.message;
    }
}

// ─── Custom Description Template ──────────────────────────────────────────────

/**
 * Fill the user's custom description template with product data.
 * Throws if no custom template has been set.
 *
 * @param {string} productTitle
 * @param {string} carouselHtml
 * @param {string} descriptionHtml
 * @returns {string} Rendered HTML
 */
async function fillCustomDescriptionTemplate(productTitle, carouselHtml, descriptionHtml) {
    const { custom_description_template } = await chrome.storage.local.get(['custom_description_template']);
    let template = custom_description_template;

    if (!template) {
        alert('Please set a custom description template in the settings.');
        throw new Error('Custom description template not set.');
    }

    return template
        .replaceAll('{{Product_Title}}', productTitle || '')
        .replaceAll('{{Product_Carousel}}', carouselHtml || '')
        .replaceAll('{{Product_Description}}', descriptionHtml || '');
}

// ─── Preview Modal ─────────────────────────────────────────────────────────────

let isPreviewModalUpdating = false;

/**
 * Render a preview of the custom description template in the modal overlay.
 *
 * @param {string} productTitle
 * @param {string} carouselHtml
 * @param {string} descriptionHtml
 */
async function showDescriptionPreviewModal(productTitle, carouselHtml, descriptionHtml) {
    const modal = document.getElementById('previewModal');
    const modalContent = document.getElementById('modalPreviewContent');
    const renderedHtml = await fillCustomDescriptionTemplate(productTitle, carouselHtml, descriptionHtml);
    modalContent.innerHTML = renderedHtml;
    modal.style.display = 'flex';
}

// ─── Settings Page (DOMContentLoaded) ─────────────────────────────────────────

document.addEventListener('DOMContentLoaded', async () => {
    // ── Element refs ──────────────────────────────────────────────────────────
    const codeModeBtn = document.getElementById('codeModeBtn');
    const liveModeBtn = document.getElementById('liveModeBtn');
    const codeEditor = document.getElementById('codeEditor');
    const liveEditor = document.getElementById('liveEditor');
    const fillDummyBtn = document.getElementById('fillDummyBtn');
    /* const saveTemplateBtn = document.getElementById('saveTemplateBtn'); */  // referenced but unused
    const resetBtn = document.getElementById('resetBtn');
    const descPromptBox = document.getElementById('descPromptBox');
    const generateAiDescBtn = document.getElementById('generateAiDescBtn');
    const resetPromptBtn = document.getElementById('resetPromptBtn');
    const previewModal = document.getElementById('previewModal');
    const closePreviewModal = document.getElementById('closePreviewModal');

    const DEFAULT_DESCRIPTION_PROMPT =
        'You are an expert copywriter. Turn the user\u2019s text into an appealing product description. Keep it concise and professional.';

    // ── Load default template HTML from extension assets ──────────────────────
    let defaultTemplate = await fetch(
        chrome.runtime.getURL('Ebay_Description_Templates/Description_Template_2023//Main_Template/Main_Template_3.html')
    ).then(r => r.text());

    // ── Initialise code editor with stored or default template ────────────────
    const { custom_description_template } = await chrome.storage.local.get(['custom_description_template']);
    codeEditor.value = custom_description_template || defaultTemplate;

    if (!custom_description_template) {
        await chrome.storage.local.set({ custom_description_template: defaultTemplate });
    }

    // Show live editor by default
    liveEditor.innerHTML = codeEditor.value;
    codeEditor.style.display = 'none';
    liveEditor.style.display = 'block';
    liveModeBtn.classList.add('active');

    // ── Mode toggle buttons ───────────────────────────────────────────────────
    codeModeBtn.onclick = () => {
        codeModeBtn.classList.add('active');
        liveModeBtn.classList.remove('active');
        liveEditor.style.display = 'none';
        codeEditor.style.display = 'block';
    };

    liveModeBtn.onclick = () => {
        liveModeBtn.classList.add('active');
        codeModeBtn.classList.remove('active');
        codeEditor.style.display = 'none';
        liveEditor.style.display = 'block';
        liveEditor.innerHTML = codeEditor.value;
    };

    // ── Auto-save on edit ─────────────────────────────────────────────────────
    liveEditor.addEventListener('input', async () => {
        if (isPreviewModalUpdating) return;
        const updatedHtml = liveEditor.innerHTML;
        codeEditor.value = updatedHtml;
        await chrome.storage.local.set({ custom_description_template: updatedHtml });
    });

    codeEditor.addEventListener('input', async () => {
        if (codeModeBtn.classList.contains('active')) {
            liveEditor.innerHTML = codeEditor.value;
        }
        await chrome.storage.local.set({ custom_description_template: codeEditor.value });
    });

    // ── Fill dummy data preview ───────────────────────────────────────────────
    fillDummyBtn.onclick = async () => {
        const dummyCarousel = buildImageCarousel([
            'https://m.media-amazon.com/images/I/81b36indJ7L.__AC_SX300_SY300_QL70_FMwebp_.jpg',
            'https://m.media-amazon.com/images/I/81jtjc59wXL._AC_SX679_.jpg',
            'https://m.media-amazon.com/images/I/81ooM2MNw3L._AC_SX679_.jpg'
        ]).outerHTML;

        await showDescriptionPreviewModal(
            'WORKPRO 476PCS Rotary Tool Accessories Kit',
            dummyCarousel,
            '<p>Accessories for cutting, sanding, grinding, and polishing.</p>'
        );
    };

    // ── Generate AI description preview ──────────────────────────────────────
    generateAiDescBtn.onclick = async () => {
        const aiDescription = await generateDescriptionWithStructuredOutput(
            '<p>Accessories for cutting, sanding, grinding, and polishing.</p>'
        );
        const dummyCarousel = buildImageCarousel([
            'https://m.media-amazon.com/images/I/81b36indJ7L.__AC_SX300_SY300_QL70_FMwebp_.jpg',
            'https://m.media-amazon.com/images/I/81jtjc59wXL._AC_SX679_.jpg',
            'https://m.media-amazon.com/images/I/81ooM2MNw3L._AC_SX679_.jpg'
        ]).outerHTML;

        await showDescriptionPreviewModal(
            'WORKPRO 476PCS Rotary Tool Accessories Kit',
            dummyCarousel,
            aiDescription
        );
    };

    // ── Modal close ───────────────────────────────────────────────────────────
    closePreviewModal.onclick = () => {
        previewModal.style.display = 'none';
    };

    // ── Reset template ────────────────────────────────────────────────────────
    resetBtn.onclick = async () => {
        await chrome.storage.local.set({ custom_description_template: defaultTemplate });
        codeEditor.value = defaultTemplate;
        liveEditor.innerHTML = defaultTemplate;
        alert('Template reset to default!');
    };

    // ── Description prompt box ────────────────────────────────────────────────
    const { descriptionPrompt } = await chrome.storage.local.get(['descriptionPrompt']);
    descPromptBox.value = descriptionPrompt || DEFAULT_DESCRIPTION_PROMPT;

    descPromptBox.addEventListener('input', async () => {
        await chrome.storage.local.set({ descriptionPrompt: descPromptBox.value });
    });

    resetPromptBtn.onclick = async () => {
        descPromptBox.value = DEFAULT_DESCRIPTION_PROMPT;
        await chrome.storage.local.set({ descriptionPrompt: DEFAULT_DESCRIPTION_PROMPT });
        alert('Prompt reset to default!');
    };
});



