console.log('ebay_description.js');

// ─── Core description injection flow (basic template) ───────────────────────

/**
 * Builds an eBay description from the basic HTML template, injects it into the
 * listing editor, then waits for the network response to confirm the save.
 * @param {Object} productData  Full product data object (custom_title, descriptionText,
 *                              bullet_points, bullet_points_html, mpn, brand, sku)
 */
function addBasicDescriptionToEbay(productData) {
    return new Promise(async (resolve, reject) => {
        documentTitle = 'Ebay: Adding Description...';

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

        // Populate placeholders, then strip flagged keywords
        html = sanitizeFlaggedKeywords(html);
        html = replaceTitleDescriptionAndBullets(html, productData);
        html = removeMPNFromText(html, productData.mpn);
        html = removeBrandFromText(html, productData.brand);

        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;

        await injectDescriptionIntoEditor(html);
        documentTitle = 'Ebay: Description Added';
        await triggerDescriptionNetworkRequest();
        await waitForDescriptionNetworkResponse();
        resolve();
    });
}

/**
 * Builds and returns the basic description HTML string without injecting it.
 * Also base64-encodes the SKU into the {{SKU}} placeholder.
 * @param {Object} productData
 * @returns {string} Populated 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 = replaceTitleDescriptionAndBullets(html, productData);

    console.log('removeFlaggedKeywords template');
    html = sanitizeFlaggedKeywords(html);

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

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

    try {
        const encodedSku = btoa(productData.sku);
        html = html.replace('{{SKU}}', encodedSku);
    } catch (e) {
        console.log('error', e);
    }

    return html;
}

// ─── OpenAI / ChatGPT description generation ────────────────────────────────

/**
 * Uses a text prompt file + the legacy OpenAI completions API (via background
 * script) to generate a description, then injects it into the eBay editor.
 * Retries automatically on server_error responses.
 * @param {Object} productData
 */
async function generateAndInjectDescriptionViaOpenAILegacy(productData) {
    let descriptionHtml;

    const promptTemplate = await fetchPrompt('text_prompts/description_prompt.txt');
    const bulletAndDescText = productData.bullet_points.join('\n') + '\n' + productData.descriptionText;
    const title = productData.custom_title;

    const filledPrompt = promptTemplate
        .replace('{{productTitle}}', title)
        .replace('{{productDescription}}', bulletAndDescText);

    documentTitle = 'OpenAI: Generating Description...';

    const response = await receiveOpenAiResponseFromBackgroundScript(filledPrompt);

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

        descriptionHtml = removeMPNFromText(descriptionHtml, productData.mpn);
        descriptionHtml = removeBrandFromText(descriptionHtml, productData.brand);

        const shippingUrl = chrome.runtime.getURL('Ebay_Description_Templates/Shipping_Handling.html');
        const shippingHtml = await fetch(shippingUrl).then(r => r.text());

        descriptionHtml = descriptionHtml + await fetchCallToActionHtml() + shippingHtml;
        descriptionHtml = wrapInFontDiv(descriptionHtml);

        documentTitle = 'OpenAI: Adding Description to Ebay';
        await injectDescriptionIntoEditor(descriptionHtml);
        documentTitle = 'OpenAI: Description Added to Ebay';
        await triggerDescriptionNetworkRequest();
        await waitForDescriptionNetworkResponse();
    } else {
        console.log('response.error', response.error);
        const errorType = response.error.type;

        if (errorType.includes('server_error')) {
            console.log('Retrying due to server_error...');
            await generateAndInjectDescriptionViaOpenAILegacy(productData);
            return;
        }
    }
}

/**
 * Injects a pre-built description HTML string directly into the eBay editor.
 * @param {string} descriptionHtml
 */
async function injectPrebuiltDescription(descriptionHtml) {
    documentTitle = 'OpenAI: Adding Description to Ebay';
    await injectDescriptionIntoEditor(descriptionHtml);
    documentTitle = 'OpenAI: Description Added to Ebay';
    await triggerDescriptionNetworkRequest();
    await waitForDescriptionNetworkResponse();
}

/**
 * Posts to the SilentSniper title-builder server to generate a description, then
 * returns the final HTML string (with call-to-action and shipping appended).
 * Retries once if the response is empty.
 * @param {Object} productData
 * @returns {string} Final description HTML
 */
