Bazaar + TE Info PDA Version

Adds Bazaar listings, TornExchange data, sorting/filtering, and Market Margin Calculator to Item Market/Bazaar detail views. Optimized for PDA/mobile.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Bazaar + TE Info PDA Version 
// @namespace    https://weav3r.dev/
// @version      1.6
// @description  Adds Bazaar listings, TornExchange data, sorting/filtering, and Market Margin Calculator to Item Market/Bazaar detail views. Optimized for PDA/mobile.
// @author       WTV [3281931]
// @match        https://www.torn.com/*
// @match        https://www.torn.com/itemmarket.php*
// @match        https://www.torn.com/bazaar.php*
// @grant        GM_xmlhttpRequest
// @connect      weav3r.dev
// @connect      tornexchange.com
// @connect      www.torn.com
// @run-at       document-end  
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    
    console.log("[Bazaar + TE Info PDA Version V 2.9.9.3 UI Restructure].");

    // Global state
    window._visitedBazaars = window._visitedBazaars || new Set();
    window._cachedListings = window._cachedListings || {}; 
    window._marketValueCache = window._marketValueCache || {}; 
    window._activeSort = window._activeSort || { type: 'price', dir: 'asc' }; 
    // NEW GLOBAL STATE
    window._currentMarketNetPrice = window._currentMarketNetPrice || 0; 
    
    let isMobileView = false;
    let currentDarkMode = document.body.classList.contains("dark-mode");
    let lastUrl = window.location.href; 
    
    // ----------------------------------------------------------------------
    // --- UTILITY & STYLES ---
    // ----------------------------------------------------------------------
    
    function checkMobileView() {
        isMobileView = window.innerWidth <= 784; 
        return isMobileView;
    }
    checkMobileView();
    window.addEventListener("resize", checkMobileView);
    
    function extractItemId() {
        let itemId = null;
        
        if (isMobileView) {
            const btn = document.querySelector('[class*="itemsHeader"] button[aria-controls^="wai-itemInfo-"]');
            if (btn) {
                const controls = btn.getAttribute("aria-controls");
                const parts = controls.split("-");
                itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1];
            }
            
            if (!document.querySelector('ul[class*="sellerList"]')) {
                return null;
            }
            
        } else { // PC View
            const itemDetails = document.querySelector('[class*="item-details"], [class*="item-info-desc"]');
            
            if (!itemDetails) {
                 return null;
            }
            
            const itemImg = itemDetails.querySelector('img[src*="/images/items/"]');
            if (itemImg) {
                 const src = itemImg.getAttribute('src');
                 const imgMatch = src.match(/\/images\/items\/(\d+)\//i);
                 if (imgMatch && imgMatch[1]) { itemId = imgMatch[1]; }
            }
            
            if (!itemId) {
                const currentURL = new URL(window.location.href);
                const urlMatch = currentURL.search.match(/ID=(\d+)/i) || window.location.hash.match(/ID=(\d+)/i);
                if (urlMatch && urlMatch[1]) { itemId = urlMatch[1]; }
            }
        }
        
        return (itemId && itemId !== "unknown") ? itemId : null;
    }
    
    /**
     * Cleans and formats a number input value with commas.
     * @param {HTMLInputElement} input - The input element to process.
     * @returns {number} The parsed number value.
     */
    function formatNumberInput(input) {
        let value = input.value.replace(/[^\d]/g, ''); // Remove non-digit characters
        let numValue = parseInt(value, 10);

        if (isNaN(numValue) || numValue < 0) {
            input.value = '';
            return 0;
        }

        // Format with commas (only on blur/change for better typing experience)
        if (document.activeElement !== input) {
            input.value = numValue.toLocaleString();
        }

        return numValue;
    }

    function updateStyles() {
        let styleEl = document.getElementById("bazaar-enhanced-styles");
        if (!styleEl) {
            styleEl = document.createElement("style");
            styleEl.id = "bazaar-enhanced-styles";
            document.head.appendChild(styleEl);
        }
        styleEl.textContent = `
            .bazaar-info-container { border: 1px solid #888; margin: 5px 0 10px 0; padding: 5px; background: #222; color: #fff; border-radius: 4px; max-width: 100%; box-sizing: border-box; }
            .dark-mode .bazaar-info-container { background: #222; border-color: #444; }
            .bazaar-control-line { display: flex; flex-wrap: nowrap; gap: 4px; align-items: center; overflow-x: auto; padding-right: 5px; } 
            .bazaar-filter-toggle-btn { background: #555; color: white; border: none; padding: 4px 5px; cursor: pointer; font-weight: bold; height: 28px; width: 50px; flex-shrink: 0; font-size: 12px; border-radius: 4px; }
            .bazaar-filter-toggle-btn.active-filter { background: #007bff; }
            .bazaar-sort-btn { cursor:pointer; font-size: 14px; line-height: 1; margin: 0; padding: 0; }
            .bazaar-filter-input { padding: 4px; background: #333; border: 1px solid #444; color: white; height: 28px; box-sizing: border-box; font-size: 12px; border-radius: 4px; }
            .bazaar-filter-group-price input { width: 48px; } 
            .bazaar-filter-group-quantity input { width: 45px; } 
            .bazaar-apply-btn { background: #28a745; color: white; border: none; padding: 4px 8px; cursor: pointer; font-weight: bold; height: 28px; flex-shrink: 0; font-size: 12px; border-radius: 4px; }
            .bazaar-card-container { display:flex; overflow-x:auto; padding:5px; gap:5px; }
            .bazaar-card { border:1px solid #444; background:#222; color:#eee; padding: 2px 3px; margin:2px; width: 95px; flex-shrink: 0; cursor:pointer; display:flex; flex-direction:column; font-family: inherit; font-size:14px; transition:transform 0.2s; position: relative; gap: 0; border-radius: 4px; } 
            .bazaar-card a { overflow: hidden !important; white-space: nowrap !important; text-overflow: ellipsis; line-height: 1.1; font-size: 14px !important; padding: 0; margin: 0 0 3px 0; display: block; }
            .bazaar-card-line-1 { font-size: 14px !important; line-height: 1.0; padding: 0; margin: 3px 0 3px 0; font-weight: bold; display: flex; justify-content: space-between; align-items: center; } /* Price Line */
            .bazaar-card-line-2 { font-size: 12px !important; line-height: 1.0; padding: 0; margin: 3px 0 0 0; display: flex; justify-content: space-between; align-items: baseline; } /* Qty/Diff Line */
            .bazaar-card-line-3 { font-size: 12px !important; line-height: 1.0; padding: 0; margin: 0; display: flex; justify-content: space-between; align-items: baseline; } /* Margin Line */
            .bazaar-title { font-size: 16px !important; font-weight: bold !important; flex-grow: 1; overflow: visible; white-space: normal;}
            .bazaar-info-header { font-weight:bold;margin-bottom:5px; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; font-size: 14px; overflow: visible; }
            .bazaar-te-id-line { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
            .bazaar-te-id-line .item-id-display { color:#aaa; font-size:13px; font-weight: bold; white-space: nowrap; }
            .bazaar-filter-inputs-group { display: flex; gap: 2px; align-items: center; flex-wrap: nowrap; flex-grow: 1; justify-content: flex-end; flex-shrink: 0; }
            .bazaar-filter-group-price, .bazaar-filter-group-quantity { display: flex; align-items:center; gap:2px; flex-shrink: 0; }
            .best-buyer-line { font-weight:bold; margin-bottom: 5px; color:#FFA500; display: flex; flex-wrap: nowrap; justify-content: flex-start; align-items: center; gap: 5px; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .best-buyer-line > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .best-buyer-line > span:first-child, .best-buyer-line .price-display-span { flex-shrink: 0; }
            .best-buyer-line a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; }
            
            /* CALCULATOR STYLES */
            .bazaar-market-calc {
                background: #333;
                padding: 8px;
                margin-top: 5px; /* Added margin-top to separate it from filters */
                margin-bottom: 10px;
                border: 1px solid #555;
                display: flex;
                flex-direction: column;
                gap: 5px;
                font-size: 13px;
                border-radius: 4px;
            }

            .bazaar-market-calc label {
                display: flex;
                align-items: center;
                gap: 5px;
                white-space: nowrap;
                justify-content: space-between;
            }

            .bazaar-calc-input {
                width: 100px;
                padding: 3px 5px;
                border: 1px solid #888;
                background: #111;
                color: #fff;
                font-size: 13px;
                text-align: right;
                border-radius: 4px;
            }

            .bazaar-net-profit {
                font-weight: bold;
                color: #00BFFF;
                font-size: 14px;
            }
            
            /* MARGIN STYLES */
            .margin-info {
                font-size: 12px !important;
                font-weight: bold;
                white-space: nowrap;
                line-height: 1.0; 
                padding: 0; 
                margin: 0;
                display: flex; 
                justify-content: space-between; 
                width: 100%; 
            }
            .diff-text-positive { color: red; font-weight: bold; } /* Loss/Negative Margin */
            .diff-text-negative { color: limegreen; font-weight: bold; } /* Profit/Positive Margin */
            .diff-text-neutral { color: gold; font-weight: bold; }
        `;
    }
    updateStyles();

    const darkModeObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.attributeName === "class") {
                const newDarkMode = document.body.classList.contains("dark-mode");
                if (newDarkMode !== currentDarkMode) {
                    currentDarkMode = newDarkMode;
                    updateStyles();
                }
            }
        });
    });
    darkModeObserver.observe(document.body, { attributes: true });

    function renderNoListingsMessage(container, isError){
        const cardContainer = container.querySelector('.bazaar-card-container');
        if(!cardContainer) return;
        cardContainer.innerHTML = '';
        const msg = document.createElement('div');
        msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
        msg.innerHTML = isError ? "API Error<br><span style='font-size:12px;color:#ccc;'>Could not fetch bazaar data.</span>"
                                 : "No bazaar listings available for this item.";
        cardContainer.appendChild(msg);
    }
    
    function fetchUpvoteCountAndId(userName, callback) {
        const url = `https://tornexchange.com/prices/${userName}/`;
        GM_xmlhttpRequest({
            method: "GET", url: url,
            onload: function(response) {
                let upvoteCount = null;
                let traderId = null;
                try {
                    const doc = new DOMParser().parseFromString(response.responseText, "text/html");
                    const upvoteElement = doc.querySelector('#vote-score');
                    if (upvoteElement) { upvoteCount = upvoteElement.textContent.trim(); }
                    const profileLink = doc.querySelector('a[href*="profiles.php?XID="]');
                    if (profileLink) {
                        const href = profileLink.getAttribute('href');
                        const match = href.match(/XID=(\d+)/);
                        if (match && match[1]) { traderId = match[1]; }
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                callback(upvoteCount, traderId);
            },
            onerror: function() { callback(null, null); }
        });
    }

    // ----------------------------------------------------------------------
    // --- MARKET CALCULATOR LOGIC ---
    // ----------------------------------------------------------------------

    function calculateMarketNet(container) {
        const inputEl = container.querySelector('.bazaar-calc-input');
        const profitEl = container.querySelector('.bazaar-net-profit');

        // 1. Get cleaned price.
        const price = parseFloat(inputEl.value.replace(/[^\d]/g, ''));

        if (isNaN(price) || price <= 0) {
            window._currentMarketNetPrice = 0;
            if (profitEl) profitEl.textContent = `$0`;
            // Refresh to clear margins
            sortAndFilterListings(container.dataset.itemid, container);
            return;
        }

        // 2. Calculate Net Profit (5% fee)
        const netProfit = Math.floor(price * 0.95);
        window._currentMarketNetPrice = netProfit;

        // 3. Update display
        if (profitEl) {
             profitEl.textContent = `$${netProfit.toLocaleString()}`;
        }
        
        // 4. Refresh cards
        sortAndFilterListings(container.dataset.itemid, container);
    }
    
    /**
     * Binds input, blur, and change events to the market price input.
     * @param {HTMLElement} container - The main script container element.
     */
    function addMarketFeeListener(container) {
        const inputEl = container.querySelector('.bazaar-calc-input');
        if (!inputEl) return;

        // Input event: Calculate immediately for responsiveness
        inputEl.addEventListener('input', () => calculateMarketNet(container));

        // Blur event: Apply formatting and recalculate
        inputEl.addEventListener('blur', () => {
            formatNumberInput(inputEl);
            calculateMarketNet(container);
        });

        // Change event (fallback)
        inputEl.addEventListener('change', () => calculateMarketNet(container));

        // Enter key: Blur input (which triggers format/recalculate)
        inputEl.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                inputEl.blur(); 
            }
        });
    }

    // ----------------------------------------------------------------------
    // --- RENDERING & INJECTION (MODIFIED) ---
    // ----------------------------------------------------------------------

    function createInfoContainer(itemName, itemId, marketValue, bestBuyer, upvoteCount, traderId) {
        const container = document.createElement('div');
        container.className = 'bazaar-info-container';
        container.dataset.itemid = itemId;
        if (marketValue) container.dataset.marketValue = marketValue; 
        if (traderId) container.dataset.bestBuyerId = traderId;
        
        const marketText = marketValue ? ` <span style="color:#FFD700; flex-shrink: 0; font-size: 14px;">(MV: ${marketValue})</span>` : '';
        const encodedItemName = encodeURIComponent(itemName);
        const teListingsLink = `https://tornexchange.com/listings?model_name_contains=${encodedItemName}&order_by=&status=`;
        const teListingHTML = `<a href="${teListingsLink}" target="_blank" style="color:#00BFFF; font-size: 13px; text-decoration: none; white-space:nowrap; font-weight: bold;">TE Listings</a>`;
        const itemIdHTML = `<span class="item-id-display">Item #: ${itemId}</span>`;
        
        const teIdLineHTML = `<div class="bazaar-te-id-line" style="margin-bottom: 5px;">${teListingHTML}${itemIdHTML}</div>`;

        // --- Market Calculator HTML ---
        const calculatorHTML = `
            <div class="bazaar-market-calc">
                <label>
                    Market Sell Price:
                    <input type="text" pattern="[0-9]*" inputmode="numeric" placeholder="Enter Price" class="bazaar-calc-input">
                </label>
                <div style="display:flex; justify-content: space-between; align-items: center;">
                    Net Profit (After 5% Fee): <span class="bazaar-net-profit">$0</span>
                </div>
            </div>
        `;
        
        let bestBuyerInfoHTML = '';
        if (bestBuyer && bestBuyer.price) {
            const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
            const priceDisplayHTML = `<span class="price-display-span" style="color:lime; font-weight:bold; white-space:nowrap; font-size: 16px;">${formattedPrice}</span>`;
            if (bestBuyer.trader && traderId) {
                const traderLinkHTML = `<a href="https://www.torn.com/profiles.php?XID=${traderId}" target="_blank" style="color:#1E90FF; text-decoration:none; font-weight:bold; cursor:pointer;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">${bestBuyer.trader}</a>`;
                const upvoteText = upvoteCount ? ` (⭐ ${upvoteCount})` : ''; 
                bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${priceDisplayHTML}</span><span style="flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;">by ${traderLinkHTML}${upvoteText}</span>`;
            } else {
                 bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${formattedPrice}</span>`;
            }
        } else {
             bestBuyerInfoHTML = `<span style="flex-shrink: 0;">No Best Trader Data</span>`;
        }
        const bestBuyerHTML = `<div class="best-buyer-line">${bestBuyerInfoHTML}</div>`;
        
        const filterControlsHTML = `
            <div class="bazaar-control-row" style="display:flex; flex-direction: column; gap:8px; margin: 5px 0 8px 0;">
                <div class="bazaar-control-line">
                    
                    <div class="bazaar-price-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
                        <button class="bazaar-filter-toggle-btn active-filter" data-filter-type="price" style="background:#007bff;">Price</button>
                        <div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
                            <span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="asc" style="color:#00BFFF;">🔼</span>
                            <span class="bazaar-sort-btn" data-sort-by="price" data-sort-dir="desc" style="color:#555;">🔽</span>
                        </div>
                    </div>
                    
                    <div class="bazaar-quantity-controls" style="display: flex; gap: 4px; align-items: center; flex-shrink: 0; white-space: nowrap;">
                        <button class="bazaar-filter-toggle-btn" data-filter-type="quantity">Qty</button>
                        <div style="display: flex; flex-direction: column; height: 28px; justify-content: center; align-items: center; padding-right: 4px;">
                            <span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="asc" style="color:#555;">🔼</span>
                            <span class="bazaar-sort-btn" data-sort-by="quantity" data-sort-dir="desc" style="color:#555;">🔽</span>
                        </div>
                    </div>
                    
                    <div class="bazaar-filter-inputs-group">
                        <div class="bazaar-filter-group-price">
                            <input type="number" placeholder="Min" class="bazaar-filter-input" data-filter-type="minPrice">
                            <input type="number" placeholder="Max" class="bazaar-filter-input" data-filter-type="maxPrice">
                        </div>
                        <div class="bazaar-filter-group-quantity" style="display: none;">
                            <input type="number" placeholder="Min" class="bazaar-filter-input" data-filter-type="minQty">
                            <input type="number" placeholder="Max" class="bazaar-filter-input" data-filter-type="maxQty">
                        </div>
                        <button class="bazaar-apply-btn" style="display: block;">Apply</button>
                    </div>
                </div>
            </div>
        `;

        // --- MODIFIED CONTAINER STRUCTURE ---
        container.innerHTML = `
            <div class="bazaar-info-header">
                <span class="bazaar-title">${itemName}${marketText}</span>
            </div>
            ${bestBuyerHTML}
            ${teIdLineHTML}
            ${filterControlsHTML}
            ${calculatorHTML} <div class="bazaar-card-container"></div>
        `;
        // --- END MODIFIED CONTAINER STRUCTURE ---

        const cardContainer = container.querySelector('.bazaar-card-container');
        if (cardContainer) {
            cardContainer.addEventListener("wheel", e => {
                if (e.deltaY !== 0) { e.preventDefault(); cardContainer.scrollLeft += e.deltaY; }
            });
        }

        addFilterListeners(container, itemId);
        addMarketFeeListener(container);

        return container;
    }

    function sortAndFilterListings(itemId, container) {
        let listings = window._cachedListings[itemId];
        if (!listings) {
            renderNoListingsMessage(container, !window._cachedListings[itemId]);
            return; 
        }
        
        const sortType = window._activeSort.type;
        const sortDir = window._activeSort.dir;
        
        const minPrice = parseFloat(container.querySelector('[data-filter-type="minPrice"]').value) || null;
        const maxPrice = parseFloat(container.querySelector('[data-filter-type="maxPrice"]').value) || null;
        const minQty = parseInt(container.querySelector('[data-filter-type="minQty"]').value) || null;
        const maxQty = parseInt(container.querySelector('[data-filter-type="maxQty"]').value) || null;
        
        let filteredListings = listings.slice().filter(listing => {
            const price = parseFloat(listing.price.toString().replace(/,/g, ''));
            const qty = parseInt(listing.quantity);
            if (minPrice !== null && price < minPrice) return false;
            if (maxPrice !== null && price > maxPrice) return false;
            if (minQty !== null && qty < minQty) return false;
            if (maxQty !== null && qty > maxQty) return false;
            return true;
        });

        filteredListings.sort((a, b) => {
            let primaryValA, primaryValB;
            if (sortType === 'price') {
                primaryValA = parseFloat(a.price.toString().replace(/,/g, ''));
                primaryValB = parseFloat(b.price.toString().replace(/,/g, ''));
            } else { // 'quantity'
                primaryValA = parseInt(a.quantity);
                primaryValB = parseInt(b.quantity);
            }
            let comparison = 0;
            if (sortDir === 'asc') { comparison = primaryValA - primaryValB; } else if (sortDir === 'desc') { comparison = primaryValB - primaryValA; }
            
            if (comparison === 0) {
                 const priceA = parseFloat(a.price.toString().replace(/,/g, ''));
                 const priceB = parseFloat(b.price.toString().replace(/,/g, ''));
                 return priceA - priceB;
            }
            return comparison;
        });
        
        const marketNum = window._marketValueCache?.[itemId] || null;
        renderCards(container, filteredListings, marketNum);
    }
    
    function addFilterListeners(container, itemId) {
        const priceFilterGroup = container.querySelector('.bazaar-filter-group-price');
        const quantityFilterGroup = container.querySelector('.bazaar-filter-group-quantity');
        const sortBtns = container.querySelectorAll('.bazaar-sort-btn'); 
        const filterToggleBtns = container.querySelectorAll('.bazaar-filter-toggle-btn');
        const applyBtn = container.querySelector('.bazaar-apply-btn');
        const defaultColor = '#555';
        const activeColor = '#00BFFF'; 
        const reverseColor = '#dc3545'; 

        const updateSortVisuals = () => {
             filterToggleBtns.forEach(btn => {
                 const filterType = btn.dataset.filterType;
                 if (filterType === window._activeSort.type) { 
                      btn.style.background = '#007bff';
                      btn.classList.add('active-filter');
                 } else {
                      btn.style.background = defaultColor;
                      btn.classList.remove('active-filter');
                 }
             });
             sortBtns.forEach(btn => {
                 const type = btn.dataset.sortBy;
                 const dir = btn.dataset.sortDir;
                 let color = defaultColor;
                 if (type === window._activeSort.type && dir === window._activeSort.dir) {
                     color = dir === 'asc' ? activeColor : reverseColor;
                 } else if (type === window._activeSort.type) {
                     color = '#777'; 
                 }
                 btn.style.color = color;
             });
        };

        sortBtns.forEach(btn => {
            btn.addEventListener('click', (e) => {
                window._activeSort.type = btn.dataset.sortBy;
                window._activeSort.dir = btn.dataset.sortDir;
                updateSortVisuals();
                sortAndFilterListings(itemId, container);
            });
        });

        filterToggleBtns.forEach(btn => {
            btn.addEventListener('click', (e) => {
                const filterType = btn.dataset.filterType;
                priceFilterGroup.style.display = (filterType === 'price') ? 'flex' : 'none';
                quantityFilterGroup.style.display = (filterType === 'quantity') ? 'flex' : 'none';
                applyBtn.style.display = 'block';
                updateSortVisuals();
                sortAndFilterListings(itemId, container);
            });
        });

        applyBtn.addEventListener('click', () => { sortAndFilterListings(itemId, container); });

        container.querySelectorAll('.bazaar-filter-input').forEach(input => {
            input.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') { e.preventDefault(); input.blur(); sortAndFilterListings(itemId, container); }
            });
            input.addEventListener('blur', () => sortAndFilterListings(itemId, container));
        });
        updateSortVisuals();
    }

    function renderCards(container, listings, marketNum){
        const cardContainer=container.querySelector('.bazaar-card-container');
        if(!cardContainer || !listings) return;
        cardContainer.innerHTML='';
        
        if(listings.length===0){
            const msg = document.createElement('div');
            msg.style.cssText='color:#fff;text-align:center;padding:20px;width:100%;';
            msg.innerHTML = "No bazaar listings match the current filters.";
            cardContainer.appendChild(msg);
            return;
        }

        const bestBuyerId = container.dataset.bestBuyerId;
        // Retrieve the calculated net price
        const marketNetPrice = window._currentMarketNetPrice || 0;

        listings.forEach(listing=>{
            const card=document.createElement('div');
            card.className = "bazaar-card";
            const isVisited=window._visitedBazaars.has(listing.player_id);
            const isBestBuyer = bestBuyerId && listing.player_id == bestBuyerId;
            const bazaarLink = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&highlightItem=${listing.item_id}#/`;
            
            if (isBestBuyer) { card.style.cssText += `border: 2px solid #28a745 !important; background: #333 !important;`; }

            const priceNum = parseFloat(listing.price.toString().replace(/,/g, ''));
            const formattedPrice = `$${Math.round(priceNum).toLocaleString()}`;
            
            // --- MARGIN CALCULATION AND DISPLAY ---
            let marginHTML = '';
            
            if (marketNetPrice > 0) {
                // Margin: (Net Sell Price - Bazaar List Price) / Net Sell Price * 100
                const margin = ((marketNetPrice - priceNum) / marketNetPrice) * 100;
                
                let marginClass = 'diff-text-neutral';
                if (margin > 0.1) { marginClass = 'diff-text-negative'; } // Profit (Green)
                else if (margin < -0.1) { marginClass = 'diff-text-positive'; } // Loss (Red)
                
                const sign = margin >= 0 ? '+' : '';

                // Mgn: on left, % on right
                marginHTML = `
                    <div class="margin-info">
                        <b>Mgn:</b> 
                        <span class="${marginClass}">${sign}${margin.toFixed(2)}%</span>
                    </div>`;
            }
            // --- END MARGIN CALCULATION ---

            let diffTextHTML = '';
            if(marketNum){
                const percent = ((priceNum - marketNum)/marketNum*100).toFixed(1);
                let color = 'gold'; 
                if (percent < -0.5) { color = 'limegreen'; } else if (percent > 0.5) { color = 'red'; } 
                const sign = percent > 0 ? '+' : '';
                diffTextHTML = `<span style="font-weight:bold; color:${color}; font-size: 12px; margin-left: auto; white-space: nowrap; flex-shrink: 0;">${sign}${percent}%</span>`;
            }

            const playerLinkStyle = isVisited?'#800080':'#1E90FF';

            // --- CARD STRUCTURE ---
            card.innerHTML=`
                <a href="${bazaarLink}" target="_blank"
                    style="font-weight:bold; color:${playerLinkStyle}; text-decoration:none; cursor:pointer; 
                           overflow: hidden; white-space: nowrap; text-overflow: ellipsis; 
                           line-height: 1.1; padding: 0; margin: 0 0 3px 0; display: block;">
                    ${listing.player_name || 'Unknown'}
                </a>
                <div class="bazaar-card-line-1">
                    ${formattedPrice}
                </div>
                <div class="bazaar-card-line-2">
                    <span style="white-space: nowrap;">Qty: ${listing.quantity}</span>
                    ${diffTextHTML}
                </div>
                <div class="bazaar-card-line-3">
                    ${marginHTML} 
                </div>
            `;
            // --- END CARD STRUCTURE ---
            
            card.addEventListener('click', (e)=>{
                if(e.target.tagName === 'A') return;
                if(listing.player_id){
                    window._visitedBazaars.add(listing.player_id);
                    const nameLink = card.querySelector('a:first-child');
                    if(nameLink) nameLink.style.color='#800080';
                }
                window.open(bazaarLink, '_blank');
            });
            cardContainer.appendChild(card);
        });
    }

    // ----------------------------------------------------------------------
    // --- CORE FETCH LOGIC ---
    // ----------------------------------------------------------------------
    
    function fetchTornExchangeData(itemId, callback) {
        let marketValue = null; 
        let bestBuyer = null;
        let requestsCompleted = 0;
        
        const checkCompletion = () => {
             requestsCompleted++;
             if (requestsCompleted === 2) {
                 if (bestBuyer && bestBuyer.trader) {
                     fetchUpvoteCountAndId(bestBuyer.trader, (count, id) => {
                         callback(marketValue, bestBuyer, count, id);
                     });
                 } else {
                     callback(marketValue, bestBuyer, null, null);
                 }
             }
        };
        
        GM_xmlhttpRequest({
            method: "GET", url: `https://tornexchange.com/api/te_price?item_id=${itemId}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.status === 'success' && data.data && data.data.te_price) { 
                        window._marketValueCache[itemId] = data.data.te_price; 
                        marketValue = `$${Math.round(data.data.te_price).toLocaleString()}`; 
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                checkCompletion();
            },
            onerror: function() { checkCompletion(); }
        });
        
        GM_xmlhttpRequest({
            method: "GET", url: `https://tornexchange.com/api/best_listing?item_id=${itemId}`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data && data.status === 'success' && data.data) {
                        if (data.data.price) { bestBuyer = { price: data.data.price, trader: data.data.trader || null }; }
                    }
                } catch (e) { /* silent fail, as per V 1.0 */ }
                checkCompletion();
            },
            onerror: function() { checkCompletion(); }
        });
    }

    function fetchBazaarListings(itemId, infoContainer) {
        GM_xmlhttpRequest({
            method:"GET",
            url:`https://weav3r.dev/api/marketplace/${itemId}`,
            onload:function(response){
                try{
                    const data = JSON.parse(response.responseText); 
                    const listingsReceived = data.listings ? data.listings.length : 0;
                    
                    if(!data || !data.listings || listingsReceived === 0){
                        renderNoListingsMessage(infoContainer, false);
                        return;
                    }
                    
                    const allListings = data.listings.map(l=>({
                        player_name:l.player_name, player_id:l.player_id, quantity:l.quantity, price:l.price, item_id:l.item_id
                    }));

                    window._cachedListings[itemId] = allListings;
                    sortAndFilterListings(itemId, infoContainer); 

                } catch(e){
                    console.error(`[Bazaar + TE Info PDA Version Error] Failed to process Weaver API response for item ${itemId}:`, e);
                    renderNoListingsMessage(infoContainer, true);
                }
            },
            onerror:function(error){
                console.error(`[Bazaar + TE Info PDA Version Error] GM_xmlhttpRequest failed for item ${itemId}:`, error);
                renderNoListingsMessage(infoContainer, true);
            }
        });
    }

    function updateInfoContainer(wrapper, itemId, itemName) {
        const infoContainer = createInfoContainer(itemName, itemId, null, null, null, null);
        wrapper.insertBefore(infoContainer, wrapper.firstChild);

        fetchTornExchangeData(itemId, (marketValue, bestBuyer, upvoteCount, traderId) => {
            
            let bestBuyerInfoHTML = '';
            if (bestBuyer && bestBuyer.price) {
                const formattedPrice = `$${Math.round(bestBuyer.price).toLocaleString()}`;
                const priceDisplayHTML = `<span class="price-display-span" style="color:lime; font-weight:bold; white-space:nowrap; font-size: 16px;">${formattedPrice}</span>`;
                if (bestBuyer.trader && traderId) {
                    const traderLinkHTML = `<a href="https://www.torn.com/profiles.php?XID=${traderId}" target="_blank" style="color:#1E90FF; text-decoration:none; font-weight:bold; cursor:pointer;" onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';">${bestBuyer.trader}</a>`;
                    const upvoteText = upvoteCount ? ` (⭐ ${upvoteCount})` : ''; 
                    bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${priceDisplayHTML}</span><span style="flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;">by ${traderLinkHTML}${upvoteText}</span>`;
                } else {
                     bestBuyerInfoHTML = `<span style="flex-shrink: 0;">Best Trader: ${formattedPrice}</span>`;
                }
            } else {
                 bestBuyerInfoHTML = `<span style="flex-shrink: 0;">No Best Trader Data</span>`;
            }

            const newBestBuyerHTML = `<div class="best-buyer-line">${bestBuyerInfoHTML}</div>`;
            const oldBestBuyer = infoContainer.querySelector('.best-buyer-line');
            if (oldBestBuyer && oldBestBuyer.parentNode) {
                 const tempDiv = document.createElement('div');
                 tempDiv.innerHTML = newBestBuyerHTML;
                 oldBestBuyer.parentNode.replaceChild(tempDiv.firstChild, oldBestBuyer);
            }
            
            const marketText = marketValue ? ` (MV: ${marketValue})` : '';
            const itemNameEl = infoContainer.querySelector('.bazaar-title');
            if (itemNameEl) {
                itemNameEl.innerHTML = `${itemName}<span style="color:#FFD700; flex-shrink: 0; font-size: 14px;">${marketText}</span>`;
            }

            // Re-render the listings if they are cached to update the Market Value % difference
            if (window._cachedListings[itemId]) {
                sortAndFilterListings(itemId, infoContainer);
            }
        });

        fetchBazaarListings(itemId, infoContainer);
    }

    // ----------------------------------------------------------------------
    // --- MAIN EXECUTION & MONITORS ---
    // ----------------------------------------------------------------------
    
    function processBazaarOrMarketItem(){
        if (!window.location.href.match(/(itemmarket|bazaar|page)\.php/i)) return;
        
        const itemId = extractItemId();
        if (!itemId) return;

        let wrapper = null;
        let itemName = '';
        
        if (isMobileView) {
            const sellerListWrapper = document.querySelector('ul.sellerList__e4C9, ul[class*="sellerList"]');
            if (!sellerListWrapper) return;
            wrapper = sellerListWrapper.parentNode;
            
            const headerEl = document.querySelector('.itemsHeader__ZTO9r .title__ruNCT, [class*="itemsHeader"] [class*="title"]');
            itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
        } else {
            const itemDetails = document.querySelector('[class*="item-details"], [class*="item-info-desc"]');
            if (!itemDetails) return;
            wrapper = itemDetails.closest('.content-wrapper > .content') || itemDetails.parentElement;

            const nameEl = itemDetails.querySelector('h3.item-name, [class*="item-name"], .title');
            if (nameEl) { itemName = nameEl.textContent.trim(); }
        }

        if (!wrapper || !itemName) return;

        const existingContainer = document.querySelector(`.bazaar-info-container`);
        const existingContainerForCurrentItem = document.querySelector(`.bazaar-info-container[data-itemid="${itemId}"]`);

        if (existingContainerForCurrentItem) {
             return;
        }

        if (existingContainer && existingContainer.dataset.itemid !== itemId) {
             console.log(`[Bazaar + TE Info PDA Version] Removing old container for item ${existingContainer.dataset.itemid}.`);
             existingContainer.remove();
        }

        if (!document.querySelector('.bazaar-info-container')) {
             updateInfoContainer(wrapper, itemId, itemName);
        }
    }

    // URL Monitor
    setInterval(() => {
        if (window.location.href !== lastUrl) {
            document.querySelectorAll('.bazaar-info-container').forEach(el => el.remove());
            lastUrl = window.location.href;
            window._cachedListings = {}; 
            window._marketValueCache = {}; 
            window._activeSort = { type: 'price', dir: 'asc' }; 
            window._currentMarketNetPrice = 0; // Reset net price on navigation
        }
    }, 500); 

    // Mutation Observer
    const observer = new MutationObserver(()=>{ 
        processBazaarOrMarketItem(); 
    });

    observer.observe(document.body, {childList:true,subtree:true});
    processBazaarOrMarketItem(); // Initial run on page load
    
})();

// --- Bazaar Page Green Highlight ---
;(() => {
    const params = new URLSearchParams(window.location.search);
    const itemIdToHighlight = params.get("highlightItem");
    if (!itemIdToHighlight) return;
    
    const observer = new MutationObserver(() => {
        const imgs = document.querySelectorAll("img");
        imgs.forEach((img) => {
            if (img.src.includes(`images/items/${itemIdToHighlight}/`)) {
                img.closest("div")?.style.setProperty("outline", "3px solid green", "important");
                img.scrollIntoView({ behavior: "smooth", block: "center" });
            }
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();