Source: detailsModal.js

// DETAILS MODAL FUNCTIONS WITH PIE CHART INTEGRATION
// =============================================================================

/** @type {string} Current pie chart metric — purchase | melt | retail | gainLoss */
let detailsChartMetric = 'purchase';

/** @type {ResizeObserver|null} Active ResizeObserver for chart resize handling */
let detailsResizeObserver = null;

/**
 * Calculates breakdown data for specified metal by type and location
 * RENAMED from calculateBreakdownData to avoid 403 errors
 * 
 * @param {string} metal - Metal type to calculate ('Silver', 'Gold', 'Platinum', or 'Palladium')
 * @returns {Object} Breakdown data organized by type and location
 */
const getBreakdownData = (metal) => {
  const metalItems = inventory.filter(item => item.metal === metal);
  const currentSpot = spotPrices[metal.toLowerCase()] || 0;

  const typeBreakdown = {};
  const locationBreakdown = {};

  const initBucket = () => ({
    count: 0,
    weight: 0,
    purchase: 0,
    melt: 0,
    retail: 0,
    gainLoss: 0
  });

  metalItems.forEach(item => {
    const qty = Number(item.qty) || 1;
    const weight = parseFloat(item.weight) || 0;
    const weightOz = (item.weightUnit === 'gb') ? weight * GB_TO_OZT : weight;
    const itemWeight = qty * weightOz;
    const purchasePrice = parseFloat(item.price) || 0;
    const purchaseTotal = qty * purchasePrice;
    const purity = parseFloat(item.purity) || 1.0;
    const meltValue = itemWeight * currentSpot * purity;
    const gbDenomPrice = (typeof getGoldbackRetailPrice === 'function') ? getGoldbackRetailPrice(item) : null;
    const rawMarket = parseFloat(item.marketValue) || 0;
    const isManualRetail = !gbDenomPrice && rawMarket > 0;
    const retailTotal = gbDenomPrice   ? gbDenomPrice * qty
                      : isManualRetail ? rawMarket * qty
                      : meltValue;
    const gainLoss = retailTotal - purchaseTotal;

    // Type breakdown
    if (!typeBreakdown[item.type]) typeBreakdown[item.type] = initBucket();
    typeBreakdown[item.type].count += qty;
    typeBreakdown[item.type].weight += itemWeight;
    typeBreakdown[item.type].purchase += purchaseTotal;
    typeBreakdown[item.type].melt += meltValue;
    typeBreakdown[item.type].retail += retailTotal;
    typeBreakdown[item.type].gainLoss += gainLoss;

    // Location breakdown
    const loc = item.purchaseLocation || 'Unknown';
    if (!locationBreakdown[loc]) locationBreakdown[loc] = initBucket();
    locationBreakdown[loc].count += qty;
    locationBreakdown[loc].weight += itemWeight;
    locationBreakdown[loc].purchase += purchaseTotal;
    locationBreakdown[loc].melt += meltValue;
    locationBreakdown[loc].retail += retailTotal;
    locationBreakdown[loc].gainLoss += gainLoss;
  });

  return { typeBreakdown, locationBreakdown };
};

/**
 * Calculates breakdown data across all metals — by metal and by location
 * Used when clicking the "All Metals" summary card
 *
 * @returns {Object} Breakdown data organized by metal and location
 */
const getAllMetalsBreakdownData = () => {
  const metalBreakdown = {};
  const locationBreakdown = {};

  const initBucket = () => ({
    count: 0,
    weight: 0,
    purchase: 0,
    melt: 0,
    retail: 0,
    gainLoss: 0
  });

  inventory.forEach(item => {
    const qty = Number(item.qty) || 1;
    const weight = parseFloat(item.weight) || 0;
    const weightOz = (item.weightUnit === 'gb') ? weight * GB_TO_OZT : weight;
    const itemWeight = qty * weightOz;
    const purchasePrice = parseFloat(item.price) || 0;
    const purchaseTotal = qty * purchasePrice;
    const currentSpot = spotPrices[item.metal.toLowerCase()] || 0;
    const purity = parseFloat(item.purity) || 1.0;
    const meltValue = itemWeight * currentSpot * purity;
    const gbDenomPrice = (typeof getGoldbackRetailPrice === 'function') ? getGoldbackRetailPrice(item) : null;
    const rawMv2 = parseFloat(item.marketValue) || 0;
    const isManualRetail = !gbDenomPrice && rawMv2 > 0;
    const retailTotal = gbDenomPrice   ? gbDenomPrice * qty
                      : isManualRetail ? rawMv2 * qty
                      : meltValue;
    const gainLoss = retailTotal - purchaseTotal;

    // Metal breakdown
    const metal = item.metal || 'Unknown';
    if (!metalBreakdown[metal]) metalBreakdown[metal] = initBucket();
    metalBreakdown[metal].count += qty;
    metalBreakdown[metal].weight += itemWeight;
    metalBreakdown[metal].purchase += purchaseTotal;
    metalBreakdown[metal].melt += meltValue;
    metalBreakdown[metal].retail += retailTotal;
    metalBreakdown[metal].gainLoss += gainLoss;

    // Location breakdown
    const loc = item.purchaseLocation || 'Unknown';
    if (!locationBreakdown[loc]) locationBreakdown[loc] = initBucket();
    locationBreakdown[loc].count += qty;
    locationBreakdown[loc].weight += itemWeight;
    locationBreakdown[loc].purchase += purchaseTotal;
    locationBreakdown[loc].melt += meltValue;
    locationBreakdown[loc].retail += retailTotal;
    locationBreakdown[loc].gainLoss += gainLoss;
  });

  return { metalBreakdown, locationBreakdown };
};