async function generateDescriptionFromTitleBuilderServer(productData) {
    let descriptionHtml;

    const bulletText = productData.bullet_points.join('\n') + '\n' + productData.descriptionText;
    const title = productData.custom_title;
    const serverUrl = 'https://ebaysnipertitlebuilder.ngrok.io/api/TitleBuilder/GenerateDescription';

    let serverResponse = await postToServer(serverUrl, {
        request_type: 'generate_description',
        product_description: bulletText,
        product_title: title,
    });

    descriptionHtml = serverResponse.optimizedDescription;

    // Retry once if the response is empty/null
    if (descriptionHtml == null || descriptionHtml === '' || descriptionHtml === ' ') {
        serverResponse = await postToServer(serverUrl, {
            request_type: 'generate_description',
            product_description: bulletText,
            product_title: title,
        });
        descriptionHtml = serverResponse.optimizedDescription;
    }

    documentTitle = 'OpenAI: Description Generated';
    descriptionHtml = removeMPNFromText(descriptionHtml, productData.mpn);
    descriptionHtml = removeBrandFromText(descriptionHtml, productData.brand);

    const shippingUrl = chrome.runtime.getURL('Ebay_Description_Templates/Shipping_Handling.html');
    const shippingHtml = await fetch(shippingUrl).then(r => r.text());

    descriptionHtml = descriptionHtml + await fetchCallToActionHtml() + shippingHtml;
    return wrapInFontDiv(descriptionHtml);
}

// ─── Template helpers ────────────────────────────────────────────────────────

/**
 * Replaces [TITLE], [DESCRIPTION], and [BULLETS] placeholders in an HTML
 * template string using product data.
 * @param {string} html
 * @param {Object} productData
 * @returns {string}
 */
function replaceTitleDescriptionAndBullets(html, productData) {
    html = html.replace(/\[TITLE\]/, productData.custom_title);

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

    if (productData.bullet_points_html) {
        html = html.replace(/\[BULLETS\]/, productData.bullet_points_html);
    } else {
        html = html.replace(/\[BULLETS\]/, '');
    }

    // Clean up redundant consecutive <br> sequences left by empty bullet blocks
    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;
}

/**
 * Wraps an HTML string in a styled <div> (font, size, color).
 * @param {string} html
 * @param {string} [fontFamily='Arial']
 * @param {string} [fontSize='16px']
 * @param {string} [color='black']
 * @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 injection ───────────────────────────────────────────────────

/**
 * Injects HTML into the eBay description rich-text editor iframe, dispatches a
 * change event to notify eBay's JS, and highlights the iframe border.
 * @param {string} descriptionHtml
 * @returns {Promise<void>}
 */
function injectDescriptionIntoEditor(descriptionHtml) {
    return new Promise(async (resolve, reject) => {
        const iframe = document.getElementById('se-rte-frame__summary');
        scrollIntoView(iframe);

        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
        const editor = iframeDoc.body.querySelector('.se-rte-editor__rich');

        if (editor) {
            console.log('contentEditable exists');
        } else {
            console.log('contentEditable does not exist');
        }

        // Validate HTML by parsing (unused result, kept for parity)
        document.createElement('div').innerHTML = descriptionHtml;

        editor.innerHTML = descriptionHtml;
        await waitSomeTime(1000);
        editor.focus();

        const changeEvent = new Event('change', { bubbles: true });
        editor.dispatchEvent(changeEvent);

        iframe.style.border = '3px solid #DFFF00';
        resolve();
    });
}

