← back to Handbag Authentication

public/js/app.js

1100 lines

// API base URL
const API_BASE = window.location.origin;

// State
let currentFilters = {
  brand: '',
  signature: '',
  minPrice: '',
  maxPrice: '',
  type: '',
  dealOnly: false,
  minDeal: 0,
  sort: 'profit'
};

let autoRefreshInterval = null;
let lastListingCount = 0;

// Infinite scroll state
let allListings = [];
let displayedCount = 0;
const ITEMS_PER_PAGE = 20;
let isLoading = false;

// Auto-translate flag
let autoTranslateEnabled = true;

// Initialize
document.addEventListener('DOMContentLoaded', () => {
  loadStats();
  loadListings();
  setupEventListeners();
  setupInfiniteScroll();
  startAutoRefresh();
  initializeConditionGuide();
  
  // Add auto-translate toggle to the page
  addTranslateToggle();
});

// Setup event listeners
function setupEventListeners() {
  document.getElementById('filter-brand').addEventListener('input', debounce(applyFilters, 500));
  document.getElementById('filter-signature').addEventListener('change', applyFilters);
  document.getElementById('filter-type').addEventListener('change', applyFilters);
  document.getElementById('filter-deals-only').addEventListener('change', applyFilters);
  document.getElementById('filter-min-price').addEventListener('input', debounce(applyFilters, 500));
  document.getElementById('filter-max-price').addEventListener('input', debounce(applyFilters, 500));
  document.getElementById('filter-min-deal').addEventListener('input', debounce(applyFilters, 500));
  document.getElementById('btn-refresh').addEventListener('click', () => {
    loadStats();
    loadListings();
  });
  document.getElementById('btn-export').addEventListener('click', exportToCSV);

  const btnCrawl = document.getElementById('btn-crawl');
  if (btnCrawl) {
    btnCrawl.addEventListener('click', triggerCrawl);
  }

  // Sort tabs
  document.querySelectorAll('.sort-tab').forEach(tab => {
    tab.addEventListener('click', () => {
      // Remove active from all tabs
      document.querySelectorAll('.sort-tab').forEach(t => t.classList.remove('active'));
      // Add active to clicked tab
      tab.classList.add('active');
      // Update sort and reload
      currentFilters.sort = tab.getAttribute('data-sort');
      loadListings();
    });
  });
}

// Debounce helper
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Apply filters
function applyFilters() {
  const signatureModel = document.getElementById('filter-signature').value;

  currentFilters = {
    brand: signatureModel ? '' : document.getElementById('filter-brand').value, // Clear brand if signature selected
    signature: signatureModel,
    minPrice: document.getElementById('filter-min-price').value,
    maxPrice: document.getElementById('filter-max-price').value,
    type: document.getElementById('filter-type').value,
    dealOnly: document.getElementById('filter-deals-only').checked,
    minDeal: document.getElementById('filter-min-deal').value || 0,
    sort: currentFilters.sort // Keep current sort from tabs
  };
  loadListings();
}

// Load stats
async function loadStats() {
  try {
    const response = await fetch(`${API_BASE}/api/stats`);
    const stats = await response.json();

    document.getElementById('stat-total').textContent = stats.total_listings.toLocaleString();
    document.getElementById('stat-deals').textContent = stats.total_deals.toLocaleString();
    document.getElementById('stat-avg-deal').textContent = `${stats.avg_deal_percentage}%`;
    document.getElementById('stat-best-deal').textContent = `${stats.best_deal_percentage}%`;
  } catch (error) {
    console.error('Error loading stats:', error);
  }
}

// Load listings
async function loadListings() {
  const loadingEl = document.getElementById('loading');
  const gridEl = document.getElementById('listings-grid');
  const emptyEl = document.getElementById('empty-state');

  loadingEl.style.display = 'block';
  gridEl.innerHTML = '';
  emptyEl.style.display = 'none';

  try {
    const params = new URLSearchParams();

    // If signature model selected, use it; otherwise use brand filter
    if (currentFilters.signature) {
      params.append('model', currentFilters.signature);
    } else if (currentFilters.brand) {
      params.append('brand', currentFilters.brand);
    }

    if (currentFilters.minPrice) params.append('minPrice', currentFilters.minPrice);
    if (currentFilters.maxPrice) params.append('maxPrice', currentFilters.maxPrice);
    if (currentFilters.type) params.append('type', currentFilters.type);
    if (currentFilters.dealOnly) params.append('dealOnly', 'true');
    if (currentFilters.minDeal) params.append('minDeal', currentFilters.minDeal);
    params.append('sort', currentFilters.sort);
    params.append('limit', '1000'); // Get more items for infinite scroll

    const response = await fetch(`${API_BASE}/api/listings?${params}`);
    const data = await response.json();

    loadingEl.style.display = 'none';

    if (data.listings.length === 0) {
      emptyEl.style.display = 'block';
      allListings = [];
      displayedCount = 0;
      return;
    }

    // Store all listings and render first batch
    allListings = data.listings;
    displayedCount = 0;
    renderNextBatch();
  } catch (error) {
    console.error('Error loading listings:', error);
    loadingEl.textContent = 'Error loading listings. Please try again.';
  }
}