/**
 * Creates breakdown DOM elements for display
 * CHANGED from renderBreakdownHTML to use DOM methods instead of innerHTML
 * 
 * @param {Object} breakdown - Breakdown data object
 * @returns {DocumentFragment} DOM fragment containing the breakdown elements
 */
const createBreakdownElements = (breakdown, colorMap = {}) => {
  const container = document.createDocumentFragment();

  if (Object.keys(breakdown).length === 0) {
    const item = document.createElement('div');
    item.className = 'breakdown-item';
    const label = document.createElement('span');
    label.className = 'breakdown-label';
    label.textContent = 'No data available';
    item.appendChild(label);
    container.appendChild(item);
    return container;
  }

  // Sort by purchase value descending
  const sortedEntries = Object.entries(breakdown).sort((a, b) => b[1].purchase - a[1].purchase);

  sortedEntries.forEach(([key, data]) => {
    const item = document.createElement('div');
    item.className = 'breakdown-item';

    // Header row: name + count/weight
    const header = document.createElement('div');
    header.className = 'breakdown-header';

    // Color dot matching pie chart segment
    if (colorMap[key]) {
      const dot = document.createElement('span');
      dot.className = 'breakdown-color-dot';
      dot.style.backgroundColor = colorMap[key];
      header.appendChild(dot);
    }

    const label = document.createElement('span');
    label.className = 'breakdown-label';
    label.textContent = key;

    const meta = document.createElement('span');
    meta.className = 'breakdown-meta';
    meta.textContent = `${data.count} items \u00B7 ${data.weight.toFixed(2)} oz`;

    header.appendChild(label);
    header.appendChild(meta);

    // 2x2 financial grid
    const grid = document.createElement('div');
    grid.className = 'breakdown-grid';

    const cells = [
      { label: 'Purchase', value: formatCurrency(data.purchase), cls: 'breakdown-purchase' },
      { label: 'Melt', value: formatCurrency(data.melt), cls: 'breakdown-melt' },
      { label: 'Retail', value: formatCurrency(data.retail), cls: 'breakdown-retail' },
      { label: 'Gain/Loss', value: formatCurrency(Math.abs(data.gainLoss)), cls: data.gainLoss >= 0 ? 'breakdown-gain' : 'breakdown-loss' }
    ];

    cells.forEach(cell => {
      const el = document.createElement('div');
      el.className = `breakdown-cell ${cell.cls}`;
      const lbl = document.createElement('span');
      lbl.className = 'breakdown-cell-label';
      lbl.textContent = cell.label;
      const val = document.createElement('span');
      val.className = 'breakdown-cell-value';
      val.textContent = cell.label === 'Gain/Loss'
        ? (data.gainLoss > 0 ? '+' : data.gainLoss < 0 ? '-' : '') + cell.value
        : cell.value;
      el.appendChild(lbl);
      el.appendChild(val);
      grid.appendChild(el);
    });

    item.appendChild(header);
    item.appendChild(grid);
    container.appendChild(item);
  });

  return container;
};

/**
 * Shows the details modal for specified metal with pie charts
 * 
 * @param {string} metal - Metal type to show details for
 */