/**
 * Fetches the call-to-action HTML snippet from the extension bundle.
 * @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 / experimental functions (not used in production flow) ────────────

/** Debug function: fires a battery of change/click events on the editor. */
function debugFireEditorEvents() {
    const iframe = document.getElementById('se-rte-frame__summary');
    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
    const editor = iframeDoc.body.querySelector('.se-rte-editor__rich');

    monitorEvents(editor);
    editor.focus();

    let evt = new CustomEvent('change');
    editor.dispatchEvent(evt);

    evt = document.createEvent('HTMLEvents');
    evt.initEvent('change', true, false);
    editor.dispatchEvent(evt);

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

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

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

    editor.focus();
    editor.blur();
    iframeDoc.body.focus();
    document.body.click();

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

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

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

// ─── Text sanitisation helpers ───────────────────────────────────────────────

/**
 * Removes each string in the mpn array (case-insensitive) from the text.
 * @param {string} text
 * @param {string[]} mpnList
 * @returns {string}
 */
function removeMPNFromText(text, mpnList) {
    if (!mpnList) return text;
    if (!Array.isArray(mpnList)) return text;

    if (mpnList.length > 0) {
        for (let i = 0; i < mpnList.length; i++) {
            const mpn = mpnList[i].toLowerCase();
            text = insensitiveReplaceAll(text, mpn, '');
        }
    }
    return text;
}

/**
 * Removes the brand string (and its hyphenated variant) from text,
 * case-insensitively.
 * @param {string} text
 * @param {string} brand
 * @returns {string}
 */
function removeBrandFromText(text, brand) {
    if (!brand) return text;
    text = insensitiveReplaceAll(text, brand, '');
    const hyphenatedBrand = brand.replace(/\s+/g, '-');
    return insensitiveReplaceAll(text, hyphenatedBrand, '');
}

/**
 * Strips/replaces a large list of policy-violating or platform-specific terms
 * from a description HTML string (Amazon references, warranty language, URLs,
 * emails, phone numbers, etc.).  Also blanks href/src attributes to avoid
 * external links, and upgrades http: to https:.
 * @param {string} html
 * @returns {string}
 */
function sanitizeFlaggedKeywords(html) {
    // Upgrade mixed-content URLs
    html = html.replace(/http\:/gi, 'https:');

    // Strip href and src attributes (no external links in eBay descriptions)
    html = html.replace(/<a(.*)href=("|')([^'"]+)("|')(.*)>/gi, "<a$1href=''$5>");
    html = html.replace(/<img(.*)src=("|')([^'"]+)("|')(.*)>/gi, "<img$1src=''$5>");

    // Remove script tags entirely
    html = html.replace(/<script.*?>[\s\S]*?<\/script>/gi, '');

    // Platform references
    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', '');

    // Warranty / refund language (eBay policy)
    html = insensitiveReplaceAll(html, 'warranty', 'satisfaction');
    html = insensitiveReplaceAll(html, 'warran-ty', 'satisfaction');
    html = insensitiveReplaceAll(html, 'refund', 'satisfaction');

    // Contact details
    html = insensitiveReplaceAll(html, 'Email Address', '');
    html = insensitiveReplaceAll(html, 'Email', '');
    html = insensitiveReplaceAll(html, 'Address', '');
    html = insensitiveReplaceAll(html, 'phone', '');

    // Policy / marketing terms
    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, email addresses, and phone numbers via regex
    html = html.replace(/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, '');
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/gim, '');
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/g, '');
    html = html.replace(/([^.@\s]+)(\.[^.@\s]+)*@([^.@\s]+\.)+([^.@\s]+)/gi, '');
    html = html.replace(/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/g, '');
    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]))', '');

    // Generic brand reference → 'product'
    html = insensitiveReplaceAll(html, 'brand', 'product');

    return html;
}

// ─── Trigger eBay network save ───────────────────────────────────────────────

/**
 * Clicks the listing title input to blur the description editor, which causes
 * eBay's React state to flush and triggers the description save network request.
 * @returns {Promise<void>}
 */
function triggerDescriptionNetworkRequest() {
    return new Promise((resolve, reject) => {
        documentTitle = 'Trigger Description Network Request';
        document.querySelector(".smry.summary__title input[name='title']").focus();
        resolve();
    });
}

// ─── OpenAI structured-output description generators ────────────────────────

/**
 * Polls the Cloud Function / OpenAI structured-output endpoint (up to 99 times)
 * until a non-empty optimized_description is returned, then sanitises and
 * returns it.
 * @param {Object} productData
 * @returns {Promise<string>} Plain text / HTML description
 */