// Calculate profit for a listing
function calculateProfit(listing) {
  const estimatedUSPrice = listing.avg_us_price || (listing.price_usd * 1.8);
  const shippingCost = 50;
  const platformFee = estimatedUSPrice * 0.15;
  const authenticationCost = 50;
  const totalCosts = listing.price_usd + shippingCost + platformFee + authenticationCost;
  const grossProfit = estimatedUSPrice - totalCosts;
  const profitMargin = ((grossProfit / totalCosts) * 100);

  return { grossProfit, profitMargin };
}

// Setup infinite scroll
function setupInfiniteScroll() {
  const sentinel = document.createElement('div');
  sentinel.id = 'scroll-sentinel';
  sentinel.style.height = '1px';
  document.querySelector('main').appendChild(sentinel);

  const observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting && !isLoading && displayedCount < allListings.length) {
      renderNextBatch();
    }
  }, { threshold: 0.5 });

  observer.observe(sentinel);
}

// Render next batch of listings
function renderNextBatch() {
  if (isLoading || displayedCount >= allListings.length) return;

  isLoading = true;
  const gridEl = document.getElementById('listings-grid');

  // Sort all listings based on current filter
  let sortedListings = [...allListings];

  if (currentFilters.sort === 'profit') {
    sortedListings.sort((a, b) => {
      const profitA = calculateProfit(a).grossProfit;
      const profitB = calculateProfit(b).grossProfit;
      return profitB - profitA;
    });
  } else if (currentFilters.sort === 'profit_margin') {
    sortedListings.sort((a, b) => {
      const marginA = calculateProfit(a).profitMargin;
      const marginB = calculateProfit(b).profitMargin;
      return marginB - marginA;
    });
  } else if (currentFilters.sort === 'price_low') {
    sortedListings.sort((a, b) => a.price_usd - b.price_usd);
  } else if (currentFilters.sort === 'date') {
    sortedListings.sort((a, b) => new Date(b.crawled_at) - new Date(a.crawled_at));
  } else if (currentFilters.sort === 'brand') {
    sortedListings.sort((a, b) => {
      const brandA = (a.brand || '').toLowerCase();
      const brandB = (b.brand || '').toLowerCase();
      return brandA.localeCompare(brandB);
    });
  }

  // Update allListings with sorted version
  allListings = sortedListings;

  // Get next batch
  const nextBatch = sortedListings.slice(displayedCount, displayedCount + ITEMS_PER_PAGE);

  nextBatch.forEach((listing, index) => {
    const card = createListingCard(listing, displayedCount + index);
    gridEl.appendChild(card);
  });

  displayedCount += nextBatch.length;
  isLoading = false;

  // Show load more indicator
  updateLoadMoreIndicator();
}

// Update load more indicator
function updateLoadMoreIndicator() {
  let indicator = document.getElementById('load-more-indicator');

  if (!indicator) {
    indicator = document.createElement('div');
    indicator.id = 'load-more-indicator';
    indicator.style.cssText = 'text-align: center; padding: 2rem; color: #7f8c8d; font-weight: 600;';
    const sentinel = document.getElementById('scroll-sentinel');
    sentinel.parentNode.insertBefore(indicator, sentinel);
  }

  if (displayedCount < allListings.length) {
    indicator.innerHTML = `
      <div style="display: inline-block; animation: pulse 1.5s ease-in-out infinite;">
        📦 Loading more deals... (${displayedCount} of ${allListings.length})
      </div>
    `;
  } else if (allListings.length > 0) {
    indicator.innerHTML = `✅ All ${allListings.length} listings loaded!`;
  } else {
    indicator.innerHTML = '';
  }
}

