/**
 * Download Manager
 * 
 * Handles file download queue, progress tracking, and storage.
 * Downloads files via presigned S3 URLs from Filevine.
 */

import { STORAGE_KEYS, MESSAGE_TYPES } from '../lib/constants.js';
import { filevineClient } from './api.js';
import { webApiClient } from './web-api-client.js';
import { authManager } from './auth.js';

class DownloadManager {
  constructor() {
    this.queue = [];
    this.activeDownloads = new Map();
    this.maxConcurrent = 3;
    this.dbName = 'filevine_docs';
    this.db = null;
  }

  /**
   * Initialize IndexedDB for file storage
   */
  async initializeDb() {
    if (this.db) return this.db;

    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      
      request.onerror = () => reject(request.error);
      
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;

        // Store for file blobs
        if (!db.objectStoreNames.contains('files')) {
          const fileStore = db.createObjectStore('files', { keyPath: 'docId' });
          fileStore.createIndex('projectId', 'projectId');
          fileStore.createIndex('downloadedAt', 'downloadedAt');
        }

        // Store for file metadata
        if (!db.objectStoreNames.contains('metadata')) {
          const metaStore = db.createObjectStore('metadata', { keyPath: 'docId' });
          metaStore.createIndex('filename', 'filename');
          metaStore.createIndex('mimeType', 'mimeType');
        }

        // Store for DICOM image series (multiple images from a folder)
        if (!db.objectStoreNames.contains('series')) {
          const seriesStore = db.createObjectStore('series', { keyPath: 'seriesId' });
          seriesStore.createIndex('projectId', 'projectId');
          seriesStore.createIndex('folderId', 'folderId');
          seriesStore.createIndex('createdAt', 'createdAt');
        }
      };
    });
  }

  /**
   * Add a file to the download queue
   */
  async queueDownload({ docId, projectId, filename, mimeType, autoOpen = false, folderPath = null }) {
    // Check if already queued or downloading
    if (this.queue.find(d => d.docId === docId) || this.activeDownloads.has(docId)) {
      return { queued: false, reason: 'Already in queue' };
    }

    console.log('[Download] Queueing download - docId:', docId, 'filename:', filename, 'folderPath:', folderPath, 'mimeType:', mimeType);

    const download = {
      docId,
      projectId,
      filename: filename || 'download', // Ensure we have a filename
      mimeType,
      folderPath: folderPath || [], // Array of folder names representing the path
      autoOpen,
      status: 'queued',
      progress: 0,
      queuedAt: Date.now(),
      error: null
    };

    this.queue.push(download);
    this.broadcastState();
    
    // Start processing if capacity available
    this.processQueue();

    return { queued: true, download };
  }

  /**
   * Process queued downloads
   */
  async processQueue() {
    while (
      this.queue.length > 0 && 
      this.activeDownloads.size < this.maxConcurrent
    ) {
      const download = this.queue.shift();
      this.activeDownloads.set(download.docId, download);
      
      // Start download without blocking queue processing
      this.executeDownload(download).catch(error => {
        console.error('Download failed:', error);
      });
    }
  }

  /**
   * Execute a single download
   */
  async executeDownload(download) {
    try {
      download.status = 'downloading';
      this.broadcastState();

      // Get presigned S3 URL from Filevine
      // Use web API client if using cookie-based auth, otherwise use external API client
      const authState = authManager.getState();
      let downloadInfo;
      
      if (authState.auth?.usingWebApi) {
        console.log('[Download] Using web API client for download URL');
        downloadInfo = await webApiClient.getDocumentDownloadUrl(download.docId, download.projectId);
      } else {
        console.log('[Download] Using external API client for download URL');
        downloadInfo = await filevineClient.getDocumentDownloadUrl(download.docId);
      }
      
      const downloadUrl = downloadInfo.url || downloadInfo.downloadUrl;
      
      if (!downloadUrl) {
        throw new Error('No download URL returned from API');
      }
      
      console.log('[Download] Got download URL:', downloadUrl.substring(0, 100) + '...');

      // Prepare fetch options
      const fetchOptions = {
        credentials: 'include' // Include cookies for web API endpoints
      };
      
      // If using web API (cookie-based auth), include cookie in headers
      if (authState.auth?.usingWebApi && downloadUrl.includes('product.filevineapp.com')) {
        if (authState.auth?.cookie) {
          fetchOptions.headers = {
            'Cookie': authState.auth.cookie
          };
        }
      }

      // Fetch file from S3 or web API endpoint
      const response = await fetch(downloadUrl, fetchOptions);
      
      if (!response.ok) {
        throw new Error(`Download failed: ${response.status}`);
      }

      // Track progress via content-length if available
      const contentLength = response.headers.get('content-length');
      const totalBytes = contentLength ? parseInt(contentLength) : 0;
      let loadedBytes = 0;

      // Read response as stream for progress tracking
      const reader = response.body.getReader();
      const chunks = [];

      while (true) {
        const { done, value } = await reader.read();
        
        if (done) break;
        
        chunks.push(value);
        loadedBytes += value.length;
        
        if (totalBytes > 0) {
          download.progress = Math.round((loadedBytes / totalBytes) * 100);
          this.broadcastState();
        }
      }

      // Combine chunks into blob
      const blob = new Blob(chunks, { type: download.mimeType || 'application/octet-stream' });

      // Store in IndexedDB
      await this.storeFile(download, blob);

      // Mark complete
      download.status = 'complete';
      download.progress = 100;
      download.completedAt = Date.now();

      // Notify completion
      this.broadcastDownloadComplete(download);

      // Auto-open if requested
      if (download.autoOpen) {
        this.openFile(download.docId);
      }

    } catch (error) {
      download.status = 'error';
      download.error = error.message;
      console.error(`Download error for ${download.docId}:`, error);
      
      chrome.runtime.sendMessage({
        type: MESSAGE_TYPES.DOWNLOAD_ERROR,
        docId: download.docId,
        error: error.message
      }).catch(() => {});
      
    } finally {
      this.activeDownloads.delete(download.docId);
      this.processQueue(); // Check for more queued downloads
      this.broadcastState();
    }
  }

  /**
   * Store downloaded file in IndexedDB
   */
  async storeFile(download, blob) {
    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['files', 'metadata'], 'readwrite');
      
      transaction.onerror = () => reject(transaction.error);
      transaction.oncomplete = () => resolve();

      // Store file blob
      transaction.objectStore('files').put({
        docId: download.docId,
        projectId: download.projectId,
        blob: blob,
        downloadedAt: Date.now()
      });

      // Store metadata - preserve original filename and folder path
      console.log('[Download] Storing file metadata - docId:', download.docId, 'filename:', download.filename, 'folderPath:', download.folderPath, 'mimeType:', download.mimeType);
      
      transaction.objectStore('metadata').put({
        docId: download.docId,
        projectId: download.projectId,
        filename: download.filename || 'download', // Use original filename
        folderPath: download.folderPath || [], // Store folder path
        mimeType: download.mimeType,
        size: blob.size,
        downloadedAt: Date.now()
      });
    });
  }

  /**
   * Get a downloaded file
   */
  async getFile(docId) {
    if (!docId) {
      console.warn('[Download] getFile called with invalid docId:', docId);
      return null;
    }

    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['files', 'metadata'], 'readonly');

      const fileRequest = transaction.objectStore('files').get(docId);
      const metaRequest = transaction.objectStore('metadata').get(docId);

      transaction.oncomplete = () => {
        if (fileRequest.result && metaRequest.result) {
          console.log('[Download] Retrieved file from IndexedDB');
          console.log('[Download] Metadata filename:', metaRequest.result.filename);
          console.log('[Download] Full metadata:', JSON.stringify(metaRequest.result, null, 2));
          resolve({
            blob: fileRequest.result.blob,
            metadata: metaRequest.result
          });
        } else {
          console.log('[Download] File not found in IndexedDB - fileRequest:', fileRequest.result, 'metaRequest:', metaRequest.result);
          resolve(null);
        }
      };

      transaction.onerror = () => reject(transaction.error);
    });
  }

  /**
   * Store a DICOM series (multiple images from a folder)
   */
  async storeSeries({ seriesId, projectId, folderId, folderName, docIds, imageCount }) {
    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['series'], 'readwrite');

      transaction.onerror = () => reject(transaction.error);
      transaction.oncomplete = () => resolve();

      transaction.objectStore('series').put({
        seriesId,
        projectId,
        folderId,
        folderName,
        docIds, // Array of document IDs in the series
        imageCount,
        createdAt: Date.now()
      });
    });
  }

  /**
   * Get a DICOM series by ID
   */
  async getSeries(seriesId) {
    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['series'], 'readonly');
      const request = transaction.objectStore('series').get(seriesId);

      transaction.oncomplete = () => {
        if (request.result) {
          console.log('[Download] Retrieved series from IndexedDB:', seriesId);
          resolve(request.result);
        } else {
          console.log('[Download] Series not found:', seriesId);
          resolve(null);
        }
      };

      transaction.onerror = () => reject(transaction.error);
    });
  }

  /**
   * Get complete folder tree for a project
   * Shows ALL folders in the project, even if empty
   *
   * SIMPLIFIED: Just get the folder tree structure - no document filtering
   */
  async getProjectDicomFiles(projectId) {
    try {
      console.log('[Download] 📂 Fetching complete folder tree for project', projectId);

      // Get auth state to determine which API client to use
      const authState = await authManager.getState();

      if (!authState.auth?.usingWebApi) {
        console.log('[Download] ⚠️  Web API not available, falling back to standard API');
        const response = await filevineClient.getProjectDocuments(projectId, {
          limit: 1000
        });
        const allDocuments = response.items || [];
        return this.groupDocumentsByFolder(allDocuments);
      }

      // Step 1: Get the first level of folders
      console.log('[Download] Step 1: Fetching first level of folder tree...');

      const firstLevelTree = await webApiClient.getProjectFolderTree(projectId, {
        descendantFolderIDs: [],
        maxChildrenPerFolder: 500
      });

      console.log('[Download] ✅ First level folder tree response:');
      console.log('[Download]   Root folder ID:', firstLevelTree.rootFolderID);
      console.log('[Download]   First level folder count:', Object.keys(firstLevelTree.folderNameMap || {}).length);

      // Step 2: Get ALL folder IDs to fetch the complete tree with all descendants
      const allFolderIds = Object.keys(firstLevelTree.folderNameMap || {}).map(id => Number(id));
      console.log('[Download] Step 2: Fetching complete tree with', allFolderIds.length, 'folder IDs...');

      const folderTree = await webApiClient.getProjectFolderTree(projectId, {
        descendantFolderIDs: allFolderIds,
        maxChildrenPerFolder: 500
      });

      console.log('[Download] ✅ Complete folder tree response:');
      console.log('[Download]   Root folder ID:', folderTree.rootFolderID);
      console.log('[Download]   Children lookup keys:', Object.keys(folderTree.childrenLookup || {}).length);
      console.log('[Download]   Folder lookup keys:', Object.keys(folderTree.folderLookup || {}).length);
      console.log('[Download]   Total folders in map:', Object.keys(folderTree.folderNameMap || {}).length);

      // Step 3: Build a flat list of ALL folders from the complete tree
      const folders = this.buildFolderListFromTree(folderTree);

      console.log('[Download] ✅ Built', folders.length, 'folders from tree');
      folders.forEach(folder => {
        console.log(`[Download]   - ${folder.folderName} (ID: ${folder.folderId})`);
      });

      // Step 4: Fetch documents for each folder
      console.log('[Download] 📄 Fetching documents for', folders.length, 'folders...');
      await this.fetchDocumentsForFolders(folders, projectId, webApiClient);

      // Step 5: Filter out folders without DICOM files (keep parent folders if children have files)
      const foldersWithDicom = this.filterFoldersWithDicomFiles(folders);

      console.log('[Download] 📁 Filtered from', folders.length, 'to', foldersWithDicom.length, 'folders with DICOM files');

      return foldersWithDicom;

    } catch (error) {
      console.error('[Download] ❌ Failed to fetch folder tree:', error);
      throw error;
    }
  }

  /**
   * Build a flat list of folders from the folder tree structure
   * Uses folderNameMap (from folderTree) instead of folderLookup (usually empty)
   */
  buildFolderListFromTree(folderTree) {
    const folders = [];
    const { rootFolderID, childrenLookup, folderNameMap } = folderTree;

    console.log('[Download] 🌲 Building folder list from tree...');
    console.log('[Download]   Root folder ID:', rootFolderID);
    console.log('[Download]   folderNameMap has', Object.keys(folderNameMap || {}).length, 'entries');
    console.log('[Download]   childrenLookup has', Object.keys(childrenLookup || {}).length, 'entries');

    // Log childrenLookup structure for debugging
    if (childrenLookup) {
      console.log('[Download]   childrenLookup keys:', Object.keys(childrenLookup));
      console.log('[Download]   Full childrenLookup structure:');
      for (const [parentId, children] of Object.entries(childrenLookup)) {
        const parentName = folderNameMap[parentId] || folderNameMap[String(parentId)] || folderNameMap[Number(parentId)] || 'Unknown';
        console.log(`[Download]     ${parentId} (${parentName}) -> ${children.length} children:`, children);
      }
      const rootChildren = childrenLookup[rootFolderID] || childrenLookup[String(rootFolderID)] || [];
      console.log('[Download]   Root folder has', rootChildren.length, 'direct children:', rootChildren);
    }

    // Log all folders in folderNameMap
    console.log('[Download]   All folders in folderNameMap:');
    for (const [folderId, folderName] of Object.entries(folderNameMap)) {
      console.log(`[Download]     ${folderId} -> ${folderName}`);
    }

    if (!folderNameMap || Object.keys(folderNameMap).length === 0) {
      console.warn('[Download] ⚠️  No folders in folderNameMap');
      return folders;
    }

    // Traverse the tree starting from root
    const traverse = (folderId, path = [], depth = 0) => {
      // Get folder name from folderNameMap - try both number and string keys
      const folderIdStr = String(folderId);
      const folderIdNum = Number(folderId);
      const folderName = folderNameMap[folderId] || folderNameMap[folderIdStr] || folderNameMap[folderIdNum];

      if (!folderName) {
        console.warn('[Download] ⚠️  Folder name not found for ID:', folderId);
        return;
      }

      const indent = '  '.repeat(depth);
      console.log(`[Download] ${indent}📁 ${folderName} (ID: ${folderId})`);

      // Add folder to list with full path for display
      const currentPath = [...path, folderName];
      const displayName = depth > 0 ? `${'  '.repeat(depth)}${folderName}` : folderName;

      folders.push({
        folderId: folderId,
        folderName: displayName, // Indented name for display
        folderPath: currentPath, // Full path array
        files: [], // Will be populated later if needed
        depth: depth // Track depth for UI rendering
      });

      // Recursively process children - try all key types
      const childIds = (childrenLookup && (
        childrenLookup[folderId] ||
        childrenLookup[folderIdStr] ||
        childrenLookup[folderIdNum]
      )) || [];
      console.log(`[Download] ${indent}  └─ ${childIds.length} children`);

      for (const childId of childIds) {
        traverse(childId, currentPath, depth + 1);
      }
    };

    // Start traversal from root
    if (rootFolderID) {
      console.log('[Download] Starting tree traversal from root:', rootFolderID);
      traverse(rootFolderID, [], 0);
    } else {
      console.warn('[Download] ⚠️  No root folder ID found, cannot traverse tree');
    }

    console.log('[Download] ✅ Built folder tree with', folders.length, 'total folders');

    return folders;
  }

  /**
   * Fetch documents for each folder and populate the files array
   */
  async fetchDocumentsForFolders(folders, projectId, webApiClient) {
    console.log('[Download] 📄 Starting document fetch for', folders.length, 'folders...');

    let totalDocuments = 0;
    let foldersWithFiles = 0;

    // Process folders in batches to avoid overwhelming the API
    const BATCH_SIZE = 5;
    for (let i = 0; i < folders.length; i += BATCH_SIZE) {
      const batch = folders.slice(i, i + BATCH_SIZE);

      // Fetch documents for each folder in the batch concurrently
      await Promise.all(batch.map(async (folder) => {
        try {
          console.log(`[Download]   Fetching documents for folder ${folder.folderId}: ${folder.folderName.trimStart()}`);

          // Fetch documents for this folder
          const response = await webApiClient.getProjectDocuments(projectId, {
            folderID: folder.folderId,
            limit: 500 // Get up to 500 documents per folder
          });

          if (response && response.items) {
            // Log first document structure for debugging
            if (response.items.length > 0) {
              console.log(`[Download]   First document structure:`, JSON.stringify(response.items[0], null, 2));
            }

            // Filter for DICOM files (.dcm extension)
            const dicomFiles = response.items.filter(doc => {
              const filename = doc.filename || doc.name || '';
              return filename.toLowerCase().endsWith('.dcm');
            });

            // Store document metadata in the folder
            folder.files = dicomFiles.map(doc => {
              const docId = doc.docId || doc.documentID || doc.documentId || doc.id;
              if (!docId) {
                console.warn('[Download]   ⚠️  Document missing ID:', doc);
              }
              return {
                docId: docId,
                filename: doc.filename || doc.name,
                size: doc.size || 0,
                uploadedBy: doc.uploadedBy,
                uploadedAt: doc.uploadedAt || doc.uploadDate
              };
            });

            totalDocuments += dicomFiles.length;
            if (dicomFiles.length > 0) {
              foldersWithFiles++;
              console.log(`[Download]   ✅ Found ${dicomFiles.length} DICOM files in folder ${folder.folderId}`);
            }
          } else {
            console.log(`[Download]   ⚠️  No documents response for folder ${folder.folderId}`);
            folder.files = [];
          }
        } catch (error) {
          console.error(`[Download]   ❌ Error fetching documents for folder ${folder.folderId}:`, error);
          folder.files = []; // Set empty array on error
        }
      }));

      // Log progress
      const processed = Math.min(i + BATCH_SIZE, folders.length);
      console.log(`[Download]   Progress: ${processed}/${folders.length} folders processed`);
    }

    console.log(`[Download] ✅ Document fetch complete: ${totalDocuments} DICOM files found in ${foldersWithFiles}/${folders.length} folders`);
  }

  /**
   * Filter folders to only include those with DICOM files or that have descendants with DICOM files
   */
  filterFoldersWithDicomFiles(folders) {
    console.log('[Download] 🔍 Filtering folders with DICOM files...');

    // First, identify all folders that have DICOM files directly
    const foldersWithFiles = new Set();
    folders.forEach(folder => {
      if (folder.files && folder.files.length > 0) {
        foldersWithFiles.add(folder.folderId);
        console.log(`[Download]   ✓ ${folder.folderName.trimStart()} has ${folder.files.length} DICOM files`);
      }
    });

    // Build a map of folder IDs to their folder objects for quick lookup
    const folderMap = new Map();
    folders.forEach(folder => {
      folderMap.set(folder.folderId, folder);
    });

    // For each folder with files, mark all its ancestors as needed
    const foldersToKeep = new Set();
    foldersWithFiles.forEach(folderId => {
      foldersToKeep.add(folderId);

      // Walk up the tree and mark all parent folders
      const folder = folderMap.get(folderId);
      if (folder && folder.folderPath) {
        // Find all parent folders by matching folderPath
        folders.forEach(potentialParent => {
          // A folder is a parent if its path is a prefix of this folder's path
          if (potentialParent.depth < folder.depth &&
              folder.folderPath.length > potentialParent.folderPath.length &&
              folder.folderPath.slice(0, potentialParent.folderPath.length).every((name, i) => name === potentialParent.folderPath[i])) {
            foldersToKeep.add(potentialParent.folderId);
          }
        });
      }
    });

    // Filter folders to only those we want to keep
    const filtered = folders.filter(folder => foldersToKeep.has(folder.folderId));

    console.log(`[Download] ✅ Keeping ${filtered.length} folders (${foldersWithFiles.size} with files, ${filtered.length - foldersWithFiles.size} parent folders)`);

    return filtered;
  }

  /**
   * Group documents by folder (no filtering)
   */
  groupDocumentsByFolder(allDocuments) {
    console.log('[Download] 📊 Grouping', allDocuments.length, 'documents by folder...');

    // Group ALL documents by folder (no DICOM filtering)
    const folderMap = new Map();

    for (const doc of allDocuments) {
      const folderId = doc.folderID || doc.folderId || 'root';
      const folderName = doc.folderName || doc.folder || 'Root Folder';

      if (!folderMap.has(folderId)) {
        folderMap.set(folderId, {
          folderId: folderId,
          folderName: folderName,
          folderPath: [folderName],
          files: []
        });
      }

      folderMap.get(folderId).files.push({
        docId: doc.docId || doc.id,
        filename: doc.filename || doc.name || 'Untitled',
        uploadedAt: doc.uploadedAt || doc.uploadDate || doc.createdAt,
        mimeType: doc.mimeType || doc.contentType
      });
    }

    // Convert to array and sort folders
    const folders = Array.from(folderMap.values()).sort((a, b) => {
      return a.folderName.localeCompare(b.folderName);
    });

    console.log('[Download] 📂 Grouped into', folders.length, 'folders:');
    folders.forEach(folder => {
      console.log(`[Download]   - ${folder.folderName}: ${folder.files.length} files`);
    });

    return folders;
  }

  /**
   * Open a file in the appropriate viewer
   *
   * Note: Service workers don't support URL.createObjectURL, so we convert
   * blobs to data URLs. For very large files, we use chrome.downloads API.
   */
  async openFile(docId) {
    const file = await this.getFile(docId);
    
    if (!file) {
      throw new Error('File not found in storage');
    }

    console.log('[Download] Opening file:', file.metadata.filename, 'MIME:', file.metadata.mimeType);

    // Convert blob to data URL (works in service workers)
    // Data URLs have size limits, so for very large files we'll download instead
    const maxDataUrlSize = 10 * 1024 * 1024; // 10MB limit (browsers typically support up to 2MB, but let's try)
    
    // Check if this file type should be opened in browser or downloaded
    const ext = file.metadata.filename?.toLowerCase().split('.').pop() || '';
    const mime = file.metadata.mimeType?.toLowerCase() || '';
    
    // Check if there's a viewer for this file type (e.g., DICOM viewer)
    const hasViewer = ext === 'dcm' || ext === 'dicom' || mime === 'application/dicom';
    
    // Files that should open in browser: HTML, images, text files, or files with viewers
    const shouldOpenInBrowser = ext === 'html' || 
                                 mime.startsWith('image/') || 
                                 mime.startsWith('text/') ||
                                 hasViewer;
    
    if (file.blob.size <= maxDataUrlSize && shouldOpenInBrowser) {
      try {
        // Convert blob to data URL for browser-displayable files
        console.log('[Download] File size is', file.blob.size, 'bytes, creating data URL for browser display...');
        const dataUrl = await this.createDataUrl(file.blob, file.metadata);
        console.log('[Download] Data URL created successfully, length:', dataUrl.length);
        
        // Determine viewer based on file type
        const viewerUrl = this.getViewerUrl(file.metadata, dataUrl);
        console.log('[Download] Viewer URL:', viewerUrl);
        
        // Open in new tab
        chrome.tabs.create({ url: viewerUrl });
        console.log('[Download] Opened file in new tab');
        
        return { opened: true, viewerUrl };
      } catch (error) {
        console.error('[Download] Error creating data URL, falling back to download:', error);
        console.error('[Download] Error details:', error.message, error.stack);
        // Fall through to download
      }
    } else {
      if (file.blob.size > maxDataUrlSize) {
        console.log('[Download] File size is', file.blob.size, 'bytes, too large for data URL, using chrome.downloads');
      } else {
        console.log('[Download] File type', ext, 'should be downloaded, not opened in browser');
      }
    }
    
    // Large file or data URL creation failed - use chrome.downloads API
    const dataUrl = await this.createDataUrl(file.blob, file.metadata);
    if (dataUrl) {
      // Build filename with folder path if available
      let downloadFilename = file.metadata.filename || 'download';
      
      // If folder path exists, prepend it to the filename
      if (file.metadata.folderPath && Array.isArray(file.metadata.folderPath) && file.metadata.folderPath.length > 0) {
        // Sanitize folder names for filesystem (remove invalid characters)
        const sanitizeFolderName = (name) => {
          return name.replace(/[<>:"/\\|?*]/g, '_').trim();
        };
        
        const sanitizedPath = file.metadata.folderPath.map(sanitizeFolderName).filter(p => p.length > 0);
        if (sanitizedPath.length > 0) {
          // Join folder path with forward slashes (Chrome will create folders)
          downloadFilename = sanitizedPath.join('/') + '/' + downloadFilename;
        }
      }
      
      console.log('[Download] Downloading file with folder structure:', downloadFilename);
      console.log('[Download] Folder path:', file.metadata.folderPath);
      
      await chrome.downloads.download({
        url: dataUrl,
        filename: downloadFilename,
        saveAs: false
      });
      return { opened: false, downloaded: true, filename: downloadFilename };
    } else {
      throw new Error('Failed to create download URL');
    }
  }

  /**
   * Open a DICOM series in the viewer
   */
  async openSeries(seriesId) {
    try {
      console.log('[Download] Opening DICOM series:', seriesId);

      const series = await this.getSeries(seriesId);
      if (!series) {
        throw new Error('Series not found');
      }

      console.log('[Download] Series contains', series.imageCount, 'images');
      console.log('[Download] Series projectId:', series.projectId);

      // Open viewer with series ID AND projectId for folder browser
      const viewerUrl = chrome.runtime.getURL(
        `src/viewers/dicom/viewer.html?seriesId=${encodeURIComponent(seriesId)}&projectId=${series.projectId}`
      );

      console.log('[Download] Opening viewer URL:', viewerUrl);
      await chrome.tabs.create({ url: viewerUrl });

      return { opened: true, seriesId, imageCount: series.imageCount };
    } catch (error) {
      console.error('[Download] Failed to open series:', error);
      throw error;
    }
  }

  /**
   * Download a file directly (not queued) - used for series where we need files immediately
   */
  async downloadFileDirectly({ docId, projectId, filename, mimeType, folderPath }) {
    console.log('[Download] Downloading file directly:', { docId, filename });

    try {
      // Get download URL
      const authState = authManager.getState();
      let downloadInfo;

      if (authState.auth?.usingWebApi) {
        downloadInfo = await webApiClient.getDocumentDownloadUrl(docId, projectId);
      } else {
        downloadInfo = await filevineClient.getDocumentDownloadUrl(docId);
      }

      const downloadUrl = downloadInfo.url || downloadInfo.downloadUrl;

      if (!downloadUrl) {
        throw new Error('No download URL returned from API');
      }

      // Fetch file
      const fetchOptions = {
        credentials: 'include'
      };

      if (authState.auth?.usingWebApi && downloadUrl.includes('product.filevineapp.com')) {
        if (authState.auth?.cookie) {
          fetchOptions.headers = {
            'Cookie': authState.auth.cookie
          };
        }
      }

      const response = await fetch(downloadUrl, fetchOptions);

      if (!response.ok) {
        throw new Error(`Download failed: ${response.status} ${response.statusText}`);
      }

      const blob = await response.blob();

      // Store in IndexedDB
      const download = {
        docId,
        projectId,
        filename,
        mimeType,
        folderPath,
        downloadedAt: Date.now(),
        status: 'completed'
      };

      await this.storeFile(download, blob);

      console.log('[Download] File downloaded and stored:', filename);

      return { docId, filename, success: true };

    } catch (error) {
      console.error(`[Download] Direct download failed for ${docId}:`, error);
      throw error;
    }
  }

  /**
   * Open a folder as a DICOM series (downloads DICOM files and opens viewer)
   */
  async openFolderAsDicom({ projectId, folderId, folderName, docIds, filenames }) {
    console.log('[Download] Opening folder as DICOM series:', folderName, 'folderId:', folderId);
    console.log('[Download] Parameters:', { projectId, folderId, folderName, docIds, docIdsCount: docIds?.length });

    // Allow folderId to be 0, -1, or any number (check for null/undefined instead)
    if (!projectId || folderId === null || folderId === undefined || !folderName || !docIds || !Array.isArray(docIds) || docIds.length === 0) {
      throw new Error(`Invalid parameters: projectId=${projectId}, folderId=${folderId}, folderName=${folderName}, docIds=${docIds}`);
    }

    try {
      // Step 1: Download all DICOM files directly (not queued)
      console.log('[Download] Starting direct downloads for', docIds.length, 'DICOM files...');
      const downloadPromises = [];

      for (let i = 0; i < docIds.length; i++) {
        const docId = docIds[i];
        const filename = filenames && filenames[i] ? filenames[i] : `dicom-${docId}.dcm`;

        // Start download directly (not queued) and wait for it
        const downloadPromise = this.downloadFileDirectly({
          docId,
          projectId,
          filename,
          mimeType: 'application/dicom',
          folderPath: [folderName]
        }).catch(error => {
          console.error(`[Download] Failed to download DICOM file ${docId}:`, error);
          return { docId, error: error.message, success: false };
        });

        downloadPromises.push(downloadPromise);
      }

      // Wait for all downloads to complete
      console.log('[Download] Waiting for all downloads to complete...');
      const results = await Promise.all(downloadPromises);

      const successful = results.filter(r => r.success).length;
      console.log('[Download] Successfully downloaded', successful, 'of', docIds.length, 'DICOM files');

      if (successful === 0) {
        throw new Error('Failed to download any DICOM files');
      }

      // Step 2: Create series
      const seriesId = `series_${projectId}_${folderId}_${Date.now()}`;

      await this.storeSeries({
        seriesId,
        projectId,
        folderId,
        folderName,
        docIds: docIds,
        imageCount: docIds.length
      });

      console.log('[Download] Stored DICOM series:', seriesId, 'with', docIds.length, 'images');
      console.log('[Download] All files downloaded, opening viewer...');

      // Step 3: Open series in viewer (files are already downloaded)
      await this.openSeries(seriesId);

      return {
        seriesId,
        folderId,
        folderName,
        downloaded: successful,
        totalFiles: docIds.length
      };
    } catch (error) {
      console.error('[Download] Error opening folder as DICOM:', error);
      throw error;
    }
  }

  /**
   * Ensure filename has the correct extension based on MIME type
   */
  ensureFileExtension(filename, mimeType) {
    // Map MIME types to extensions
    const mimeToExt = {
      'application/pdf': '.pdf',
      'image/jpeg': '.jpg',
      'image/png': '.png',
      'image/gif': '.gif',
      'image/webp': '.webp',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
      'application/vnd.ms-excel': '.xls',
      'application/msword': '.doc',
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
      'text/plain': '.txt',
      'text/html': '.html',
      'application/json': '.json',
      'application/xml': '.xml',
      'application/zip': '.zip',
      'application/x-zip-compressed': '.zip',
      'application/octet-stream': '' // Unknown type, no extension
    };

    // Get extension from MIME type
    const extension = mimeToExt[mimeType] || '';
    
    // If no filename provided, create one with extension
    if (!filename || filename === 'download' || filename.trim() === '') {
      const baseName = filename && filename !== 'download' ? filename : 'download';
      return extension ? `${baseName}${extension}` : 'download';
    }

    // Check if filename already has an extension
    const lastDot = filename.lastIndexOf('.');
    const hasExtension = lastDot > 0 && lastDot < filename.length - 1;
    
    if (hasExtension) {
      return filename; // Already has extension
    }

    // Add extension if we have one
    return extension ? `${filename}${extension}` : filename;
  }

  /**
   * Create a data URL from a blob
   * Service workers don't support URL.createObjectURL, so we use data URLs
   */
  async createDataUrl(blob, metadata) {
    try {
      const arrayBuffer = await blob.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);
      
      // Convert to base64
      let binaryString = '';
      const chunkSize = 8192; // Process in chunks to avoid stack overflow
      for (let i = 0; i < uint8Array.length; i += chunkSize) {
        const chunk = uint8Array.subarray(i, i + chunkSize);
        binaryString += String.fromCharCode.apply(null, chunk);
      }
      
      const base64 = btoa(binaryString);
      const mimeType = metadata.mimeType || 'application/octet-stream';
      return `data:${mimeType};base64,${base64}`;
    } catch (error) {
      console.error('[Download] Error creating data URL:', error);
      return null;
    }
  }

  /**
   * View a file directly without downloading (streams to viewer)
   */
  async viewFileDirect({ docId, projectId, filename, mimeType }) {
    try {
      console.log('[Download] Viewing file directly:', { docId, filename, mimeType });

      // Get download URL from Filevine
      const authState = authManager.getState();
      let downloadInfo;
      
      if (authState.auth?.usingWebApi) {
        downloadInfo = await webApiClient.getDocumentDownloadUrl(docId, projectId);
      } else {
        downloadInfo = await filevineClient.getDocumentDownloadUrl(docId);
      }
      
      const downloadUrl = downloadInfo.url || downloadInfo.downloadUrl;
      
      if (!downloadUrl) {
        throw new Error('No download URL returned from API');
      }

      console.log('[Download] Got download URL for direct view:', downloadUrl.substring(0, 100) + '...');

      // For DICOM files, pass the URL directly to the viewer
      // The viewer will fetch it (with proper auth headers if needed)
      const ext = filename?.toLowerCase().split('.').pop() || '';
      const mime = mimeType?.toLowerCase() || '';
      
      if (ext === 'dcm' || ext === 'dicom' || mime === 'application/dicom') {
        // Create a special URL that includes auth info for the viewer to use
        // We'll pass the download URL and let the viewer fetch it
        // Also include projectId so the viewer can load the folder browser
        const viewerUrl = chrome.runtime.getURL(
          `src/viewers/dicom/viewer.html?file=${encodeURIComponent(downloadUrl)}&direct=true&projectId=${projectId}`
        );

        chrome.tabs.create({ url: viewerUrl });
        return { opened: true, viewerUrl };
      }

      throw new Error('Direct view only supported for DICOM files');
    } catch (error) {
      console.error('[Download] Failed to view file directly:', error);
      throw error;
    }
  }

  /**
   * Get appropriate viewer URL for file type
   */
  getViewerUrl(metadata, blobUrl) {
    const ext = metadata.filename?.toLowerCase().split('.').pop();
    const mime = metadata.mimeType?.toLowerCase();

    // DICOM files
    if (ext === 'dcm' || ext === 'dicom' || mime === 'application/dicom') {
      return chrome.runtime.getURL(`src/viewers/dicom/viewer.html?file=${encodeURIComponent(blobUrl)}`);
    }

    // Default: download or browser viewer
    return blobUrl;
  }

  /**
   * Delete a file from storage
   */
  async deleteFile(docId) {
    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['files', 'metadata'], 'readwrite');
      
      transaction.objectStore('files').delete(docId);
      transaction.objectStore('metadata').delete(docId);

      transaction.oncomplete = () => resolve(true);
      transaction.onerror = () => reject(transaction.error);
    });
  }

  /**
   * Clear all downloaded files
   */
  async clearAllFiles() {
    const db = await this.initializeDb();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['files', 'metadata'], 'readwrite');
      
      transaction.objectStore('files').clear();
      transaction.objectStore('metadata').clear();

      transaction.oncomplete = () => resolve(true);
      transaction.onerror = () => reject(transaction.error);
    });
  }

  /**
   * Get current download queue state
   */
  getState() {
    return {
      queue: this.queue,
      active: Array.from(this.activeDownloads.values()),
      maxConcurrent: this.maxConcurrent
    };
  }

  /**
   * Broadcast state to all extension contexts
   */
  broadcastState() {
    chrome.runtime.sendMessage({
      type: MESSAGE_TYPES.DOWNLOAD_PROGRESS,
      state: this.getState()
    }).catch(() => {
      // Ignore errors when no listeners
    });
  }

  /**
   * Broadcast download completion
   */
  broadcastDownloadComplete(download) {
    chrome.runtime.sendMessage({
      type: MESSAGE_TYPES.DOWNLOAD_COMPLETE,
      download: {
        docId: download.docId,
        filename: download.filename,
        completedAt: download.completedAt
      }
    }).catch(() => {});
  }

  /**
   * Queue all documents in a folder for download (recursively including subfolders)
   */
  async queueFolderDownload({ projectId, folderId, folderName }) {
    console.log('[Download] Queueing folder download:', folderName, 'folderId:', folderId);
    
    try {
      const authState = authManager.getState();
      
      // Step 1: Get folder tree to find all subfolders recursively and build folder paths
      let allFolderIds = [folderId]; // Start with the root folder
      let folderPathMap = {}; // Map of folderId -> array of folder names (path from root)
      let folderNameMap = {}; // Map of folderId -> folder name
      
      if (authState.auth?.usingWebApi) {
        // Get folder tree to find all subfolders
        const folderTree = await webApiClient.getProjectFolderTree(projectId, {
          descendantFolderIDs: [folderId],
          maxChildrenPerFolder: 500
        });
        
        // Extract folder names from folderTree if available
        if (folderTree.folderNameMap) {
          folderNameMap = folderTree.folderNameMap;
        }
        
        // Build folder paths recursively
        const buildFolderPaths = (parentId, parentPath = []) => {
          const parentIdStr = String(parentId);
          const childIds = folderTree.childrenLookup[parentIdStr] || folderTree.childrenLookup[parentId] || [];
          
          for (const childId of childIds) {
            if (!allFolderIds.includes(childId)) {
              allFolderIds.push(childId);
            }
            
            // Get folder name
            const childFolderName = folderNameMap[childId] || folderNameMap[String(childId)] || `Folder ${childId}`;
            
            // Build path: parent path + current folder name
            const childPath = [...parentPath, childFolderName];
            folderPathMap[childId] = childPath;
            folderPathMap[String(childId)] = childPath;
            
            // Recursively build paths for subfolders
            buildFolderPaths(childId, childPath);
          }
        };
        
        // Start building paths from the root folder
        const rootFolderName = folderNameMap[folderId] || folderNameMap[String(folderId)] || folderName;
        folderPathMap[folderId] = [rootFolderName];
        folderPathMap[String(folderId)] = [rootFolderName];
        
        buildFolderPaths(folderId, [rootFolderName]);
        console.log('[Download] Found', allFolderIds.length, 'folders (including subfolders)');
        console.log('[Download] Built folder paths for', Object.keys(folderPathMap).length, 'folders');
      }
      
      // Step 2: Get all documents from all folders (including subfolders)
      let allDocuments = [];
      
      for (const currentFolderId of allFolderIds) {
        try {
          let documents = [];
          
          if (authState.auth?.usingWebApi) {
            const response = await webApiClient.getProjectDocuments(projectId, {
              folderID: currentFolderId,
              limit: 1000, // Get all documents
              offset: 0
            });
            documents = response.items || [];
          } else {
            const response = await filevineClient.getProjectDocuments(projectId, {
              folderID: currentFolderId,
              limit: 1000,
              offset: 0
            });
            documents = response.items || [];
          }
          
          // Add folder path info to each document
          const folderPath = folderPathMap[currentFolderId] || folderPathMap[String(currentFolderId)] || [];
          documents.forEach(doc => {
            doc._folderPath = folderPath; // Store folder path with document
            doc._folderId = currentFolderId; // Store folder ID
          });
          
          console.log('[Download] Found', documents.length, 'documents in folder', currentFolderId, 'path:', folderPath.join('/'));
          allDocuments.push(...documents);
        } catch (error) {
          console.warn(`[Download] Failed to get documents from folder ${currentFolderId}:`, error);
          // Continue with other folders
        }
      }
      
      console.log('[Download] Total documents found:', allDocuments.length, 'across', allFolderIds.length, 'folders');

      // Step 3: Check if this is a DICOM series (multiple DICOM files in folder)
      const dicomFiles = allDocuments.filter(doc => {
        const filename = (doc.filename || doc.name || '').toLowerCase();
        const mimeType = (doc.mimeType || doc.contentType || '').toLowerCase();
        return filename.endsWith('.dcm') ||
               filename.endsWith('.dicom') ||
               mimeType === 'application/dicom';
      });

      console.log('[Download] Found', dicomFiles.length, 'DICOM files in folder');

      let seriesId = null;
      if (dicomFiles.length > 1) {
        // Multiple DICOM files - create a series
        seriesId = `series_${projectId}_${folderId}_${Date.now()}`;
        console.log('[Download] Creating DICOM series:', seriesId);
      }

      // Step 4: Queue each document for download with folder path
      const results = [];
      for (const doc of allDocuments) {
        const docId = doc.id || doc.docId || doc.documentId;
        const filename = doc.filename || doc.name || doc.title || 'Unknown';
        const mimeType = doc.mimeType || doc.contentType;
        const docFolderPath = doc._folderPath || []; // Get folder path from document
        
        try {
          const result = await this.queueDownload({
            docId,
            projectId,
            filename,
            mimeType,
            folderPath: docFolderPath, // Include folder path
            autoOpen: false // Don't auto-open when downloading folder
          });
          results.push({ docId, filename, folderPath: docFolderPath, ...result });
        } catch (error) {
          console.error(`[Download] Failed to queue document ${docId}:`, error);
          results.push({ docId, filename, folderPath: docFolderPath, queued: false, error: error.message });
        }
      }
      
      const queued = results.filter(r => r.queued).length;
      console.log('[Download] Queued', queued, 'of', allDocuments.length, 'documents from', allFolderIds.length, 'folders');

      // Step 5: If DICOM series detected, store series metadata after downloads complete
      if (seriesId && dicomFiles.length > 1) {
        // Wait a bit for downloads to complete before storing series
        // (downloads happen in background, so we store series immediately with docIds)
        const dicomDocIds = dicomFiles.map(doc => doc.id || doc.docId || doc.documentId);

        await this.storeSeries({
          seriesId,
          projectId,
          folderId,
          folderName,
          docIds: dicomDocIds,
          imageCount: dicomFiles.length
        });

        console.log('[Download] Stored DICOM series:', seriesId, 'with', dicomFiles.length, 'images');

        // Auto-open the series in the DICOM viewer
        setTimeout(() => {
          this.openSeries(seriesId).catch(err => {
            console.error('[Download] Failed to auto-open DICOM series:', err);
          });
        }, 2000); // Wait 2 seconds for first few files to download
      }

      return {
        folderId,
        folderName,
        totalFolders: allFolderIds.length,
        totalDocuments: allDocuments.length,
        queued,
        results,
        seriesId, // Include series ID if created
        dicomCount: dicomFiles.length
      };
    } catch (error) {
      console.error('[Download] Error queueing folder download:', error);
      throw error;
    }
  }

  /**
   * Cancel a queued download
   */
  cancelDownload(docId) {
    const queueIndex = this.queue.findIndex(d => d.docId === docId);
    if (queueIndex >= 0) {
      this.queue.splice(queueIndex, 1);
      this.broadcastState();
      return { cancelled: true };
    }
    return { cancelled: false, reason: 'Not in queue (may be actively downloading)' };
  }
}

// Singleton instance
export const downloadManager = new DownloadManager();