async function getDescriptionFromOpenAIStructuredOutput(productData) {
    let descriptionHtml = null;
    let bulletText;

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

    const fullText = bulletText + '\n' + productData.descriptionText;
    const title = productData.custom_title;
    let attempts = 0;

    while (attempts < 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' + fullText;

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

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

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

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

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

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

    try { descriptionHtml = removeMPNFromText(descriptionHtml, productData.mpn); } catch (e) { }
    try { descriptionHtml = removeBrandFromText(descriptionHtml, productData.brand); } catch (e) { }

    return descriptionHtml;
}

/**
 * Uses `askChatGptForDescription` (defined elsewhere) to generate a description,
 * then sanitises and returns it.
 * @param {Object} productData
 * @returns {Promise<string>}
 */
async function getDescriptionFromChatGpt(productData) {
    let descriptionHtml = null;
    let bulletText;

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

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

    descriptionHtml = await askChatGptForDescription(title, fullText);
    console.log('getDescriptionHtmlFromChatGpt descriptionHtml', descriptionHtml);
    console.log('getDescriptionHtmlFromOpenAi descriptionHtml', descriptionHtml);
    documentTitle = 'OpenAI: Description Generated';

    try { descriptionHtml = removeMPNFromText(descriptionHtml, productData.mpn); } catch (e) { }
    try { descriptionHtml = removeBrandFromText(descriptionHtml, productData.brand); } catch (e) { }

    return descriptionHtml;
}

// ─── 2023 Description Template builders ─────────────────────────────────────

/**
 * Assembles the 2023 multi-section description template (Main_Template_2) with
 * contact, shipping, payment, return, and feedback partials, then injects
 * product images as a carousel and encodes the SKU.
 * @param {string} productDescriptionHtml  The main product description block
 * @param {Object} productData             Product data (custom_title, sku)
 * @returns {Promise<string>}
 */
async function buildDescriptionTemplate2023(productDescriptionHtml, productData) {
    console.log('createDescriptionFromTemplate2023');

    const templateDir = 'Ebay_Description_Templates/Description_Template_2023/';

    const mainTemplate = await fetch(chrome.runtime.getURL(templateDir + '/Main_Template/Main_Template_2.html')).then(r => r.text());
    const contactInfo = await fetch(chrome.runtime.getURL(templateDir + '/Contact_Info/Contact_Info.html')).then(r => r.text());
    const shippingInfo = await getLocalizedShippingHtml();
    const paymentInfo = await fetch(chrome.runtime.getURL(templateDir + '/Payment_Info/Payment_Info.html')).then(r => r.text());
    const returnInfo = await fetch(chrome.runtime.getURL(templateDir + '/Return_Info/Return_Info.html')).then(r => r.text());
    const feedbackInfo = await fetch(chrome.runtime.getURL(templateDir + '/Feedback_Info/Feedback_Info.html')).then(r => r.text());

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

    // Static image asset substitutions
    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');

    const productImages = await getListingImageUrls();
    html = html
        .replace('{{Product_Image_URL}}', productImages[0])
        .replace('{{Product_Title}}', productData.custom_title)
        .replace('{{Store_Thank_You_Message}}', 'Thank you for supporting our small family business!');

    try {
        const carouselHtml = buildImageCarousel(productImages).outerHTML;
        html = html.replace('{{Product_Carousel}}', carouselHtml);
    } catch (e) {
        html = html.replace('{{Product_Carousel}}', '');
    }

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

/**
 * Assembles the localised 2023 description template (Main_Template_3), with
 * domain-aware partial HTML files, multilingual section headers, and optional
 * removal of accordion sections.
 * @param {string} productDescriptionHtml
 * @param {Object} productData             Product data (custom_title, sku)
 * @returns {Promise<string>}
 */
async function buildLocalizedDescriptionTemplate2023(productDescriptionHtml, productData) {
    const { domain } = await chrome.storage.local.get('domain');
    const templateDir = 'Ebay_Description_Templates/Description_Template_2023/';

    const mainTemplate = await fetch(chrome.runtime.getURL(templateDir + '/Main_Template/Main_Template_3.html')).then(r => r.text());
    const contactInfo = await fetch(chrome.runtime.getURL(templateDir + '/Contact_Info/Contact_Info-' + domain + '.html')).then(r => r.text());
    const shippingInfo = await getLocalizedShippingHtml();
    const paymentInfo = await fetch(chrome.runtime.getURL(templateDir + '/Payment_Info/Payment_Info-' + domain + '.html')).then(r => r.text());
    const returnInfo = await fetch(chrome.runtime.getURL(templateDir + '/Return_Info/Return_Info-' + domain + '.html')).then(r => r.text());
    const feedbackInfo = await fetch(chrome.runtime.getURL(templateDir + '/Feedback_Info/Feedback_Info-' + domain + '.html')).then(r => r.text());

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

    const productImages = await getListingImageUrls();

    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}}', productImages[0])
        .replace('{{Product_Title}}', productData.custom_title);

    // ── Localised thank-you message ──────────────────────────────────────────
    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');

    // ── Localised section headers ─────────────────────────────────────────────
    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 = (disableThankYouMessage
        ? html.replace('{{Store_Thank_You_Message}}', '')
        : html.replace('{{Store_Thank_You_Message}}', thankYouMessage))
        .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') payment = 'Pagamento'; // bug in original: sets global `payment`

    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);

    // ── Optionally strip accordion/panel 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 a hidden SKU div for later extraction
        const skuDiv = document.createElement('div');
        skuDiv.className = 'footerss';
        skuDiv.setAttribute('data-label', '{{SKU}}');
        skuDiv.style.display = 'none';
        html += skuDiv.outerHTML;
    }

    // ── Product image carousel ───────────────────────────────────────────────
    try {
        const carouselHtml = buildImageCarousel(productImages).outerHTML;
        html = html.replace('{{Product_Carousel}}', carouselHtml);
    } catch (e) {
        html = html.replace('{{Product_Carousel}}', '');
    }

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