// Create listing card
function createListingCard(listing, index) {
  const card = document.createElement('div');
  card.className = 'listing-card';
  card.style.animationDelay = `${index * 0.05}s`;

  // Calculate profit potential with detailed market research logic
  const brandMultipliers = {
    'Hermes': 2.5,  // Hermes bags hold 150%+ value
    'Chanel': 2.2,  // Classic Chanel appreciates
    'Louis Vuitton': 1.9,  // Strong resale market
    'Gucci': 1.7,  // Popular but more supply
    'Dior': 1.8,
    'Prada': 1.6,
    'Fendi': 1.7,
    'Balenciaga': 1.6,
    'Celine': 1.8,
    'Bottega Veneta': 1.7
  };

  const brandKey = Object.keys(brandMultipliers).find(b =>
    listing.brand && listing.brand.toLowerCase().includes(b.toLowerCase())
  );

  const multiplier = brandKey ? brandMultipliers[brandKey] : 1.8;
  const estimatedUSPrice = listing.avg_us_price || (listing.price_usd * multiplier);

  const shippingCost = 50; // Average shipping from Japan via Buyee/FromJapan
  const platformFee = estimatedUSPrice * 0.15; // 15% avg platform commission
  const authenticationCost = 50; // Entrupy or Real Authentication
  const totalCosts = listing.price_usd + shippingCost + platformFee + authenticationCost;
  const grossProfit = estimatedUSPrice - totalCosts;
  const profitMargin = ((grossProfit / totalCosts) * 100).toFixed(0);

  // Add deal class based on profit margin
  if (profitMargin >= 50) {
    card.classList.add('deal-exceptional');
  } else if (profitMargin >= 30) {
    card.classList.add('deal-great');
  } else if (profitMargin >= 20) {
    card.classList.add('deal-good');
  }

  // Profit badge
  let profitBadge = '';
  if (grossProfit > 0 && profitMargin >= 20) {
    const badgeClass = profitMargin >= 50 ? 'badge-exceptional' :
                       profitMargin >= 30 ? 'badge-great' : 'badge-good';
    const emoji = profitMargin >= 50 ? '💰💰💰' :
                  profitMargin >= 30 ? '💰💰' : '💰';
    profitBadge = `<div class="listing-badge ${badgeClass}">${emoji} ${profitMargin}% Profit</div>`;
  }

  // Image
  const imageUrl = listing.thumbnail_url || listing.image_url || 'https://via.placeholder.com/280x250?text=No+Image';

  // Listing type
  const typeIcon = listing.listing_type === 'auction' ? '⚡' : '🛒';
  const typeClass = listing.listing_type === 'auction' ? 'type-auction' : 'type-buy';
  const typeText = listing.listing_type === 'auction' ? 'Auction' : 'Buy Now';

  // Where to sell with detailed platform info and quick-list buttons
  const translatedTitle = translateJapanese(listing.title);
  const translatedBrand = translateJapanese(listing.brand || '');

  const sellPlatforms = [];
  const platformInfo = {
    'Fashionphile': {
      commission: '15-30%',
      speed: 'Fast (24-48h)',
      bestFor: 'Hermes, Chanel, LV',
      url: 'https://www.fashionphile.com/sell-to-us',
      searchUrl: `https://www.fashionphile.com/search?query=${encodeURIComponent(translatedBrand)}`,
      color: '#000'
    },
    'Rebag': {
      commission: '15-25%',
      speed: 'Instant (Quote)',
      bestFor: 'All luxury',
      url: 'https://www.rebag.com/sell',
      searchUrl: `https://www.rebag.com/shop?q=${encodeURIComponent(translatedBrand)}`,
      color: '#FF6B6B'
    },
    'TheRealReal': {
      commission: '15-50%',
      speed: 'Medium (3-7 days)',
      bestFor: 'High-end vintage',
      url: 'https://www.therealreal.com/consign',
      searchUrl: `https://www.therealreal.com/designers/${translatedBrand.toLowerCase().replace(/\s+/g, '-')}`,
      color: '#2E7D32'
    },
    'Vestiaire': {
      commission: '12-25%',
      speed: 'Medium (5-14 days)',
      bestFor: 'European brands',
      url: 'https://www.vestiairecollective.com/sell/',
      searchUrl: `https://www.vestiairecollective.com/search/?q=${encodeURIComponent(translatedBrand)}`,
      color: '#8E44AD'
    },
    'eBay Auth': {
      commission: '13%',
      speed: 'Variable',
      bestFor: 'All brands',
      url: 'https://www.ebay.com/sh/sell',
      searchUrl: `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(translatedBrand + ' bag')}&LH_Sold=1&LH_Complete=1`,
      color: '#E53935'
    },
    'Poshmark': {
      commission: '20%',
      speed: 'Fast',
      bestFor: 'Mid-tier luxury',
      url: 'https://poshmark.com/sell',
      searchUrl: `https://poshmark.com/search?query=${encodeURIComponent(translatedBrand)}&department=Women&type=bags`,
      color: '#721B65'
    }
  };

  if (translatedBrand && ['Hermes', 'Chanel', 'Louis Vuitton'].some(b => translatedBrand.includes(b))) {
    sellPlatforms.push('Fashionphile', 'Rebag', 'TheRealReal');
  } else if (translatedBrand && ['Gucci', 'Prada', 'Dior'].some(b => translatedBrand.includes(b))) {
    sellPlatforms.push('Rebag', 'Vestiaire', 'eBay Auth');
  } else {
    sellPlatforms.push('eBay Auth', 'Poshmark', 'Vestiaire');
  }

  const sellPlatformsHTML = sellPlatforms.length > 0 ? `
    <div class="sell-platforms">
      <div class="sell-header">
        <span class="sell-label">💼 Best Resale Platforms</span>
        <span class="sell-subtitle">Research comps or quick-list this item in seconds</span>
      </div>
      <div class="platform-grid">
        ${sellPlatforms.map(p => {
          const info = platformInfo[p];
          return `
            <div class="platform-card-compact">
              <div class="platform-info">
                <div class="platform-name">${p}</div>
                <div class="platform-meta">
                  <span class="platform-commission">${info.commission} fee</span>
                  <span class="platform-speed">${info.speed}</span>
                </div>
              </div>
              <div class="platform-buttons">
                <button class="btn-platform-research" onclick="window.open('${info.searchUrl}', '_blank'); event.stopPropagation();" title="See sold ${translatedBrand} bags on ${p}">
                  📊 Sold
                </button>
                <button class="btn-platform-list" onclick="quickListItem('${p}', '${escapeHtml(translatedTitle)}', ${estimatedUSPrice}, '${info.url}', '${listing.image_url}'); event.stopPropagation();" title="Quick-list to ${p}">
                  🚀 List
                </button>
              </div>
            </div>
          `;
        }).join('')}
      </div>
      <div class="pricing-logic">
        <strong>💡 Pricing Logic:</strong> ${translatedBrand || 'These'} bags resell at <strong>${multiplier}x</strong> Japan price (${Math.round((multiplier - 1) * 100)}% markup).
        <a href="https://www.google.com/search?q=${encodeURIComponent((translatedBrand || 'luxury') + ' bag sold prices')}" target="_blank" rel="noopener noreferrer" class="research-link">See market data →</a>
      </div>
    </div>
  ` : '';

  // Profit breakdown
  const profitBreakdown = grossProfit > 0 ? `
    <div class="profit-breakdown">
      <div class="profit-header">
        <span class="profit-label">Est. Profit</span>
        <span class="profit-amount ${grossProfit > 200 ? 'profit-high' : 'profit-moderate'}">$${formatNumber(grossProfit)}</span>
      </div>
      <div class="profit-details">
        <div class="profit-row">
          <span>Resale Price:</span>
          <span class="profit-value">$${formatNumber(estimatedUSPrice)}</span>
        </div>
        <div class="profit-row cost">
          <span>Your Cost:</span>
          <span class="profit-value">-$${formatNumber(listing.price_usd)}</span>
        </div>
        <div class="profit-row cost">
          <span>Shipping:</span>
          <span class="profit-value">-$${shippingCost}</span>
        </div>
        <div class="profit-row cost">
          <span>Auth Fee:</span>
          <span class="profit-value">-$${authenticationCost}</span>
        </div>
        <div class="profit-row cost">
          <span>Platform Fee (15%):</span>
          <span class="profit-value">-$${formatNumber(platformFee)}</span>
        </div>
      </div>
    </div>
  ` : '';

  card.innerHTML = `
    ${profitBadge}
    <img src="${imageUrl}" alt="${translatedTitle}" class="listing-image" loading="lazy" onerror="this.src='https://via.placeholder.com/280x250?text=No+Image'">
    <div class="listing-content">
      <div class="listing-brand">${escapeHtml(translatedBrand)}</div>
      <div class="listing-title">${escapeHtml(translatedTitle)}</div>

      <div class="listing-prices">
        <div class="jp-price">
          <span class="currency">¥</span>${formatNumber(listing.price_jpy)}
        </div>
        <div class="usd-price">≈ $${formatNumber(listing.price_usd)}</div>
      </div>

      <!-- DETAILED SPECS -->
      <div class="product-specs">
        ${listing.size ? `<div class="spec-item"><strong>📏 Size:</strong> ${escapeHtml(translateJapanese(listing.size))}</div>` : ''}
        ${listing.color ? `<div class="spec-item"><strong>🎨 Color:</strong> ${escapeHtml(translateJapanese(listing.color))}</div>` : ''}
        ${listing.material ? `<div class="spec-item"><strong>🧵 Material:</strong> ${escapeHtml(translateJapanese(listing.material))}</div>` : ''}
        ${listing.model ? `<div class="spec-item"><strong>👜 Model:</strong> ${escapeHtml(translateJapanese(listing.model))}</div>` : ''}
        ${listing.external_id ? `<div class="spec-item"><strong>🔢 ID:</strong> ${escapeHtml(listing.external_id)}</div>` : ''}
        ${listing.auction_end_date ? `<div class="spec-item"><strong>⏰ Ends:</strong> ${escapeHtml(listing.auction_end_date)}</div>` : ''}
      </div>

      <!-- CONDITION ANALYSIS -->
      ${getConditionAnalysis(listing)}

      ${profitBreakdown}
      ${sellPlatformsHTML}

      <div class="listing-meta">
        <span class="condition-badge">${escapeHtml(translateJapanese(listing.condition || 'Pre-Owned'))}</span>
        <span class="listing-type ${typeClass}">${typeIcon} ${typeText}</span>
      </div>

      ${getListingAge(listing)}

      <div class="listing-source">
        <span>📍 ${escapeHtml(listing.source)}</span>
      </div>

      <div class="action-buttons">
        <button class="btn-detail" onclick="window.location.href='/product/${listing.id}'; event.stopPropagation();" title="View complete arbitrage analysis with SKU matching, price history, and more!">
          📊 Full Analysis
        </button>
        <button class="btn-presell" onclick="presellItem(${listing.id}, '${escapeHtml(translatedTitle)}', ${estimatedUSPrice}, '${listing.product_url}', '${listing.image_url}'); event.stopPropagation();" title="List this for sale BEFORE purchasing - lock in profit!">
          ⚡ Presell Strategy
        </button>
        <button class="btn-view" onclick="window.open('${listing.product_url}', '_blank'); event.stopPropagation();">View Japan Listing →</button>
      </div>
    </div>
  `;

  // Make entire card clickable to product detail page
  card.style.cursor = 'pointer';
  card.addEventListener('click', (e) => {
    // Don't navigate if clicking on buttons
    if (!e.target.closest('button')) {
      window.location.href = `/product/${listing.id}`;
    }
  });

  return card;
}

