/**
 * Decodes HTML entities in a string to their Unicode characters.
 * Handles named entities (&nbsp;), decimal (&#32;), and hex (&#x20;).
 * Robust and environment-agnostic (does not rely on DOM).
 * 
 * @param {string} str - The string containing HTML entities
 * @returns {string} - The decoded string
 */
export const decodeHtmlEntities = (str) => {
  if (!str || typeof str !== 'string') return str || '';
  
  // 1. Handle numeric entities (decimal and hex)
  let decoded = str.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
                   .replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => String.fromCharCode(parseInt(hex, 16)));

  // 2. Handle common named entities including symbols
  const namedEntities = {
    '&nbsp;': ' ',
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&apos;': "'",
    '&bull;': '•',
    '&mdash;': '—',
    '&ndash;': '–',
    '&copy;': '©',
    '&reg;': '®',
    '&trade;': '™',
    '&euro;': '€',
    '&pound;': '£',
    '&yen;': '¥',
    '&cent;': '¢',
    '&sect;': '§',
    '&deg;': '°',
    '&check;': '✓',
    '&cross;': '✗',
    '&star;': '☆',
    '&starf;': '★'
  };
  
  decoded = decoded.replace(/&[a-z]+;/g, (match) => namedEntities[match] || match);

  // 3. Fallback for any remaining entities if DOMParser is available (Browser)
  if (typeof DOMParser !== 'undefined' && decoded.includes('&')) {
      try {
          const doc = new DOMParser().parseFromString(decoded, 'text/html');
          return doc.documentElement.textContent || decoded;
      } catch (e) {
          return decoded;
      }
  }
  
  return decoded;
};