/**
 * Replaces the existing carousel element in a description HTML string with a
 * freshly built one using the current listing's uploaded images.
 * @param {string} descriptionHtml
 * @returns {Promise<string>} Updated HTML body innerHTML
 */
async function refreshCarouselInDescription(descriptionHtml) {
    const productImages = await getListingImageUrls();
    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 (productImages.length > 0 && carouselParent) {
        const newCarousel = buildImageCarousel(productImages);
        carouselParent.appendChild(newCarousel);
    }

    return doc.body.innerHTML;
}

/**
 * Fetches the shipping info HTML partial for the current domain, falling back
 * to the default if the domain-specific file is not found.
 * @returns {Promise<string>}
 */
async function getLocalizedShippingHtml() {
    const templateDir = 'Ebay_Description_Templates/Description_Template_2023/';
    const { domain } = await chrome.storage.local.get('domain');

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

/**
 * Reads the uploaded listing images from the eBay thumbnail strip and returns
 * their full-size (s-l1600) eBay image CDN URLs.
 * @returns {Promise<string[]>}
 */
async function getListingImageUrls() {
    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 imageUrl;
        if (style.includes("url('")) {
            imageUrl = style.split("url('")[1].split("')")[0];
        }
        console.log('thumbnail image', imageUrl);

        const pathPattern = /(?=z\/).*(?<=\/)/;
        const pathMatch = imageUrl.match(pathPattern);

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

    return imageUrls;
}

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

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

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

        // Hidden radio button for CSS carousel state
        const radioInput = document.createElement('input');
        radioInput.type = 'radio';
        radioInput.name = 'radio-buttons';
        radioInput.id = slideId;
        if (index === 0) radioInput.setAttribute('checked', 'checked');
        slidesList.appendChild(radioInput);

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

        const listItem = document.createElement('li');
        listItem.className = 'slide-container';
        listItem.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>
      `;
        slidesList.appendChild(listItem);
    });

    carouselDiv.appendChild(slidesList);
    return carouselDiv;
}

// ─── Stub (empty function in original) ──────────────────────────────────────
async function _0x55ad76() { }

// ─── Firebase Cloud Function structured-output description generator ─────────

/**
 * Sends a user message to the SilentSniper structured-output Cloud Function
 * (GPT-4.1-nano) and returns the generated description_html field.
 * @param {string} userMessage
 * @returns {Promise<string>} HTML description string or error message
 */
async function generateDescriptionHtmlViaCloudFunction(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 requestBody = {
        system_message: systemMessage,
        user_message: userMessage,
        openai_model: 'gpt-4.1-nano',
        model_definition: { description_html: 'str' },
        email: email,
    };

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

// ─── Custom description template ─────────────────────────────────────────────

/**
 * Fills in a user-defined description template stored in chrome.storage.local
 * under the key `custom_description_template`.  Throws if the template is not set.
 * @param {string} productTitle
 * @param {string} productCarouselHtml
 * @param {string} productDescriptionHtml
 * @returns {Promise<string>}
 */
async function buildCustomDescriptionTemplate(productTitle, productCarouselHtml, productDescriptionHtml) {
    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}}', productCarouselHtml || '')
        .replaceAll('{{Product_Description}}', productDescriptionHtml || '');
}

// ─── Dev/test entrypoint ─────────────────────────────────────────────────────

/** Quick smoke-test: renders the 2023 template with dummy data into document.body */
async function devTestRenderTemplate() {
    const html = await buildDescriptionTemplate2023('test', { name: 'Product 1', price: 100 });
    console.log(html);
    document.body.innerHTML = html;
}

devTestRenderTemplate();