// Export to CSV
async function exportToCSV() {
  const params = new URLSearchParams();
  if (currentFilters.dealOnly) params.append('dealOnly', 'true');
  if (currentFilters.minDeal) params.append('minDeal', currentFilters.minDeal);

  window.open(`${API_BASE}/api/export/csv?${params}`, '_blank');
}

// Trigger manual crawl
async function triggerCrawl() {
  const btn = document.getElementById('btn-crawl');
  btn.disabled = true;
  btn.textContent = 'Crawling...';

  try {
    await fetch(`${API_BASE}/api/crawl`, { method: 'POST' });
    alert('Crawl started! This will take several minutes. Refresh the page in 5-10 minutes to see new results.');
  } catch (error) {
    console.error('Error starting crawl:', error);
    alert('Error starting crawl. Please try again.');
  } finally {
    btn.disabled = false;
    btn.textContent = 'Start Crawl';
  }
}

// Utility functions
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function formatNumber(num) {
  if (!num) return '0';
  return Math.round(num).toLocaleString();
}

// Initialize Condition Guide Modal
function initializeConditionGuide() {
  if (!window.japaneseConditionGuide) return;

  const guide = window.japaneseConditionGuide;

  // Populate condition words
  const wordsHtml = Object.entries(guide.conditionWords).map(([jp, info]) => `
    <div class="condition-item ${info.severity}">
      <strong>${jp}</strong> (${info.romaji}) → ${info.english}
      <span class="impact-badge ${info.severity}">${info.impact}</span>
    </div>
  `).join('');
  document.getElementById('condition-words-list').innerHTML = wordsHtml;

  // Populate grades
  const gradesHtml = Object.entries(guide.grades).map(([code, info]) => `
    <div class="grade-item">
      <strong>${code}</strong>: ${info.name} - ${info.description}
      <span class="value-badge">${(info.valueMultiplier * 100).toFixed(0)}% of retail</span>
    </div>
  `).join('');
  document.getElementById('condition-grades-list').innerHTML = gradesHtml;

  // Populate safe phrases
  const safeHtml = Object.entries(guide.safePhrases).map(([jp, en]) => `
    <div class="phrase-item safe"><strong>${jp}</strong> → ${en}</div>
  `).join('');
  document.getElementById('safe-phrases-list').innerHTML = safeHtml;

  // Populate caution phrases
  const cautionHtml = Object.entries(guide.cautionPhrases).map(([jp, en]) => `
    <div class="phrase-item caution"><strong>${jp}</strong> → ${en}</div>
  `).join('');
  document.getElementById('caution-phrases-list').innerHTML = cautionHtml;

  // Populate photo checklist
  const checklistHtml = guide.photoChecklist.map(item => `
    <div class="checklist-item">${item}</div>
  `).join('');
  document.getElementById('photo-checklist-list').innerHTML = checklistHtml;
}