const showDetailsModal = (metal) => {
  const isAll = metal === 'All';
  const typePanelTitle = document.getElementById('typePanelTitle');
  const locationPanelTitle = document.getElementById('locationPanelTitle');

  // Update modal title and panel labels
  elements.detailsModalTitle.textContent = isAll
    ? 'All Metals — Portfolio Breakdown'
    : `${metal} Detailed Breakdown`;
  if (typePanelTitle) typePanelTitle.textContent = isAll ? 'Breakdown by Metal' : 'Breakdown by Type';
  if (locationPanelTitle) locationPanelTitle.textContent = 'Breakdown by Purchase Location';

  // Clear existing content
  elements.typeBreakdown.textContent = '';
  elements.locationBreakdown.textContent = '';

  // Destroy existing charts
  destroyCharts();

  // Reset metric to default for each modal open
  detailsChartMetric = 'purchase';

  // Get breakdown data — different shape for "All" vs single metal
  let leftBreakdown, rightBreakdown;
  if (isAll) {
    const allData = getAllMetalsBreakdownData();
    leftBreakdown = allData.metalBreakdown;
    rightBreakdown = allData.locationBreakdown;
  } else {
    const metalData = getBreakdownData(metal);
    leftBreakdown = metalData.typeBreakdown;
    rightBreakdown = metalData.locationBreakdown;
  }

  // Charts hidden via CSS on mobile — skip creation entirely (STACK-70)
  const isMobile = window.innerWidth <= 768;

  // Helper: render charts with current metric
  const renderCharts = () => {
    if (isMobile) return;
    destroyCharts();
    if (Object.keys(leftBreakdown).length > 0) {
      chartInstances.typeChart = createPieChart(
        elements.typeChart,
        leftBreakdown,
        isAll ? 'Metal Breakdown' : 'Type Breakdown',
        detailsChartMetric
      );
    }
    if (Object.keys(rightBreakdown).length > 0) {
      chartInstances.locationChart = createPieChart(
        elements.locationChart,
        rightBreakdown,
        'Location Breakdown',
        detailsChartMetric
      );
    }
  };

  // Create metric toggle bar before the charts grid
  const detailsGrid = elements.detailsModal.querySelector('.details-grid');
  let existingToggle = elements.detailsModal.querySelector('.chart-metric-toggle');
  if (existingToggle) existingToggle.remove();

  if (!isMobile) {
    const toggleBar = document.createElement('div');
    toggleBar.className = 'chart-metric-toggle';
    const metrics = [
      { key: 'purchase', label: 'Purchase' },
      { key: 'melt',     label: 'Melt' },
      { key: 'retail',   label: 'Retail' },
      { key: 'gainLoss', label: 'Gain/Loss' }
    ];
    metrics.forEach(m => {
      const btn = document.createElement('button');
      btn.className = 'chart-metric-btn' + (m.key === detailsChartMetric ? ' active' : '');
      btn.textContent = m.label;
      btn.type = 'button';
      btn.addEventListener('click', () => {
        detailsChartMetric = m.key;
        toggleBar.querySelectorAll('.chart-metric-btn').forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        renderCharts();
      });
      toggleBar.appendChild(btn);
    });

    if (detailsGrid) {
      detailsGrid.parentNode.insertBefore(toggleBar, detailsGrid);
    }
  }

  // Initial chart render (no-op on mobile)
  renderCharts();

  // Build color maps matching pie chart segment order (by insertion order)
  const buildColorMap = (breakdown) => {
    const keys = Object.keys(breakdown);
    const colors = (window.generateColors || generateColors)(keys.length);
    const map = {};
    keys.forEach((key, i) => { map[key] = colors[i]; });
    return map;
  };
  const leftColorMap = buildColorMap(leftBreakdown);
  const rightColorMap = buildColorMap(rightBreakdown);

  // Append DOM elements for detailed breakdown (with color-keyed headers)
  elements.typeBreakdown.appendChild(createBreakdownElements(leftBreakdown, leftColorMap));
  elements.locationBreakdown.appendChild(createBreakdownElements(rightBreakdown, rightColorMap));

  // Show modal
  if (window.openModalById) openModalById('detailsModal');
  else {
    elements.detailsModal.style.display = 'flex';
    document.body.style.overflow = 'hidden';
  }

  // Scroll modal body to top on open
  const modalBody = elements.detailsModal.querySelector('.modal-body');
  if (modalBody) modalBody.scrollTop = 0;

  // Clean up any existing resize observer before creating a new one
  if (detailsResizeObserver) {
    detailsResizeObserver.disconnect();
    detailsResizeObserver = null;
  }

  // Add chart resize handling (skip on mobile where charts are hidden)
  if (!isMobile) {
    detailsResizeObserver = new ResizeObserver(() => {
      Object.values(chartInstances).forEach(chart => {
        if (chart) chart.resize();
      });
    });
    detailsResizeObserver.observe(elements.detailsModal);
  }
};

/**
 * Closes the details modal and cleans up charts
 */
const closeDetailsModal = () => {
  if (detailsResizeObserver) {
    detailsResizeObserver.disconnect();
    detailsResizeObserver = null;
  }
  if (window.closeModalById) closeModalById('detailsModal');
  else {
    elements.detailsModal.style.display = 'none';
    try { document.body.style.overflow = ''; } catch (e) {}
  }
  destroyCharts();
};

// =============================================================================

// Expose details modal functions globally for inline handlers
window.showDetailsModal = showDetailsModal;
window.closeDetailsModal = closeDetailsModal;