SOURCE

// content.js – Telegram Media/File Downloader con supporto multi-tipo e timestamp migliorati
(function () {
  if (window.__telegramDownloaderInjected) return;
  window.__telegramDownloaderInjected = true;

  /*********** STATE ***********/
  let isPro = false;
  let maxDownloads = 50;
  let downloadCount = 0;
  let isDownloading = false;
  let dateFrom = null;
  let dateTo = null;
  let downloadAsZip = false;
  let scanMode = 'auto';
  let downloadTypes = []; // Array di tipi: ['images', 'videos', 'documents', 'audio']

  let zipFiles = [];
  let downloadedURLs = new Set();
  let downloadedFiles = new Set(); // Traccia file già scaricati (timestamp + filename)

  const LABELS = {
    MEDIA: ['Media','Foto','Photos','Immagini','Imágenes','Média','Medien','Фотографии'],
    FILES: ['Files','Documenti','Documentos','Dateien','Файлы'],
    INFO:  ['Info','Profile','Dettagli','Details','Profilo']
  };

  // Mapping estensioni per categorizzazione
  const FILE_TYPES = {
    images: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico', 'heic', 'heif', 'tiff', 'tif'],
    videos: ['mp4', 'webm', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'm4v', '3gp', 'mpg', 'mpeg', 'ogv'],
    documents: [
      // Office
      'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 
      // Text
      'txt', 'rtf', 'odt', 'ods', 'odp', 'csv',
      // Code
      'js', 'jsx', 'ts', 'tsx', 'html', 'htm', 'css', 'scss', 'sass', 'json', 'xml', 'yml', 'yaml',
      'py', 'java', 'cpp', 'c', 'h', 'cs', 'php', 'rb', 'go', 'rs', 'swift', 'kt',
      // Archives
      'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz',
      // Other
      'epub', 'mobi', 'azw', 'azw3'
    ],
    audio: ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'wma', 'opus', 'oga', 'ape', 'alac']
  };

  /*********** UTILS ***********/
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
  const pad = n => (n<10?'0':'') + n;
  const fmtTs = (ts) => {
    const d = new Date(ts || Date.now());
    return d.getFullYear()+''+pad(d.getMonth()+1)+''+pad(d.getDate())+'_'+pad(d.getHours())+pad(d.getMinutes())+pad(d.getSeconds());
  };
  
  const guessExtFromUrl = (u, fallback='bin') => {
    try {
      const clean = (u||'').split('?')[0].split('#')[0];
      const m = clean.match(/\.(\w{1,5})$/i);
      return (m ? m[1] : fallback).toLowerCase();
    } catch { return fallback; }
  };

  function getFileCategory(ext) {
    ext = ext.toLowerCase();
    for (const [category, extensions] of Object.entries(FILE_TYPES)) {
      if (extensions.includes(ext)) return category;
    }
    return null;
  }

  function shouldDownloadFile(ext) {
    const category = getFileCategory(ext);
    return category && downloadTypes.includes(category);
  }

  function showNotification(message, type='info') {
    const n = document.createElement('div');
    n.className = 'tgdl-toast';
    n.textContent = message;
    n.style.cssText = `
      position:fixed;z-index:2147483647;inset:20px 20px auto auto;background:${type==='error'?'#e53935':type==='success'?'#43a047':'#1e88e5'};
      color:#fff;padding:12px 14px;border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,.25);
      font:13px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Arial;max-width:360px;pointer-events:none;opacity:.98;
      transition:transform .25s ease,opacity .25s ease;
    `;
    document.body.appendChild(n);
    setTimeout(()=>{ n.style.opacity='0'; n.style.transform='translateY(-6px)'; setTimeout(()=>n.remove(),300); },2400);
  }

  function setProMode(value) {
    isPro = !!value;
    maxDownloads = isPro ? 1e9 : 25;
  }

  function incrementDownloadCount() {
    if (downloadCount >= maxDownloads) return;
    downloadCount++;
    chrome.runtime?.sendMessage?.({ type: 'download' });
    if (downloadCount >= maxDownloads) {
      chrome.runtime?.sendMessage?.({ type: 'limitReached' });
    }
  }

  function isVisible(el) {
    const rect = el.getBoundingClientRect();
    if (!rect || rect.width <= 0 || rect.height <= 0) return false;
    const cs = window.getComputedStyle(el);
    return cs.display !== 'none' && cs.visibility !== 'hidden' && cs.opacity !== '0';
  }

  function isChatSelected() {
    // Telegram A
    //  - .messages-layout
    //  - .messages-container
    //  - .chat-info-wrapper
    // Telegram K
    //  - .chat-utils
    //  - .chat-info-container
    const selectors = [
      '.messages-layout',
      '.messages-container',
      '.chat-info-wrapper',
      '.chat-utils',
      '.chat-info-container',
    ];
    for (const sel of selectors) {
      const el = document.querySelector(sel);
      if (el && isVisible(el)) return true;
    }
    return false;
  }

  function findMessagesContainer() {
    const guesses = [
      '.messages-container','.bubbles-inner','.chat-background','.scrollable',
      '.chat-content .scrollable','.MessageList','[class*="MessageList"]'
    ];
    let best=null, score=-1;
    const isScrollable = el => {
      const st = getComputedStyle(el);
      return /(auto|scroll)/.test(st.overflowY) && el.scrollHeight>el.clientHeight;
    };
    const msgCount = el => el.querySelectorAll('.message,.Message,.bubble,[class*="bubble"]').length;
    
    // IMPORTANTE: escludi sidebar delle chat
    const isSidebar = el => {
      // Telegram K: sidebar ha classi come chatlist, folders-tabs, etc
      // Telegram A: sidebar ha classe .chat-list o simili
      return el.closest('.chatlist, [class*="chatlist"], .chat-list, [class*="chat-list"], .folders-tabs, .sidebar, [class*="sidebar"]') !== null;
    };

    for (const sel of guesses) {
      const el = document.querySelector(sel);
      if (el && isScrollable(el) && !isSidebar(el)) {
        const c = msgCount(el); if (c>score){best=el;score=c;}
      }
    }
    if (best) return best;

    document.querySelectorAll('div').forEach(el=>{
      if (isScrollable(el) && !isSidebar(el)) {
        const c = msgCount(el); if (c>score){best=el;score=c;}
      }
    });
    return best;
  }

  function extractDateFromMessageGroup(messageEl) {
    // Cerca il parent .message-date-group e la data nel .sticky-date
    let current = messageEl;
    let attempts = 0;
    const maxAttempts = 10;
    
    while (current && attempts < maxAttempts) {
      if (current.classList && current.classList.contains('message-date-group')) {
        const stickyDate = current.querySelector('.sticky-date span');
        if (stickyDate) {
          const dateText = stickyDate.textContent.trim();
          console.log('�� Trovata data dal sticky-date:', dateText);
          const parsed = parseDateText(dateText);
          if (parsed) {
            console.log('✅ Data parsata:', new Date(parsed).toLocaleString());
            return parsed;
          }
        }
        break;
      }
      current = current.parentElement;
      attempts++;
    }
    return null;
  }

  function parseDateText(text) {
    if (!text) return null;
    
    const lowerText = text.toLowerCase().trim();
    
    // 1. Gestisci "Oggi" / "Today"
    if (/^(oggi|today|heute|hoy|aujourd'hui)$/i.test(lowerText)) {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      console.log('�� Parsed "Oggi/Today":', new Date(today).toLocaleString());
      return today.getTime();
    }
    
    // 2. Gestisci "Ieri" / "Yesterday"
    if (/^(ieri|yesterday|gestern|ayer|hier)$/i.test(lowerText)) {
      const yesterday = new Date();
      yesterday.setDate(yesterday.getDate() - 1);
      yesterday.setHours(0, 0, 0, 0);
      console.log('�� Parsed "Ieri/Yesterday":', new Date(yesterday).toLocaleString());
      return yesterday.getTime();
    }
    
    // 3. Gestisci giorni della settimana (Lunedì, Martedì, etc.)
    const weekdayMap = {
      // Italiano
      'lunedì': 1, 'lunedi': 1, 'martedì': 2, 'martedi': 2, 'mercoledì': 3, 'mercoledi': 3,
      'giovedì': 4, 'giovedi': 4, 'venerdì': 5, 'venerdi': 5, 'sabato': 6, 'domenica': 0,
      // Inglese
      'monday': 1, 'tuesday': 2, 'wednesday': 3, 'thursday': 4, 'friday': 5, 'saturday': 6, 'sunday': 0,
      // Francese
      'lundi': 1, 'mardi': 2, 'mercredi': 3, 'jeudi': 4, 'vendredi': 5, 'samedi': 6, 'dimanche': 0,
      // Tedesco
      'montag': 1, 'dienstag': 2, 'mittwoch': 3, 'donnerstag': 4, 'freitag': 5, 'samstag': 6, 'sonntag': 0,
      // Spagnolo
      'lunes': 1, 'martes': 2, 'miércoles': 3, 'miercoles': 3, 'jueves': 4, 'viernes': 5, 'sábado': 6, 'sabado': 6, 'domingo': 0
    };
    
    const targetDay = weekdayMap[lowerText];
    if (targetDay !== undefined) {
      const today = new Date();
      const currentDay = today.getDay(); // 0 = Domenica, 1 = Lunedì, etc.
      
      // Calcola quanti giorni indietro è il giorno target
      let daysAgo = currentDay - targetDay;
      if (daysAgo <= 0) {
        // Se il giorno è nel futuro questa settimana, vai alla settimana scorsa
        daysAgo += 7;
      }
      
      const targetDate = new Date(today);
      targetDate.setDate(today.getDate() - daysAgo);
      targetDate.setHours(0, 0, 0, 0);
      
      console.log(`�� Parsed weekday "${text}" (${targetDay}) as:`, new Date(targetDate).toLocaleString(), `(${daysAgo} giorni fa)`);
      return targetDate.getTime();
    }
    
    // 4. Parse date formats like "5 settembre 2024", "15 October 2024", etc.
    const monthNames = {
      'january': 0, 'gennaio': 0, 'janvier': 0, 'januar': 0, 'enero': 0,
      'february': 1, 'febbraio': 1, 'février': 1, 'februar': 1, 'febrero': 1,
      'march': 2, 'marzo': 2, 'mars': 2, 'märz': 2,
      'april': 3, 'aprile': 3, 'avril': 3, 'abril': 3,
      'may': 4, 'maggio': 4, 'mai': 4, 'mayo': 4,
      'june': 5, 'giugno': 5, 'juin': 5, 'juni': 5, 'junio': 5,
      'july': 6, 'luglio': 6, 'juillet': 6, 'juli': 6, 'julio': 6,
      'august': 7, 'agosto': 7, 'août': 7, 'agosto': 7,
      'september': 8, 'settembre': 8, 'septembre': 8, 'september': 8, 'septiembre': 8,
      'october': 9, 'ottobre': 9, 'octobre': 9, 'oktober': 9, 'octubre': 9,
      'november': 10, 'novembre': 10, 'novembre': 10, 'november': 10, 'noviembre': 10,
      'december': 11, 'dicembre': 11, 'décembre': 11, 'dezember': 11, 'diciembre': 11
    };

    // Pattern con anno: "5 settembre 2024" o "15 October 2024"
    const matchWithYear = text.match(/(\d{1,2})\s+(\w+)\s+(\d{4})/i);
    if (matchWithYear) {
      const day = parseInt(matchWithYear[1]);
      const monthStr = matchWithYear[2].toLowerCase();
      const year = parseInt(matchWithYear[3]);
      
      const month = monthNames[monthStr];
      if (month !== undefined) {
        const date = new Date(year, month, day, 0, 0, 0, 0);
        return date.getTime();
      }
    }
    
    // Pattern SENZA anno: "26 giugno" o "15 October"
    const matchNoYear = text.match(/^(\d{1,2})\s+(\w{4,})\s*$/i);
    if (matchNoYear) {
      const day = parseInt(matchNoYear[1]);
      const monthStr = matchNoYear[2].toLowerCase();
      
      const month = monthNames[monthStr];
      if (month !== undefined) {
        const currentYear = new Date().getFullYear();
        const date = new Date(currentYear, month, day, 0, 0, 0, 0);
        
        // Se la data è nel futuro, usa anno precedente
        if (date.getTime() > Date.now()) {
          date.setFullYear(currentYear - 1);
        }
        
        console.log(`�� Parsed date without year "${text}" as:`, new Date(date).toLocaleString());
        return date.getTime();
      }
    }
    
    return null;
  }

  function combineDateTime(dateTimestamp, timeText) {
    // Combina una data (timestamp) con un orario (es. "11:39")
    if (!dateTimestamp || !timeText) return null;
    
    const timeMatch = timeText.match(/(\d{1,2}):(\d{2})/);
    if (!timeMatch) return null;
    
    const hours = parseInt(timeMatch[1]);
    const minutes = parseInt(timeMatch[2]);
    
    const date = new Date(dateTimestamp);
    date.setHours(hours, minutes, 0, 0);
    
    return date.getTime();
  }

  function extractTimestampFromElement(el) {
    // 0. PRIMA DI TUTTO: cerca la data dal message-date-group
    const groupDate = extractDateFromMessageGroup(el);
    
    // Se abbiamo trovato una data dal gruppo, cerchiamo l'orario nel messaggio
    if (groupDate) {
      // Cerca .message-time nel messaggio
      const timeEl = el.querySelector('.message-time, [class*="time"], [class*="Time"]');
      if (timeEl) {
        const timeText = timeEl.textContent.trim();
        const fullTimestamp = combineDateTime(groupDate, timeText);
        if (fullTimestamp) {
          console.log('�� Timestamp completo da sticky-date + message-time:', new Date(fullTimestamp).toLocaleString());
          return fullTimestamp;
        }
      }
    }
    
    // 1. data-timestamp / data-date (vari formati)
    const tsAttrs = ['data-timestamp', 'data-date', 'data-time', 'timestamp', 'date'];
    for (const attr of tsAttrs) {
      const val = el.getAttribute(attr) || el.dataset?.[attr.replace('data-', '')];
      if (val && !isNaN(Number(val))) {
        const ts = Number(val);
        const converted = ts > 10000000000 ? ts : ts * 1000;
        // Valida: deve essere tra 2000 e oggi + 1 anno
        if (converted > 946684800000 && converted < Date.now() + 31536000000) {
          console.log(`✅ Trovato ${attr}:`, new Date(converted).toLocaleString(), '(' + converted + ')');
          return converted;
        } else {
          console.warn(`⚠️ ${attr} fuori range:`, new Date(converted).toLocaleString(), '- ignorato');
        }
      }
    }

    // 2. data-mid (message ID di Telegram)
    const dataMid = el.getAttribute('data-mid') || el.dataset?.mid || el.id;
    if (dataMid && typeof dataMid === 'string') {
      // Pattern 1: "123456789" (solo numero)
      if (/^\d+$/.test(dataMid)) {
        const ts = parseInt(dataMid);
        if (ts > 1000000000) {
          const converted = ts > 10000000000 ? ts : ts * 1000;
          if (converted > 946684800000 && converted < Date.now() + 31536000000) {
            console.log('✅ Trovato data-mid (numero):', new Date(converted).toLocaleString(), '(' + converted + ')');
            return converted;
          }
        }
      }
      // Pattern 2: "timestamp_random" 
      const parts = dataMid.split('_');
      if (parts.length > 0) {
        const ts = parseInt(parts[0]);
        if (!isNaN(ts) && ts > 1000000000) {
          const converted = ts > 10000000000 ? ts : ts * 1000;
          if (converted > 946684800000 && converted < Date.now() + 31536000000) {
            console.log('✅ Trovato data-mid (split):', new Date(converted).toLocaleString(), '(' + converted + ')');
            return converted;
          }
        }
      }
    }

    // 3. Cerca in TUTTI i discendenti time/span con classi time/date
    const timeElements = el.querySelectorAll('time, [class*="time"], [class*="Time"], [class*="date"], [class*="Date"], span.i');
    for (const timeEl of timeElements) {
      // Attributi datetime/title
      for (const attr of ['datetime', 'title', 'data-timestamp', 'data-content']) {
        const dt = timeEl.getAttribute(attr);
        if (dt) {
          const parsed = Date.parse(dt);
          if (!isNaN(parsed) && parsed > 946684800000 && parsed < Date.now() + 31536000000) {
            console.log(`✅ Trovato time[${attr}]:`, new Date(parsed).toLocaleString(), '(' + parsed + ')');
            return parsed;
          }
        }
      }
      
      // TextContent solo se breve
      const text = (timeEl.textContent || '').trim();
      if (text && text.length < 30 && text.length > 3) {
        const parsed = parseTimeText(text);
        if (parsed && parsed > 946684800000 && parsed < Date.now() + 31536000000) {
          console.log('✅ Trovato time text:', new Date(parsed).toLocaleString(), '(' + parsed + ') da:', text);
          return parsed;
        }
      }
    }

    // 4. aria-label (spesso contiene data completa)
    const ariaLabel = el.getAttribute('aria-label');
    if (ariaLabel && ariaLabel.length < 200) {
      const parsed = extractDateFromText(ariaLabel);
      if (parsed && parsed > 946684800000 && parsed < Date.now() + 31536000000) {
        console.log('✅ Trovato aria-label:', new Date(parsed).toLocaleString(), '(' + parsed + ')');
        return parsed;
      }
    }

    return null;
  }

  function parseTimeText(text) {
    if (!text) return null;
    
    text = text.trim();
    
    // Pattern di oggi/ieri - usa solo se c'è anche un orario
    const timeMatch = text.match(/(\d{1,2}):(\d{2})/);
    if (timeMatch) {
      const hours = parseInt(timeMatch[1]);
      const minutes = parseInt(timeMatch[2]);
      
      // Se trova "oggi" o "today"
      if (/\b(oggi|today)\b/i.test(text)) {
        const today = new Date();
        today.setHours(hours, minutes, 0, 0);
        return today.getTime();
      }
      
      // Se trova "ieri" o "yesterday"
      if (/\b(ieri|yesterday)\b/i.test(text)) {
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);
        yesterday.setHours(hours, minutes, 0, 0);
        return yesterday.getTime();
      }
      
      // Non assumere nulla se c'è solo l'ora senza contesto
      return null;
    }

    // Pattern "5 apr" o "12 gen" (senza anno) - usa anno corrente
    const shortDateMatch = text.match(/^(\d{1,2})\s+(gen|feb|mar|apr|mag|giu|lug|ago|set|ott|nov|dic|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i);
    if (shortDateMatch) {
      const day = parseInt(shortDateMatch[1]);
      const monthAbbr = shortDateMatch[2].toLowerCase();
      
      const monthMap = {
        'gen': 0, 'feb': 1, 'mar': 2, 'apr': 3, 'mag': 4, 'giu': 5,
        'lug': 6, 'ago': 7, 'set': 8, 'ott': 9, 'nov': 10, 'dic': 11,
        'jan': 0, 'feb': 1, 'mar': 2, 'apr': 3, 'may': 4, 'jun': 5,
        'jul': 6, 'aug': 7, 'sep': 8, 'oct': 9, 'nov': 10, 'dec': 11
      };
      
      const month = monthMap[monthAbbr];
      if (month !== undefined) {
        const currentYear = new Date().getFullYear();
        const date = new Date(currentYear, month, day, 0, 0, 0, 0);
        
        // Se la data è nel futuro (es. siamo a ottobre e troviamo novembre), usa anno precedente
        if (date.getTime() > Date.now()) {
          date.setFullYear(currentYear - 1);
        }
        
        console.log(`�� Parsed short date "${text}" as:`, new Date(date).toLocaleString());
        return date.getTime();
      }
    }
    
    // Prova parsing diretto ISO solo DOPO aver provato i pattern specifici
    const directParse = Date.parse(text);
    if (!isNaN(directParse) && directParse > 946684800000) {
      // Verifica che non sia troppo nel passato (< 2020)
      if (directParse < new Date('2020-01-01').getTime()) {
        console.warn('⚠️ Date.parse ha restituito una data troppo vecchia:', new Date(directParse).toLocaleString(), 'da testo:', text);
        return null;
      }
      return directParse;
    }

    return null;
  }

  function extractDateFromText(text) {
    if (!text || text.length > 200) return null; // Evita testi troppo lunghi

    // Pattern 1: "12 ottobre 2025, 14:30" o "12 October 2025, 14:30"
    const monthsIT = {
      'gennaio': 0, 'febbraio': 1, 'marzo': 2, 'aprile': 3, 'maggio': 4, 'giugno': 5,
      'luglio': 6, 'agosto': 7, 'settembre': 8, 'ottobre': 9, 'novembre': 10, 'dicembre': 11
    };
    const monthsEN = {
      'january': 0, 'february': 1, 'march': 2, 'april': 3, 'may': 4, 'june': 5,
      'july': 6, 'august': 7, 'september': 8, 'october': 9, 'november': 10, 'december': 11
    };
    
    const lowerText = text.toLowerCase();
    
    // Cerca pattern "giorno mese anno"
    const datePattern = /(\d{1,2})\s+(\w+)\s+(\d{4})/i;
    const match = text.match(datePattern);
    
    if (match) {
      const day = parseInt(match[1]);
      const monthStr = match[2].toLowerCase();
      const year = parseInt(match[3]);
      
      let month = monthsIT[monthStr] ?? monthsEN[monthStr];
      
      if (month !== undefined && year >= 2000 && year <= 2100 && day >= 1 && day <= 31) {
        // Cerca anche l'orario se presente
        const timeMatch = text.match(/(\d{1,2}):(\d{2})/);
        const hours = timeMatch ? parseInt(timeMatch[1]) : 12;
        const minutes = timeMatch ? parseInt(timeMatch[2]) : 0;
        
        const date = new Date(year, month, day, hours, minutes, 0, 0);
        return date.getTime();
      }
    }

    // Pattern 2: ISO format "2025-10-12"
    const isoMatch = text.match(/(\d{4})-(\d{1,2})-(\d{1,2})/);
    if (isoMatch) {
      const parsed = Date.parse(isoMatch[0]);
      if (!isNaN(parsed) && parsed > 946684800000) return parsed;
    }

    // Pattern 3: "12/10/2025" o "12.10.2025"
    const dateSlashMatch = text.match(/(\d{1,2})[\/\.](\d{1,2})[\/\.](\d{4})/);
    if (dateSlashMatch) {
      const d = parseInt(dateSlashMatch[1]);
      const m = parseInt(dateSlashMatch[2]);
      const y = parseInt(dateSlashMatch[3]);
      
      if (y >= 2000 && y <= 2100 && m >= 1 && m <= 12 && d >= 1 && d <= 31) {
        const date = new Date(y, m - 1, d, 12, 0, 0, 0);
        return date.getTime();
      }
    }

    return null;
  }

  function parseDayHeaderText(txt) {
    if (!txt) return null;
    const t = txt.trim();
    
    // Try native Date
    const d1 = Date.parse(t);
    if (!isNaN(d1)) return d1;

    // Try common i18n formats
    const mapIT = {'gennaio':0,'febbraio':1,'marzo':2,'aprile':3,'maggio':4,'giugno':5,'luglio':6,'agosto':7,'settembre':8,'ottobre':9,'novembre':10,'dicembre':11};
    const m1 = t.toLowerCase().match(/(\d{1,2})\s+([a-zàéìòù]+)\s+(\d{4})/i);
    if (m1 && mapIT[m1[2]]!==undefined) {
      const d = new Date(Number(m1[3]), mapIT[m1[2]], Number(m1[1]));
      return d.getTime();
    }
    
    const m2 = t.match(/(\d{1,2})\s+([A-Za-z]{3,})\s+(\d{4})/);
    if (m2) {
      const d = Date.parse(`${m2[1]} ${m2[2]} ${m2[3]}`);
      if (!isNaN(d)) return d;
    }
    
    return null;
  }

  function nearestDayHeaderBefore(el, maxSteps=80) {
    let cur = el;
    let steps = 0;
    while (cur && steps<maxSteps) {
      cur = cur.previousElementSibling;
      steps++;
      if (!cur) break;
      if (cur.matches?.('.message-list-day,[class*="Day"],[role="separator"],.date-separator')) {
        const text = cur.textContent || '';
        const ts = parseDayHeaderText(text);
        if (ts) return ts;
      }
    }
    return null;
  }

  function getMessageTimestamp(msgEl) {
    if (!msgEl) {
      console.warn('⚠️ msgEl è null');
      return Date.now();
    }

    console.log('�� Cercando timestamp in:', msgEl.className, 'tag:', msgEl.tagName);

    // STRATEGIA: cerca dal più specifico al più generico
    
    // 1. Prova nel messaggio stesso
    let ts = extractTimestampFromElement(msgEl);
    if (ts && ts > 946684800000 && ts < Date.now() + 86400000) {
      console.log('✅ Timestamp trovato nel messaggio:', new Date(ts).toLocaleString(), '(' + ts + ')');
      return ts;
    }

    // 2. Cerca nel parent immediato
    if (msgEl.parentElement) {
      console.log('�� Provo parent:', msgEl.parentElement.className);
      ts = extractTimestampFromElement(msgEl.parentElement);
      if (ts && ts > 946684800000 && ts < Date.now() + 86400000) {
        console.log('✅ Timestamp trovato nel parent:', new Date(ts).toLocaleString(), '(' + ts + ')');
        return ts;
      }
    }

    // 3. Cerca nel contenitore del messaggio (bubble/message wrapper)
    const selectors = [
      '.bubble',
      '.message-container', 
      '.Message',
      '.message-date-group',
      '[class*="message"]',
      '[class*="Message"]',
      '[class*="bubble"]',
      '[data-mid]'
    ];
    
    for (const sel of selectors) {
      const bubble = msgEl.closest(sel);
      if (bubble && bubble !== msgEl) {
        console.log('�� Provo bubble:', sel);
        ts = extractTimestampFromElement(bubble);
        if (ts && ts > 946684800000 && ts < Date.now() + 86400000) {
          console.log('✅ Timestamp trovato nel bubble:', new Date(ts).toLocaleString(), '(' + ts + ')');
          return ts;
        }
      }
    }

    // 4. Cerca nel day header sopra (max 100 elementi prima)
    console.log('�� Cerco nel day header...');
    ts = nearestDayHeaderBefore(msgEl, 100);
    if (ts && ts > 946684800000 && ts < Date.now() + 86400000) {
      console.log('⚠️ Timestamp trovato nel day header:', new Date(ts).toLocaleString(), '(' + ts + ')');
      return ts;
    }

    // 5. ULTIMO RESORT: usa data corrente ma logga warning
   // console.error('❌ Nessun timestamp valido trovato, uso data corrente');
    console.log('�� HTML elemento:', msgEl.outerHTML.substring(0, 500));
    return Date.now();
  }

  function passDateFilter(ts) {
    if (!dateFrom && !dateTo) return true;
    
    console.log('�� Check date filter:', {
      messageDate: new Date(ts).toLocaleString(),
      messageTime: ts,
      dateFrom: dateFrom ? new Date(dateFrom).toLocaleString() : 'none',
      dateTo: dateTo ? new Date(dateTo).toLocaleString() : 'none'
    });
    
    // Confronto diretto con i timestamp (già includono ore)
    if (dateFrom && ts < dateFrom) {
      console.log('❌ REJECT: ts < dateFrom');
      return false;
    }
    
    if (dateTo && ts > dateTo) {
      console.log('❌ REJECT: ts > dateTo');
      return false;
    }
    
    console.log('✅ ACCEPT: dentro range');
    return true;
  }

  function shouldStopScanning(ts) {
    // NON fermare MAI basandosi su una singola data!
    // Gli elementi nel DOM non sono in ordine cronologico,
    // quindi potremmo trovare prima un elemento vecchio e poi uno recente.
    // 
    // Lo scanning si ferma solo quando:
    // - Lo scroll non può più andare indietro
    // - La posizione scroll rimane stabile per troppo tempo
    // 
    // Il filtro date viene applicato in passDateFilter()
    return false;
  }

  /*** DOWNLOAD CORE ***/
  function downloadFile(url, filename, addToZip=false) {
    if (!url) return false;
    if (downloadedURLs.has(url)) return false;
    if (downloadCount >= maxDownloads) return false;

    downloadedURLs.add(url);

    if (downloadAsZip || addToZip) {
      zipFiles.push({ url, filename });
      incrementDownloadCount();
    } else {
      // Download diretto con anchor tag
      const a = document.createElement('a');
      a.href = url;
      a.download = filename || '';
      document.body.appendChild(a);
      a.click();
      a.remove();
      incrementDownloadCount();
    }
    return true;
  }

  async function createAndDownloadZip() {
    if (!zipFiles.length) return;
    showNotification(`�� Creating ZIP (${zipFiles.length} files)…`, 'info');

    const files = zipFiles.slice();
    zipFiles = [];

    try {
      const response = await chrome.runtime.sendMessage({
        type: 'createZip',
        files: files
      });

      if (!response.success) {
        showNotification('❌ ZIP creation failed: ' + response.error, 'error');
        return;
      }

      const uint8Array = new Uint8Array(response.data);
      const zipBlob = new Blob([uint8Array], { type: 'application/zip' });

      const a = document.createElement('a');
      a.href = URL.createObjectURL(zipBlob);
      a.download = `telegram_media_${Date.now()}.zip`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      
      setTimeout(() => URL.revokeObjectURL(a.href), 100);

      chrome.runtime?.sendMessage?.({ type: 'zipReady' });
      showNotification('✅ ZIP ready', 'success');
    } catch (error) {
      console.error('ZIP creation error:', error);
      showNotification('❌ ZIP creation failed', 'error');
    }
  }

  /*********** DETECTION FUNCTIONS ***********/
  
  function isImageElement(el) {
    // ESCLUDI elementi dentro .Reactions (emoji di reazione)
    if (el.closest('.Reactions, .reactions, .Reaction, .reaction')) {
      return false;
    }
    
    // ESCLUDI elementi dentro .message-action-buttons (bottoni hover)
    if (el.closest('.message-action-buttons, .MessageActionButtons')) {
      return false;
    }
    
    // ESCLUDI elementi dentro menu popup (emoji/sticker/reazioni picker)
    if (el.closest('.menu-container, .Menu, .ReactionPicker, .StickerPicker, .EmojiPicker, .in-portal')) {
      return false;
    }
    
    // ESCLUDI per classe specifica emoji
    if (el.classList && (el.classList.contains('ReactionStaticEmoji') || 
                         el.classList.contains('ReactionEmoji') ||
                         el.classList.contains('reaction-emoji'))) {
      return false;
    }
    
    // ESCLUDI per dimensione (emoji/icone sono piccole: <= 24px)
    if (el.tagName === 'IMG') {
      const width = el.width || parseInt(el.style.width) || 0;
      const height = el.height || parseInt(el.style.height) || 0;
      
      // Se entrambe le dimensioni sono <= 24px, probabilmente è una emoji/icona
      if ((width > 0 && width <= 24) && (height > 0 && height <= 24)) {
        console.log('⏭️ Skipping small image (likely emoji/icon):', width + 'x' + height);
        return false;
      }
    }
    
    // Immagini: img tag, background-image, picture
    if (el.tagName === 'IMG') return true;
    if (el.tagName === 'PICTURE') return true;
    
    const style = getComputedStyle(el);
    if (style.backgroundImage && style.backgroundImage !== 'none') return true;
    
    return false;
  }

  function isVideoElement(el) {
    // Video: video tag, o elementi con attributi video
    if (el.tagName === 'VIDEO') return true;
    if (el.querySelector('video')) return true;
    
    // Cerca classi/attributi che indicano video
    const videoClasses = ['video', 'Video', 'media-video'];
    for (const cls of videoClasses) {
      if (el.className && el.className.includes(cls)) {
        // Verifica che non sia un'immagine
        if (!isImageElement(el)) return true;
      }
    }
    
    return false;
  }

  function isDocumentElement(el) {
    // Documenti: cerca icone/classi che indicano documenti
    const docClasses = ['document', 'Document', 'file', 'File', 'file-info'];
    const className = el.className || '';
    
    for (const cls of docClasses) {
      if (className.includes(cls)) return true;
    }
    
    // Cerca elementi con estensioni documenti visibili
    const text = el.textContent || '';
    for (const ext of FILE_TYPES.documents) {
      if (text.toLowerCase().includes('.' + ext)) return true;
    }
    
    return false;
  }

  function isAudioElement(el) {
    // Audio: audio tag o elementi con attributi audio
    if (el.tagName === 'AUDIO') return true;
    if (el.querySelector('audio')) return true;
    
    const audioClasses = ['audio', 'Audio', 'voice', 'Voice', 'voice-message', 'audio-message'];
    const className = el.className || '';
    
    for (const cls of audioClasses) {
      if (className.includes(cls)) return true;
    }
    
    // Cerca elementi con estensioni audio visibili
    const text = el.textContent || '';
    for (const ext of FILE_TYPES.audio) {
      if (text.toLowerCase().includes('.' + ext)) return true;
    }
    
    // Cerca icone audio
    const hasAudioIcon = el.querySelector('[class*="audio"]') || el.querySelector('[class*="Audio"]');
    if (hasAudioIcon) return true;
    
    return false;
  }

  /*********** CHAT SCAN ***********/
  
  function getAllChatMediaElements() {
    const allElements = Array.from(document.querySelectorAll(
      '.media-container, .media-photo, .album-item, .message img, .Message img, .bubble img, ' +
      'video, .media-video, ' +
      '.document-wrapper, .document-container, .File, .file, ' +
      'audio, .audio, .voice, ' +
      'picture, [style*="background-image"]'
    ));
    
    // Filtra elementi non validi
    const filtered = allElements.filter(el => {
      // ESCLUDI elementi dentro .Reactions (emoji di reazione)
      if (el.closest('.Reactions, .reactions, .Reaction, .reaction')) {
        return false;
      }
      
      // ESCLUDI elementi dentro .message-action-buttons (bottoni hover)
      if (el.closest('.message-action-buttons, .MessageActionButtons')) {
        return false;
      }
      
      // ESCLUDI elementi dentro menu popup (emoji/sticker/reazioni picker)
      if (el.closest('.menu-container, .Menu, .ReactionPicker, .StickerPicker, .EmojiPicker, .in-portal')) {
        return false;
      }
      
      // ESCLUDI per classe specifica emoji
      if (el.classList && (el.classList.contains('ReactionStaticEmoji') || 
                           el.classList.contains('ReactionEmoji') ||
                           el.classList.contains('reaction-emoji'))) {
        return false;
      }
      
      const msg = el.closest('.message,.Message,.bubble,[class*="message"]');
      if (!msg) return false;
      
      // Se è un sotto-elemento di un document-wrapper già nella lista, escludilo
      const parent = el.closest('.document-wrapper, .document-container');
      if (parent && parent !== el) return false; // È un figlio, escludilo
      
      return true;
    });
    
    console.log('�� Trovati', filtered.length, 'elementi media nella chat');
    return filtered;
  }

  function getElementType(el) {
    // Determina il tipo di elemento
    if (isVideoElement(el)) return 'videos';
    if (isAudioElement(el)) return 'audio';
    if (isDocumentElement(el)) return 'documents';
    if (isImageElement(el)) return 'images';
    
    // Fallback: controlla i tag
    const tag = el.tagName.toLowerCase();
    if (tag === 'img' || tag === 'picture') return 'images';
    if (tag === 'video') return 'videos';
    if (tag === 'audio') return 'audio';
    
    return null;
  }

  function extractMediaURL(element) {
    // Video - cerca in modo più approfondito
    if (element.tagName === 'VIDEO') {
      const src = element.src || element.currentSrc;
      console.log('�� Video tag diretto:', src);
      return src;
    }
    
    // Cerca video in qualsiasi punto del DOM (non solo querySelector diretto)
    const videoEl = element.querySelector('video');
    if (videoEl) {
      const src = videoEl.src || videoEl.currentSrc;
      console.log('�� Video trovato nel DOM:', src);
      if (src) return src;
      
      // Cerca source tags
      const source = videoEl.querySelector('source[src]');
      if (source?.src) {
        console.log('�� Video source tag:', source.src);
        return source.src;
      }
    }
    
    // Se l'elemento stesso ha classe video, cerca più in profondità
    if (element.className && element.className.includes('video')) {
      const allVideos = element.querySelectorAll('video');
      for (const v of allVideos) {
        const src = v.src || v.currentSrc;
        if (src) {
          console.log('�� Video trovato in profondità:', src);
          return src;
        }
      }
    }
    
    // Cerca attributi data-* per video
    const videoDataAttrs = ['data-video-url', 'data-video', 'data-src', 'data-url'];
    for (const attr of videoDataAttrs) {
      const val = element.getAttribute(attr);
      if (val && (val.startsWith('http') || val.startsWith('blob:'))) {
        console.log('�� Video da data attribute:', val);
        return val;
      }
    }
    
    // Cerca link a video nel messaggio
    const videoLink = element.querySelector('a[href*=".mp4"], a[href*=".webm"], a[href*=".mov"]');
    if (videoLink?.href) {
      console.log('�� Video da link:', videoLink.href);
      return videoLink.href;
    }
    
    // Audio
    if (element.tagName === 'AUDIO') {
      return element.src || element.currentSrc;
    }
    
    const audioEl = element.querySelector('audio');
    if (audioEl) {
      const src = audioEl.src || audioEl.currentSrc;
      if (src) return src;
      
      const source = audioEl.querySelector('source[src]');
      if (source?.src) return source.src;
    }
    
    // Documenti - cerca URL in vari modi
    if (isDocumentElement(element)) {
      // Strategia 1: Link con href
      const link = element.querySelector('a[href]:not([href^="#"]):not([href^="javascript:"])');
      if (link?.href) {
        // Verifica che sia un URL valido di Telegram
        if (link.href.includes('telegram.org') || link.href.includes('t.me') || link.href.startsWith('blob:')) {
          return link.href;
        }
      }
      
      // Strategia 2: Attributi data-*
      const dataAttrs = ['data-doc-url', 'data-url', 'data-href', 'data-src'];
      for (const attr of dataAttrs) {
        const val = element.getAttribute(attr);
        if (val && (val.startsWith('http') || val.startsWith('blob:'))) {
          return val;
        }
      }
      
      // Strategia 3: Cerca in sottoelementi
      const allLinks = element.querySelectorAll('a[href]');
      for (const link of allLinks) {
        if (link.href && !link.href.includes('#') && !link.href.includes('javascript:')) {
          const ext = guessExtFromUrl(link.href);
          // Se l'URL ha un'estensione valida, probabilmente è il file
          if (FILE_TYPES.documents.includes(ext) || FILE_TYPES.audio.includes(ext)) {
            return link.href;
          }
        }
      }
    }
    
    // Immagini
    const img = element.tagName === 'IMG' ? element : element.querySelector('img');
    if (img) {
      const srcset = img.getAttribute('srcset');
      if (srcset) {
        const list = srcset.split(',').map(p=>p.trim().split(' ')[0]).filter(Boolean);
        if (list.length) return list[list.length-1];
      }
      if (img.src && !img.src.startsWith('data:')) return img.src;
    }
    
    if (element.tagName === 'PICTURE') {
      const s = element.querySelector('source[srcset]');
      if (s?.getAttribute('srcset')) {
        const list = s.getAttribute('srcset').split(',').map(p=>p.trim().split(' ')[0]).filter(Boolean);
        if (list.length) return list[list.length-1];
      }
    }
    
    const bg = getComputedStyle(element).backgroundImage;
    const m = bg && bg.match(/url\(["']?(.*?)["']?\)/);
    if (m && m[1]) return m[1];
    
    return null;
  }

  function extractFileInfo(fileEl) {
    const msg = fileEl.closest('.message,.Message,.bubble,[class*="message"]');
    const ts = getMessageTimestamp(msg);
    
    // Cerca il nome del file
    const nameSelectors = [
      'middle-ellipsis-element', // Telegram K audio
      '.audio-title',             // Telegram K audio container
      'strong',
      '.file-name',
      '.document-name',
      '[class*="filename"]',
      '[class*="FileName"]',
      '[title]',
      '.name'
    ];
    
    let nameEl = null;
    for (const sel of nameSelectors) {
      nameEl = fileEl.querySelector(sel);
      if (nameEl) break;
    }
    
    const baseText = (nameEl?.textContent || nameEl?.getAttribute?.('title') || 'file').trim().replace(/\s+/g,'_');
    
    // Se il nome è solo una durata (es: "10:59" o "0:30"), usa nome generico
    const isDuration = /^\d{1,2}:\d{2}$/.test(baseText);
    const finalName = isDuration ? 'audio' : baseText;
    
    const safeBase = `${fmtTs(ts)}_${finalName}`;
    
    // Cerca URL del file - MULTIPLI TENTATIVI
    let url = null;
    
    // Tentativo 1: extractMediaURL (include strategia documenti)
    url = extractMediaURL(fileEl);
    
    // Tentativo 2: Link diretto con href
    if (!url) {
      const link = fileEl.querySelector('a[href]:not([href^="#"]):not([href^="javascript:"])');
      if (link?.href) {
        url = link.href;
      }
    }
    
    // Tentativo 3: data-doc-url o attributi simili
    if (!url) {
      const urlAttrs = ['data-doc-url', 'data-url', 'data-src', 'data-href', 'data-document-url'];
      for (const attr of urlAttrs) {
        const val = fileEl.getAttribute(attr) || fileEl.querySelector(`[${attr}]`)?.getAttribute(attr);
        if (val && (val.startsWith('http') || val.startsWith('blob:'))) {
          url = val;
          break;
        }
      }
    }
    
    // Tentativo 4: Cerca in tutti i link dell'elemento
    if (!url) {
      const allLinks = Array.from(fileEl.querySelectorAll('a[href]'));
      for (const link of allLinks) {
        if (link.href && !link.href.includes('#') && !link.href.includes('javascript:')) {
          // Verifica se l'URL sembra un file
          if (link.href.includes('telegram.org') || link.href.includes('.pdf') || link.href.includes('.doc')) {
            url = link.href;
            break;
          }
        }
      }
    }
    
    // Cerca pulsante download
    const dlBtnSelectors = [
      'a[download]',
      'button[aria-label*="Download"]',
      'button[aria-label*="download"]',
      'button[title*="Download"]',
      'button[title*="download"]',
      'button.download',
      '[class*="download"][role="button"]',
      '[class*="Download"][role="button"]',
      'i[class*="download"]'
    ];
    
    let downloadBtn = null;
    for (const sel of dlBtnSelectors) {
      downloadBtn = fileEl.querySelector(sel);
      if (downloadBtn) {
        // Verifica che sia effettivamente cliccabile
        if (downloadBtn.tagName === 'A' || downloadBtn.tagName === 'BUTTON' || downloadBtn.onclick || downloadBtn.hasAttribute('role')) {
          break;
        }
        downloadBtn = null;
      }
    }
    
    // Se abbiamo un'icona download, cerca il parent cliccabile
    if (!downloadBtn) {
      const downloadIcon = fileEl.querySelector('[class*="download"]');
      if (downloadIcon) {
        let parent = downloadIcon.parentElement;
        let depth = 0;
        while (parent && depth < 5) {
          if (parent.tagName === 'BUTTON' || parent.tagName === 'A' || parent.onclick || parent.hasAttribute('role')) {
            downloadBtn = parent;
            break;
          }
          parent = parent.parentElement;
          depth++;
        }
      }
    }
    
    console.log('�� ExtractFileInfo risultato:', {
      filename: safeBase,
      hasUrl: !!url,
      urlPreview: url ? url.substring(0, 80) : 'none',
      hasDownloadBtn: !!downloadBtn,
      btnType: downloadBtn ? downloadBtn.tagName : 'none'
    });
    
    return { 
      filename: safeBase, 
      url, 
      downloadBtn,
      element: fileEl,
      ts 
    };
  }

  async function scanChatProgressively() {
    console.log('�� === INIZIO scanChatProgressively ===');
    
    const container = findMessagesContainer();
    if (!container) {
    //  console.error('❌ Container messaggi non trovato!');
      return { found: 0, scanned: 0 };
    }

    console.log('✅ Container trovato:', container.className);

    const typesStr = downloadTypes.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(', ');
    showNotification(`�� Scanning ${typesStr} in chat…`, 'info');
    
    // CRITICO: Scroll in fondo più volte per forzare il caricamento dei messaggi recenti
    console.log('�� Scrolling to bottom to load recent messages...');
    for (let i = 0; i < 3; i++) {
      container.scrollTop = container.scrollHeight;
      await sleep(400);
      console.log(`�� Scroll attempt ${i + 1}/3: scrollTop=${container.scrollTop}, scrollHeight=${container.scrollHeight}`);
    }

    let downloaded = 0;
    let scannedCount = 0;
    const seen = new WeakSet();
    const processedFiles = new Set(); // Traccia filename già processati
    let stable = 0, prevTop = null, prevHeight = -1;
    
    // NUOVO: Stop intelligente per filtri date
    let consecutiveOutOfRange = 0;
    const MAX_CONSECUTIVE_OUT_OF_RANGE = 25; // Se 25 elementi consecutivi fuori range/tipo, stop

    console.log('�� Inizio loop scansione...');

    while (isDownloading) {
      const mediaElements = getAllChatMediaElements();
      console.log(`�� Loop iteration: ${mediaElements.length} elementi trovati`);
      
      let foundInRangeThisIteration = false;
      let newElementsProcessed = 0; // Conta elementi nuovi (non già visti)
      
      for (const el of mediaElements) {
        if (!isDownloading) break;
        if (seen.has(el)) continue;
        seen.add(el);
        newElementsProcessed++; // Elemento nuovo trovato

        const elementType = getElementType(el);
        if (!elementType) {
          console.log('⏭️ Elemento senza tipo riconosciuto');
          consecutiveOutOfRange++;
          continue;
        }
        
        if (!downloadTypes.includes(elementType)) {
          console.log(`⏭️ Tipo ${elementType} non selezionato, skip`);
          consecutiveOutOfRange++;
          continue;
        }

        console.log(`✅ Elemento valido: ${elementType}`);
        scannedCount++;

        const msg = el.closest('.message,.Message,.bubble,[class*="message"]');
        const ts = getMessageTimestamp(msg);

        // Filtra per data range
        if (!passDateFilter(ts)) {
          const tooRecent = dateTo && ts > dateTo;
          const tooOld = dateFrom && ts < dateFrom;
          
          if (tooRecent) {
            // Troppo recente: continua a scrollare indietro senza incrementare counter
            console.log('⏭️ Messaggio troppo recente (dopo dateTo), continuo a scrollare:', new Date(ts).toLocaleDateString());
            continue;
          }
          
          if (tooOld) {
            // Troppo vecchio: incrementa counter e fermati se troppi consecutivi
            consecutiveOutOfRange++;
            console.log('⏭️ Messaggio troppo vecchio (prima di dateFrom):', new Date(ts).toLocaleDateString());
            
            if (consecutiveOutOfRange >= 10) {
              console.log(`⛔ Stop: ${consecutiveOutOfRange} messaggi consecutivi troppo vecchi`);
              showNotification(`⛔ Scrolled past date range`, 'info');
              isDownloading = false;
              break;
            }
            continue;
          }
          
          // Altri casi (non dovrebbe succedere)
          console.log('⏭️ Skipping file outside date range:', new Date(ts).toLocaleDateString());
          continue;
        }
        
        // Reset contatore se troviamo elemento valido
        consecutiveOutOfRange = 0;
        foundInRangeThisIteration = true;

        // Gestione documenti/file
        if (elementType === 'documents' || elementType === 'audio') {
          const info = extractFileInfo(el);
          
          // Check duplicati basato su filename + timestamp
          const fileKey = `${info.filename}_${info.ts}`;
          
          if (processedFiles.has(fileKey)) {
            console.log('⏭️ File già processato, skip:', info.filename);
            continue;
          }
          processedFiles.add(fileKey);
          
          console.log(`${elementType === 'audio' ? '��' : '��'} ${elementType} trovato:`, {
            filename: info.filename,
            timestamp: new Date(info.ts).toLocaleString()
          });
          
          // Usa click destro + "Scarica" per documenti e audio
          console.log('�� Uso click destro per scaricare...');
          
          // Click destro sull'elemento
          const targetElement = el.querySelector('.document, audio-element') || el;
          const contextMenuEvent = new MouseEvent('contextmenu', {
            bubbles: true,
            cancelable: true,
            view: window,
            button: 2
          });
          targetElement.dispatchEvent(contextMenuEvent);
          
          // Aspetta che il menu contestuale appaia (più tempo per audio)
          await sleep(700);
          
          // Cerca il MenuItem download - supporta sia Telegram A che K
          // Telegram A: .MenuItem[role="menuitem"] con i.icon-download
          // Telegram K: .btn-menu-item con span contenente "Download"
          let downloadMenuItem = Array.from(document.querySelectorAll('.MenuItem[role="menuitem"]'))
            .find(item => item.querySelector('i.icon-download'));
          
          if (!downloadMenuItem) {
            // Telegram K
            const menuItems = Array.from(document.querySelectorAll('.btn-menu-item'));
            downloadMenuItem = menuItems.find(item => {
              const text = item.textContent.toLowerCase().trim();
              return text.includes('download') || text.includes('scarica') || text.includes('descargar');
            });
          }
          
          if (downloadMenuItem) {
            console.log('��️ Click su Download menu item');
            downloadMenuItem.click();
            incrementDownloadCount();
            downloaded++;
            await sleep(800); // Aspetta che il download parta e il menu si chiuda
          } else {
            console.warn('⚠️ Download menu item non trovato per documento');
            // Chiudi menu cliccando fuori
            document.body.click();
            await sleep(300);
          }
        } 
        // Gestione media (immagini/video)
        else {
          if (elementType === 'videos') {
            // Check duplicati per video basato su timestamp
            const videoKey = `video_${ts}`;
            if (processedFiles.has(videoKey)) {
              console.log('⏭️ Video già processato, skip');
              continue;
            }
            processedFiles.add(videoKey);
            
            console.log('�� Video trovato, faccio click destro...');
            
            // Trova il video element
            const videoElement = el.querySelector('video') || el;
            
            // Simula click destro (contextmenu)
            const contextMenuEvent = new MouseEvent('contextmenu', {
              bubbles: true,
              cancelable: true,
              view: window,
              button: 2
            });
            videoElement.dispatchEvent(contextMenuEvent);
            
            // Aspetta che il menu contestuale appaia
            await sleep(500);
            
            // Cerca il MenuItem download - supporta sia Telegram A che K
            // Telegram A: .MenuItem[role="menuitem"] con i.icon-download
            // Telegram K: .btn-menu-item con span contenente "Download"
            let downloadMenuItem = Array.from(document.querySelectorAll('.MenuItem[role="menuitem"]'))
              .find(item => item.querySelector('i.icon-download'));
            
            if (!downloadMenuItem) {
              // Telegram K
              downloadMenuItem = Array.from(document.querySelectorAll('.btn-menu-item'))
                .find(item => {
                  const text = item.textContent.toLowerCase().trim();
                  return text.includes('download') || text.includes('scarica') || text.includes('descargar');
                });
            }
            
            if (downloadMenuItem) {
              console.log('��️ Click su Download menu item');
              downloadMenuItem.click();
              incrementDownloadCount();
              downloaded++;
              await sleep(800); // Aspetta che il download parta e il menu si chiuda
            } else {
              console.warn('⚠️ MenuItem con icon-download non trovato');
              // Chiudi menu cliccando fuori
              document.body.click();
              await sleep(300);
            }
          } else {
            // Per immagini usa l'URL normale
            const url = extractMediaURL(el);
            if (url) {
              const ext = guessExtFromUrl(url, 'jpg');
              if (shouldDownloadFile(ext)) {
                const prefix = 'IMG';
                
                console.log('��️ Immagine trovata:', {
                  type: elementType,
                  timestamp: new Date(ts).toLocaleString(),
                  ext: ext,
                  url: url.substring(0, 80) + '...'
                });
                
                const ok = downloadFile(url, `${prefix}_${fmtTs(ts)}_${downloaded}.${ext}`, downloadAsZip);
                if (ok) downloaded++;
              }
            }
          }
        }

        if (downloadCount >= maxDownloads) { 
          isDownloading = false;
          console.log('⛔ Limite massimo download raggiunto');
          break; 
        }
        await sleep(90);
      }

      // Se non stiamo più scaricando (stop manuale o limite raggiunto), esci
      if (!isDownloading) break;

      prevTop = container.scrollTop;
      container.scrollBy({ top: -Math.floor(container.clientHeight * 0.9) });
      await sleep(600);

      const sameHeight = container.scrollHeight === prevHeight;
      const sameTop = container.scrollTop === prevTop;
      prevHeight = container.scrollHeight;
      
      // Check 1: Posizione stabile (non scrolla più) o raggiunto inizio
      if (sameHeight && sameTop) stable++; else stable = 0;
      if (stable >= 3 || container.scrollTop <= 0) {
        console.log('⛔ Stop scroll: raggiunto inizio o posizione stabile');
        break;
      }
      
      // Check 2: Nessun nuovo elemento processato E posizione non cambia
      // (significa che abbiamo già visto tutto ciò che c'è da vedere)
      if (newElementsProcessed === 0 && stable >= 1) {
        console.log('⛔ Stop: nessun nuovo elemento trovato dopo scroll');
        break;
      }
    }

    console.log(`✅ === FINE scanChatProgressively === Scanned: ${scannedCount}, Downloaded: ${downloaded}`);
    
    // Invia stato di completamento al popup
    chrome.runtime.sendMessage({ 
      type: 'downloadComplete', 
      scanned: scannedCount, 
      downloaded: downloaded 
    });
    
    if (!downloadAsZip) showNotification(`✅ Chat scan: ${downloaded} files`, 'success');
    return { found: downloaded, scanned: scannedCount }; // Ritorna entrambi
  }

  /*********** MEDIA PANEL HELPERS ***********/
  
  function elContainsText(el, words) {
    const t = (el.textContent || '').trim().toLowerCase();
    return words.some(w => t.includes(w.toLowerCase()));
  }

  function findHeaderButtons() {
    const scopes = ['header','.topbar','.TopBar','.header','.chat-header','.peer-title','.chat-info'];
    const arr = [];
    for (const sel of scopes) document.querySelectorAll(sel+' button,'+sel+' [role="button"]').forEach(b=>arr.push(b));
    return Array.from(new Set(arr));
  }

  async function openRightInfoPanel() {
    if (document.querySelector('[role="tablist"],.tabs,.profile-tabs')) return true;

    const btns = findHeaderButtons();
    for (const b of btns) {
      const label = (b.getAttribute('aria-label') || b.getAttribute('title') || b.textContent || '').toLowerCase();
      if (LABELS.INFO.some(x => label.includes(x.toLowerCase()))) {
        b.click(); await sleep(350);
        if (document.querySelector('[role="tablist"],.tabs')) return true;
      }
    }
    
    const headerAreas = ['.chat-header','.TopBar','.topbar','header','.peer-title','.chat-info'];
    for (const sel of headerAreas) {
      const el = document.querySelector(sel);
      if (el) { 
        el.click(); 
        await sleep(350); 
        if (document.querySelector('[role="tablist"],.tabs')) return true; 
      }
    }
    return !!document.querySelector('[role="tablist"],.tabs');
  }

  function clickTabByLabel(words) {
    const tabs = Array.from(document.querySelectorAll('[role="tab"], .tabs *'));
    for (const t of tabs) { 
      if (elContainsText(t, words)) { 
        t.click(); 
        return true; 
      } 
    }
    
    const side = document.querySelector('.sidebar,.side,.third-column,[class*="Sidebar"],[class*="Profile"]') || document;
    const candidates = Array.from(side.querySelectorAll('button,[role="button"],a,.item'));
    for (const c of candidates) { 
      if (elContainsText(c, words)) { 
        c.click(); 
        return true; 
      } 
    }
    return false;
  }

  function findScrollableWithMany(root, selector, minCount=5) {
    const list = Array.from(root.querySelectorAll('div,section,main,ul'));
    const scrollables = list.filter(el => {
      const st = getComputedStyle(el);
      return /(auto|scroll)/.test(st.overflowY) && el.scrollHeight > el.clientHeight;
    });
    
    let best=null, score=-1;
    for (const el of scrollables) {
      const c = el.querySelectorAll(selector).length;
      if (c>score){best=el;score=c;}
    }
    return (score>=minCount)?best:null;
  }

  async function downloadFromViewerOrThumb(thumbEl, idx, expectedType) {
    thumbEl.click();
    await sleep(320);

    const dlSelectors = [
      'button[aria-label*="Download"]','button[title*="Download"]','button.download',
      '[class*="download"] button','[class*="Download"]'
    ];
    
    let clicked = false;
    for (const sel of dlSelectors) {
      const b = document.querySelector(sel);
      if (b) { 
        b.click(); 
        incrementDownloadCount(); 
        clicked = true; 
        break; 
      }
    }

    if (!clicked) {
      let url = null;
      const viewer = document.querySelector('.modal, .viewer, [class*="Viewer"]');
      
      if (expectedType === 'videos') {
        const vid = viewer?.querySelector('video') || document.querySelector('video');
        url = vid ? (vid.currentSrc || vid.src) : null;
      } else if (expectedType === 'images') {
        const bigImg = viewer?.querySelector('img') || document.querySelector('img[loading="eager"]');
        url = bigImg ? (bigImg.currentSrc || bigImg.src) : null;
      }
      
      if (!url) {
        url = extractMediaURL(thumbEl);
      }
      
      if (url) {
        const ext = guessExtFromUrl(url, expectedType === 'videos' ? 'mp4' : 'jpg');
        if (shouldDownloadFile(ext)) {
          const prefix = expectedType === 'videos' ? 'VID' : 'IMG';
          downloadFile(url, `${prefix}_${fmtTs(Date.now())}_${idx}.${ext}`, downloadAsZip);
        }
      }
    }

    const closeBtn = document.querySelector('button[aria-label*="Close"], .modal [class*="close"], .viewer [class*="close"]');
    if (closeBtn) closeBtn.click(); 
    else document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
    
    await sleep(160);
  }

  /*********** MEDIA PANEL SCAN ***********/
  
  async function scanMediaPanel() {
    showNotification('��️ Opening Media panel…', 'info');
    const ok = await openRightInfoPanel();
    if (!ok) { 
      showNotification('❌ Cannot open Info/Media panel', 'error'); 
      return { found: 0 }; 
    }

    if (!clickTabByLabel(LABELS.MEDIA)) { 
      showNotification('❌ Media tab not found', 'error'); 
      return { found: 0 }; 
    }
    await sleep(320);

    const panel = document.querySelector('.sidebar,.side,.third-column,[class*="Sidebar"],[class*="Profile"]') || document;
    
    let downloaded = 0;
    
    // Scan per immagini e video se richiesti
    if (downloadTypes.includes('images') || downloadTypes.includes('videos')) {
      const grid = findScrollableWithMany(panel, 'img, video, picture, [style*="background-image"]', 3);
      if (grid) {
        showNotification('�� Scanning images/videos in Media panel…', 'info');
        
        const seen = new WeakSet();
        grid.scrollTop = grid.scrollHeight;
        await sleep(350);

        while (isDownloading) {
          const thumbs = Array.from(grid.querySelectorAll('img, video, picture, [style*="background-image"]'))
            .map(el => el.tagName === 'SOURCE' ? (el.parentElement||el) : el);

          for (const t of thumbs) {
            if (!isDownloading) break;
            if (seen.has(t)) continue;
            seen.add(t);

            const type = getElementType(t);
            if (!type || !downloadTypes.includes(type)) continue;

            await downloadFromViewerOrThumb(t, downloaded, type);
            downloaded++;

            if (downloadCount >= maxDownloads) { 
              isDownloading = false; 
              break; 
            }
            await sleep(120);
          }

          const prev = grid.scrollTop;
          grid.scrollBy({ top: -Math.floor(grid.clientHeight*0.9) });
          await sleep(600);
          if (grid.scrollTop===prev || grid.scrollTop<=0) break;
        }
      }
    }

    // Scan per documenti e audio se richiesti
    if (downloadTypes.includes('documents') || downloadTypes.includes('audio')) {
      if (!clickTabByLabel(LABELS.FILES)) {
        clickTabByLabel(LABELS.MEDIA);
        await sleep(250);
        clickTabByLabel(LABELS.FILES);
      }
      await sleep(320);

      const list = findScrollableWithMany(panel, 'a,button,[class*="file"],[class*="Document"]', 2) || panel;
      
      showNotification('�� Scanning files in Files panel…', 'info');

      const seen = new WeakSet();
      list.scrollTop = 0;
      await sleep(280);

      while (isDownloading) {
        const rows = Array.from(list.querySelectorAll('.Document,.document,.File,.file,.row,.list-item,[class*="file"],[class*="Document"]'));
        
        for (const r of rows) {
          if (!isDownloading) break;
          if (seen.has(r)) continue;
          seen.add(r);

          const type = getElementType(r);
          if (!type || !downloadTypes.includes(type)) continue;

          const info = extractFileInfo(r);
          if (info.url) {
            const ext = guessExtFromUrl(info.url, 'bin');
            if (shouldDownloadFile(ext)) {
              const ok = downloadFile(info.url, `${info.filename}.${ext}`, downloadAsZip);
              if (ok) downloaded++;
            }
          } else if (info.downloadBtn) {
            info.downloadBtn.click();
            incrementDownloadCount();
            downloaded++;
          }

          if (downloadCount >= maxDownloads) { 
            isDownloading = false; 
            break; 
          }
          await sleep(140);
        }

        const prev = list.scrollTop;
        list.scrollBy({ top: list.clientHeight*0.9 });
        await sleep(620);
        if (list.scrollTop===prev || list.scrollTop+list.clientHeight>=list.scrollHeight-2) break;
      }
    }

    if (!downloadAsZip) showNotification(`✅ Media panel: ${downloaded} files`, 'success');
    return { found: downloaded };
  }

  /*********** ORCHESTRATION ***********/
  
  async function downloadAllMedia() {
    console.log('�� START downloadAllMedia');
    console.log('�� Config:', {
      scanMode,
      downloadTypes,
      downloadAsZip,
      dateFrom: dateFrom ? new Date(dateFrom).toLocaleString() : 'none',
      dateTo: dateTo ? new Date(dateTo).toLocaleString() : 'none'
    });
    
    // Notifica popup che il download è iniziato
    chrome.runtime.sendMessage({ type: 'downloadStarted' });

    if (scanMode === 'media') {
      console.log('�� Mode: Media panel only');
      await scanMediaPanel();
      if (downloadAsZip && zipFiles.length) await createAndDownloadZip();
      return;
    }
    
    if (scanMode === 'chat') {
      console.log('�� Mode: Chat only');
      await scanChatProgressively();
      if (downloadAsZip && zipFiles.length) await createAndDownloadZip();
      return;
    }
    
    // Auto mode - prova prima la chat, poi il media panel SOLO se NON ha trovato elementi nella chat
    console.log('�� Mode: Auto (chat first)');
    const chat = await scanChatProgressively();
    
    console.log('�� Chat scan result:', {
      downloaded: chat.found,
      scanned: chat.scanned
    });
    
    // SOLO se NON ha trovato NESSUN elemento valido nella chat (scanned === 0), prova il media panel
    // Se ha trovato elementi ma sono stati scartati per data, NON prova il media panel
    if (isDownloading && chat.scanned === 0) {
      console.log('⚠️ Nessun elemento trovato in chat, provo media panel...');
      await scanMediaPanel();
    } else if (dateFrom || dateTo) {
      // SOLO se c'è un filtro date mostra il messaggio
      if (chat.scanned > 0 && chat.found === 0) {
        console.log('ℹ️ Trovati', chat.scanned, 'elementi ma tutti fuori dal range date');
        showNotification(`ℹ️ Found ${chat.scanned} files but all outside date range`, 'info');
      }
    } else {
      console.log('✅ Scan completato dalla chat');
    }
    
    if (downloadAsZip && zipFiles.length) {
      console.log('�� Creazione ZIP con', zipFiles.length, 'files');
      await createAndDownloadZip();
    } else if (zipFiles.length === 0 && chat.found === 0 && chat.scanned > 0) {
      console.warn('⚠️ File trovati ma nessun URL disponibile per ZIP');
      showNotification('⚠️ Files found but no direct URL available for download', 'warning');
    }
    
    console.log('�� FINE downloadAllMedia');
  }

  /*********** MESSAGES ***********/
  
  chrome.runtime.onMessage.addListener((request) => {
    console.log('�� Messaggio ricevuto:', request.type);
    
    if (request.type === 'startDownload') {
      console.log('�� START DOWNLOAD request ricevuto');
      
      if (!isChatSelected()) { 
      //  console.error('❌ Chat non selezionata!');
        showNotification('❌ Please select a Chat/Group first!', 'error');
        chrome.runtime.sendMessage({ 
          type: 'error', 
          message: '❌ Please select a Chat or Group first!' 
        });
        return; 
      }

      console.log('✅ Chat selezionata, procedo...');

      // Notifica popup che il download è iniziato
      chrome.runtime.sendMessage({ type: 'downloadStarted' });

      // Sync PRO state before starting
      chrome.storage?.local?.get(['isFullVersion'], (res) => {
        setProMode(!!res?.isFullVersion);
        console.log('�� PRO mode:', isPro);

        isDownloading = true;
        dateFrom = request.dateFrom || null;
        dateTo = request.dateTo || null;
        downloadAsZip = !!request.downloadAsZip;
        scanMode = request.scanMode || 'auto';
        downloadTypes = request.downloadTypes || ['images']; // Default a images
        
        console.log('�� Configurazione:', {
          dateFrom: dateFrom ? new Date(dateFrom).toLocaleString() : 'none',
          dateTo: dateTo ? new Date(dateTo).toLocaleString() : 'none',
          downloadAsZip,
          scanMode,
          downloadTypes
        });
        
        zipFiles = [];
        downloadedURLs.clear();

        downloadAllMedia();
      });

    } else if (request.type === 'stopDownload') {
      console.log('⛔ STOP DOWNLOAD request');
      isDownloading = false;
      
      showNotification('⛔ Download stopped', 'info');
      if (downloadAsZip && zipFiles.length > 0) {
        setTimeout(async () => { 
          if (!isDownloading && zipFiles.length) {
            console.log('�� Creazione ZIP dopo stop:', zipFiles.length, 'files');
            await createAndDownloadZip();
          }
        }, 250);
      }
    }
  });

  console.log('✅ Telegram Downloader loaded');
})();
console 命令行工具 X clear

                    
>
console