function openConditionGuide() {
  document.getElementById('condition-guide-modal').style.display = 'flex';
}

function closeConditionGuide() {
  document.getElementById('condition-guide-modal').style.display = 'none';
}

// Analyze listing condition using Japanese guide
function getConditionAnalysis(listing) {
  if (!window.japaneseConditionGuide) return '';

  const analysis = window.japaneseConditionGuide.analyzeTitle(listing.title);

  if (!analysis.hasIssues) return '';

  let html = '<div class="condition-warnings';

  // Check if it's safe or caution
  const isSafe = analysis.warnings.some(w => w.type === 'safe') && analysis.issues.length === 0;
  if (isSafe) {
    html += ' condition-safe"><div class="warning-title">✅ Condition Notes</div>';
  } else {
    html += '"><div class="warning-title">⚠️ Condition Issues Detected</div>';
  }

  // Show issues
  if (analysis.issues.length > 0) {
    analysis.issues.forEach(issue => {
      html += `<div class="warning-item">• <strong>${issue.term}</strong> (${issue.english}) - ${issue.impact}</div>`;
    });

    if (analysis.estimatedPriceAdjustment < 0) {
      html += `<div class="warning-item" style="margin-top: 0.5rem; font-weight: 700;">💰 Est. price adjustment: ${analysis.estimatedPriceAdjustment}%</div>`;
    }
  }

  // Show warnings
  if (analysis.warnings.length > 0) {
    analysis.warnings.forEach(warning => {
      html += `<div class="warning-item">• ${warning.text}</div>`;
    });
  }

  html += '</div>';
  return html;
}

// Simple Japanese to English translation for common terms
function translateJapanese(text) {
  if (!text || !autoTranslateEnabled) return text;

  const translations = {
    // Brands
    'グッチ': 'Gucci',
    'ルイヴィトン': 'Louis Vuitton',
    'ルイ・ヴィトン': 'Louis Vuitton',
    'シャネル': 'Chanel',
    'エルメス': 'Hermes',
    'プラダ': 'Prada',
    'ディオール': 'Dior',
    'クリスチャンディオール': 'Christian Dior',
    'フェンディ': 'Fendi',
    'バレンシアガ': 'Balenciaga',
    'セリーヌ': 'Celine',
    'ボッテガヴェネタ': 'Bottega Veneta',
    'ボッテガ': 'Bottega',
    'サンローラン': 'Saint Laurent',
    'イヴサンローラン': 'Yves Saint Laurent',
    'ロエベ': 'Loewe',
    'バーバリー': 'Burberry',
    'コーチ': 'Coach',
    'マイケルコース': 'Michael Kors',
    'トリーバーチ': 'Tory Burch',
    'ケイトスペード': 'Kate Spade',
    'フルラ': 'Furla',
    'マークジェイコブス': 'Marc Jacobs',
    'ジバンシィ': 'Givenchy',
    'ヴァレンティノ': 'Valentino',
    'ミュウミュウ': 'Miu Miu',
    
    // Bag Models
    'バーキン': 'Birkin',
    'ケリー': 'Kelly',
    'コンスタンス': 'Constance',
    'ガーデンパーティ': 'Garden Party',
    'ピコタン': 'Picotan',
    'ボリード': 'Bolide',
    'エヴリン': 'Evelyne',
    'リンディ': 'Lindy',
    'スピーディ': 'Speedy',
    'ネヴァーフル': 'Neverfull',
    'アルマ': 'Alma',
    'カプシーヌ': 'Capucines',
    'ツイスト': 'Twist',
    'モノグラム': 'Monogram',
    'ダミエ': 'Damier',
    'エピ': 'Epi',
    'マトラッセ': 'Matelasse',
    'チェーンショルダー': 'Chain Shoulder',
    'フラップバッグ': 'Flap Bag',
    'クラシックフラップ': 'Classic Flap',
    'ボーイシャネル': 'Boy Chanel',
    'レディディオール': 'Lady Dior',
    'サドルバッグ': 'Saddle Bag',
    'ブックトート': 'Book Tote',
    
    // Common terms
    'バッグ': 'Bag',
    'ハンドバッグ': 'Handbag',
    'ショルダーバッグ': 'Shoulder Bag',
    'トートバッグ': 'Tote Bag',
    'クラッチバッグ': 'Clutch Bag',
    'ポーチ': 'Pouch',
    '財布': 'Wallet',
    '長財布': 'Long Wallet',
    '二つ折り財布': 'Bifold Wallet',
    'カードケース': 'Card Case',
    'キーケース': 'Key Case',
    
    // Conditions
    '美品': 'Excellent',
    '極美品': 'Near Mint',
    '良品': 'Good',
    '中古': 'Used',
    '新品': 'New',
    '新品同様': 'Like New',
    '未使用': 'Unused',
    '未使用品': 'Unused Item',
    '展示品': 'Display Item',
    '訳あり': 'With Issues',
    
    // Authenticity
    '正規品': 'Authentic',
    '本物': 'Genuine',
    '鑑定済み': 'Authenticated',
    '真贋鑑定済': 'Authenticity Verified',
    
    // Accessories
    '送料無料': 'Free Shipping',
    '送料込み': 'Shipping Included',
    '即決': 'Buy It Now',
    '即決価格': 'Buy It Now Price',
    'レア': 'Rare',
    'ヴィンテージ': 'Vintage',
    'ビンテージ': 'Vintage',
    '限定': 'Limited',
    '限定品': 'Limited Edition',
    '保証書付': 'With Certificate',
    '保証書': 'Certificate',
    '箱付き': 'With Box',
    '箱付': 'With Box',
    '付属品完備': 'Complete Set',
    '付属品': 'Accessories',
    '保存袋': 'Dust Bag',
    'ギャランティカード': 'Guarantee Card',
    '購入証明': 'Proof of Purchase',
    
    // Colors
    '黒': 'Black',
    'ブラック': 'Black',
    '白': 'White',
    'ホワイト': 'White',
    '茶': 'Brown',
    'ブラウン': 'Brown',
    'ベージュ': 'Beige',
    '赤': 'Red',
    'レッド': 'Red',
    '青': 'Blue',
    'ブルー': 'Blue',
    'ネイビー': 'Navy',
    '緑': 'Green',
    'グリーン': 'Green',
    'ピンク': 'Pink',
    '黄': 'Yellow',
    'イエロー': 'Yellow',
    'オレンジ': 'Orange',
    'パープル': 'Purple',
    '紫': 'Purple',
    'グレー': 'Gray',
    '金': 'Gold',
    'ゴールド': 'Gold',
    '銀': 'Silver',
    'シルバー': 'Silver',
    
    // Materials
    'レザー': 'Leather',
    '革': 'Leather',
    '本革': 'Genuine Leather',
    'キャンバス': 'Canvas',
    'ナイロン': 'Nylon',
    'スエード': 'Suede',
    'エナメル': 'Patent',
    'パテントレザー': 'Patent Leather',
    'クロコダイル': 'Crocodile',
    'アリゲーター': 'Alligator',
    'オーストリッチ': 'Ostrich',
    'パイソン': 'Python',
    
    // Sizes
    '小': 'Small',
    '中': 'Medium',
    '大': 'Large',
    'ミニ': 'Mini',
    'プチ': 'Petit',
    'PM': 'PM',
    'MM': 'MM',
    'GM': 'GM',
    
    // Time
    '即日発送': 'Same Day Shipping',
    '翌日発送': 'Next Day Shipping'
  };

  let translated = text;
  for (const [jp, en] of Object.entries(translations)) {
    translated = translated.replace(new RegExp(jp, 'gi'), en);
  }

  return translated;
}

// Add translate toggle button to the page
function addTranslateToggle() {
  const toggleContainer = document.createElement('div');
  toggleContainer.className = 'translate-toggle-container';
  toggleContainer.innerHTML = `
    <label class="translate-toggle">
      <input type="checkbox" id="auto-translate-toggle" ${autoTranslateEnabled ? 'checked' : ''}>
      <span class="toggle-slider"></span>
      <span class="toggle-label">🌐 Auto-Translate to English</span>
    </label>
  `;
  
  // Insert after the filters section
  const filtersSection = document.querySelector('.filters');
  if (filtersSection) {
    filtersSection.parentNode.insertBefore(toggleContainer, filtersSection.nextSibling);
  }
  
  // Add event listener
  document.getElementById('auto-translate-toggle').addEventListener('change', (e) => {
    autoTranslateEnabled = e.target.checked;
    localStorage.setItem('autoTranslateEnabled', autoTranslateEnabled);
    loadListings(); // Reload listings with/without translation
  });
  
  // Load saved preference
  const saved = localStorage.getItem('autoTranslateEnabled');
  if (saved !== null) {
    autoTranslateEnabled = saved === 'true';
    document.getElementById('auto-translate-toggle').checked = autoTranslateEnabled;
  }
}

// Calculate listing age and negotiation potential
function getListingAge(listing) {
  if (!listing.crawled_at) return '';

  const crawledDate = new Date(listing.crawled_at);
  const now = new Date();
  const daysOld = Math.floor((now - crawledDate) / (1000 * 60 * 60 * 24));
  const hoursOld = Math.floor((now - crawledDate) / (1000 * 60 * 60));

  let ageText = '';
  let ageClass = '';
  let negotiationTip = '';

  if (daysOld === 0) {
    if (hoursOld === 0) {
      ageText = '🆕 Listed just now';
      ageClass = 'age-fresh';
      negotiationTip = 'Brand new listing! Act fast before others see it.';
    } else if (hoursOld === 1) {
      ageText = '🆕 Listed 1 hour ago';
      ageClass = 'age-fresh';
      negotiationTip = 'Very fresh listing! Seller may be motivated.';
    } else {
      ageText = `🆕 Listed ${hoursOld} hours ago`;
      ageClass = 'age-fresh';
      negotiationTip = 'Fresh listing! Good time to make an offer.';
    }
  } else if (daysOld === 1) {
    ageText = '📅 Listed 1 day ago';
    ageClass = 'age-recent';
    negotiationTip = 'Recent listing. Seller still evaluating offers.';
  } else if (daysOld <= 7) {
    ageText = `📅 Listed ${daysOld} days ago`;
    ageClass = 'age-recent';
    negotiationTip = 'Listed for a week. Consider making an offer.';
  } else if (daysOld <= 30) {
    ageText = `⏰ Listed ${daysOld} days ago`;
    ageClass = 'age-stale';
    negotiationTip = `${daysOld} days on market. Seller likely motivated - try 10-15% below asking!`;
  } else if (daysOld <= 60) {
    ageText = `⚠️ Listed ${daysOld} days ago`;
    ageClass = 'age-old';
    negotiationTip = `Over ${daysOld} days! High negotiation potential - try 20-25% below asking price.`;
  } else {
    ageText = `🔥 Listed ${daysOld} days ago`;
    ageClass = 'age-very-old';
    negotiationTip = `${daysOld} days on market! Seller very motivated - try 30%+ discount or best offer!`;
  }

  return `
    <div class="listing-age ${ageClass}">
      <div class="age-header">
        <span class="age-text">${ageText}</span>
      </div>
      <div class="negotiation-tip">
        💡 <strong>Negotiation Strategy:</strong> ${negotiationTip}
      </div>
    </div>
  `;
}

// PRESELL Strategy - List item for sale BEFORE purchasing from Japan
function presellItem(listingId, title, estimatedPrice, japanUrl, imageUrl) {
  const presellData = {
    listingId,
    title,
    estimatedPrice,
    japanUrl,
    imageUrl,
    timestamp: Date.now()
  };

  // Save to presell queue
  let presellQueue = JSON.parse(localStorage.getItem('luxarbPresellQueue') || '[]');
  presellQueue.push(presellData);
  localStorage.setItem('luxarbPresellQueue', JSON.stringify(presellQueue));

  const modal = `
    <div class="presell-modal" id="presell-modal">
      <div class="presell-content">
        <div class="presell-header">
          <h2>⚡ Presell Strategy Activated</h2>
          <button onclick="document.getElementById('presell-modal').remove();" class="close-btn">×</button>
        </div>

        <div class="presell-body">
          <h3>🎯 How This Works:</h3>
          <ol class="presell-steps">
            <li><strong>List NOW on your platform</strong> (eBay, Poshmark, etc.) at $${Math.round(estimatedPrice)}</li>
            <li><strong>Set handling time to 5-7 business days</strong> (gives you time to purchase & ship from Japan)</li>
            <li><strong>When item sells</strong> → Purchase from Japan immediately</li>
            <li><strong>Ship directly</strong> from Japan to buyer using Buyee/FromJapan forwarding</li>
            <li><strong>Pocket the profit</strong> with ZERO inventory risk!</li>
          </ol>

          <div class="presell-risks">
            <h4>⚠️ Important Notes:</h4>
            <ul>
              <li>Ensure Japan listing is still available before your listing sells</li>
              <li>Factor in 7-10 day shipping from Japan</li>
              <li>Have backup plan if Japan item sells out</li>
              <li>Only presell if Japan listing is "Buy Now" (not auction)</li>
            </ul>
          </div>

          <div class="presell-templates">
            <h4>📋 Copy Listing Description:</h4>
            <textarea readonly onclick="this.select();" class="presell-textarea">
${title}

✅ Authentic Designer Bag
📦 Ships from USA (7-10 business days)
🔐 100% Authentic - Sourced from Japan
📸 Photos are of actual item
💯 Money-back guarantee if not authentic

Item will be shipped directly from our trusted Japan supplier. All items are professionally authenticated before shipping. Ships within 5-7 business days of purchase.
            </textarea>
            <button onclick="navigator.clipboard.writeText(this.previousElementSibling.value); alert('Copied to clipboard!');" class="btn-copy">📋 Copy to Clipboard</button>
          </div>

          <div class="presell-actions">
            <button onclick="window.open('${japanUrl}', '_blank');" class="btn-presell-action">🇯🇵 Keep Japan Tab Open</button>
            <button onclick="window.open('https://www.ebay.com/sl/sell', '_blank');" class="btn-presell-action">📤 List on eBay</button>
            <button onclick="window.open('https://poshmark.com/sell', '_blank');" class="btn-presell-action">📤 List on Poshmark</button>
          </div>
        </div>
      </div>
    </div>
  `;

  document.body.insertAdjacentHTML('beforeend', modal);
}

// Quick-list function to auto-draft listings on resale platforms
function quickListItem(platform, title, price, platformUrl, imageUrl) {
  // Create a pre-filled listing template
  const listingData = {
    title: title,
    price: Math.round(price),
    description: `Authentic ${title} from Japan. Ships from USA. All sales final. Authentication available.`,
    images: [imageUrl],
    category: 'Luxury Handbags',
    condition: 'Pre-Owned'
  };

  // Store in localStorage for cross-tab communication
  localStorage.setItem('luxarbQuickList', JSON.stringify({
    platform,
    data: listingData,
    timestamp: Date.now()
  }));

  // Open platform in new tab
  window.open(platformUrl, '_blank');

  // Show confirmation
  alert(`✅ Listing template saved!\n\n📋 Title: ${title}\n💵 Suggested Price: $${Math.round(price)}\n\nThe ${platform} listing page will open. Paste the details from your clipboard or use our browser extension for auto-fill!`);

  // Copy to clipboard
  const clipboardText = `Title: ${title}\nPrice: $${Math.round(price)}\nDescription: ${listingData.description}`;
  navigator.clipboard.writeText(clipboardText).catch(() => {});
}

// Auto-refresh functionality
function startAutoRefresh() {
  // Show live banner
  const banner = document.getElementById('live-banner');
  banner.style.display = 'block';

  // Refresh every 5 seconds to show new listings as they come in
  autoRefreshInterval = setInterval(async () => {
    try {
      const params = new URLSearchParams();
      if (currentFilters.brand) params.append('brand', currentFilters.brand);
      if (currentFilters.minPrice) params.append('minPrice', currentFilters.minPrice);
      if (currentFilters.maxPrice) params.append('maxPrice', currentFilters.maxPrice);
      if (currentFilters.type) params.append('type', currentFilters.type);
      if (currentFilters.dealOnly) params.append('dealOnly', 'true');
      if (currentFilters.minDeal) params.append('minDeal', currentFilters.minDeal);
      params.append('sort', currentFilters.sort);

      const response = await fetch(`${API_BASE}/api/listings?${params}`);
      const data = await response.json();

      // Only update if count changed
      if (data.listings.length !== lastListingCount) {
        const newCount = data.listings.length;
        const addedCount = newCount - lastListingCount;

        if (addedCount > 0) {
          banner.innerHTML = `✨ ${addedCount} new product${addedCount > 1 ? 's' : ''} added! Total: ${newCount}`;
          banner.style.background = 'linear-gradient(135deg, #ffa502 0%, #ff6348 100%)';

          // Flash effect
          setTimeout(() => {
            banner.style.background = 'linear-gradient(135deg, #2ed573 0%, #26de81 100%)';
            banner.innerHTML = '🔄 Auto-refreshing every 5 seconds - New products appear as they\'re crawled!';
          }, 3000);
        }

        lastListingCount = newCount;
        loadStats();

        const gridEl = document.getElementById('listings-grid');
        const emptyEl = document.getElementById('empty-state');
        const loadingEl = document.getElementById('loading');

        loadingEl.style.display = 'none';

        if (data.listings.length === 0) {
          emptyEl.style.display = 'block';
          gridEl.innerHTML = '';
          allListings = [];
          displayedCount = 0;
        } else {
          emptyEl.style.display = 'none';
          gridEl.innerHTML = '';
          allListings = data.listings;
          displayedCount = 0;
          renderNextBatch();
        }
      }
    } catch (error) {
      console.error('Auto-refresh error:', error);
    }
  }, 5000); // Refresh every 5 seconds
}