class OneDisplayFileManager {
	constructor() {
        if (!OneDisplayFileManager.instance) {
            // Initialize only once
            this.downloadedFilesIndex = {};
            this.mediaUsage = {};
            
            this.downloadQueue = this.downloadQueue || {};
            this.activeDownloads = this.activeDownloads || new Set();

            this._workerRunning = false;
            this._workerStop = false;
            this._wakeResolver = null;   // set when worker is sleeping
            
            /*this.downloadQueue = {};
            this.isProcessingQueue = false;
            this.activeDownloads = new Set(); // track filenames/ids in-flight*/
            this.db = null;
            this.initialized = false;
            
            // Initialize immediately
            this._initialize();
            
            OneDisplayFileManager.instance = this;
        }
        console.log("File manager initialized");
        return OneDisplayFileManager.instance;
    }
	
	async _initialize() {
        try {
            // Setup storage
            this.initializeFromStorage();
            
            // Setup IndexedDB if needed
            if (typeof window.tizen === 'undefined') {
                await this.setupIndexedDB();
            }
            
            // Initialize media usage tracking
            await this._initializeMediaUsage();
            
            // Start cleanup timer
            this._startCleanupTimer();
            
            // Start download queue worker
            this.startQueueWorker();
            
            this.initialized = true;
        } catch (error) {
            console.error("FileManager initialization failed:", error);
        }
    }

    async _initializeMediaUsage() {
        if (typeof window.tizen !== 'undefined') {
            await this._initializeTizenMediaUsage();
        } else {
            await this._initializeWebMediaUsage();
        }
        this.saveToStorage();
    }

    _startCleanupTimer() {
        // Run cleanup every hour
        //setInterval(() => this.cleanupMedia(), 10 * 60 * 1000);
        setInterval(() => this.cleanupMedia(), 30 * 60 * 1000); // 30 minutes
    }

    // Initialize from localStorage
    initializeFromStorage() {
        const storedCache = getStoredValue("downloadsCache");
        if (storedCache) {
            try {
                this.downloadedFilesIndex = JSON.parse(storedCache);
            } catch (e) {
                this.downloadedFilesIndex = {};
            }
        }

        const usage = getStoredValue("mediaUsageObject");
        if (usage) {
            try {
                this.mediaUsage = JSON.parse(usage);
            } catch (e) {
                this.mediaUsage = {};
            }
        }
    }
    
    async setupIndexedDB() {
        return new Promise((resolve, reject) => {
            let request = window.indexedDB.open("media", 1);
            request.onupgradeneeded = (event) => {
                let db = event.target.result;
                if (!db.objectStoreNames.contains("items")) {
                    db.createObjectStore("items");
                }
            };
            request.onsuccess = (event) => {
                this.db = event.target.result;
                console.log("DB initialized");
                resolve();
            };
            request.onerror = (event) => reject(event);
        });
    }
    

    async _initializeTizenMediaUsage() {
        await new Promise((resolve, reject) => {
            tizen.filesystem.resolve(
                'downloads',
                (dirEntry) => {
                    dirEntry.listFiles(
                        (files) => {
                            files.forEach((file) => {
                                if (!this.mediaUsage[file.name]) {
                                    this.mediaUsage[file.name] = { 
                                        lastUsed: Date.now(), 
                                        lastUsedDateTime: new Date().toLocaleString() 
                                    };
                                }
                            });
                            resolve();
                        },
                        (error) => reject(error)
                    );
                },
                (error) => reject(error),
                "r"
            );
        });
    }

    async _initializeWebMediaUsage() {
        try {
            const keys = await this._getAllIndexedDBKeys();
            keys.forEach(key => {
                const fileName = this._getFileNameFromKey(key);
                if (fileName && !this.mediaUsage[fileName]) {
                    this.mediaUsage[fileName] = { 
                        lastUsed: Date.now(), 
                        lastUsedDateTime: new Date().toLocaleString() 
                    };
                }
            });
        } catch (error) {
            console.error("Error initializing web media usage:", error);
            throw error;
        }
    }

    async _getAllIndexedDBKeys() {
        return new Promise((resolve, reject) => {
            if (!this.db) {
                reject(new Error("IndexedDB not initialized"));
                return;
            }
    
            const transaction = this.db.transaction(["items"], "readonly");
            const store = transaction.objectStore("items");
            const request = store.getAllKeys();
    
            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event.target.error);
        });
    }

    _getFileNameFromKey(key) {
        if(typeof this.downloadedFilesIndex[key] !== 'undefined') {
            return this.downloadedFilesIndex[key].filename;
        }
        if (typeof key === 'string') {
            return key.includes('/') ? key.split('/').pop() : key;
        }
        return null;
    }

    // Save current state to storage
    saveToStorage() {
        setStoredValue("downloadsCache", JSON.stringify(this.downloadedFilesIndex));
        setStoredValue("mediaUsageObject", JSON.stringify(this.mediaUsage));
    }

    // --- File Existence Checks ---
    async checkFileExists(id, filename, force = false) {
        if (typeof window.tizen !== "undefined") {
            return this.checkFileExistsOnStorage(id, filename, force);
        }
        return this.checkFileExistsInDB(id);
    }

    async checkFileExistsInDB(id) {
        if (this.downloadedFilesIndex[id] !== undefined) {
            return true;
        }
        if(!this.db) return false;
        //if (!this.player.db) return false;

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(["items"], "readonly");
            const request = transaction.objectStore("items").get(id);
            request.onsuccess = (event) => resolve(event.target.result !== undefined);
            request.onerror = (event) => reject(event);
        });
    }

    async checkFileExistsOnStorage(id, filename, forceCheck = false) {
	  if (!forceCheck && this.downloadedFilesIndex[id] !== undefined) return true;

	  return new Promise((resolve) => {
	    tizen.filesystem.resolve('downloads/' + filename, (file) => {
	      this.downloadedFilesIndex[id] = { id, file: file.toURI(), filename };
	      resolve(true);
	    }, () => resolve(false), 'r');
	  });
	}
    /*
    async checkFileExistsOnStorage(id, filename, forceCheck = false) {
        if (!forceCheck && this.downloadedFilesIndex[id] !== undefined) {
            return true;
        }

        return new Promise((resolve, reject) => {
            tizen.filesystem.resolve('downloads', (dir) => {
                try {
                    const file = dir.resolve(filename);
                    if (file) {
                        this.downloadedFilesIndex[id] = { 
                            id: id, 
                            file: file.toURI(), 
                            filename: filename 
                        };
                        resolve(true);
                    } else {
                        resolve(false);
                    }
                } catch (e) {
                    resolve(false);
                }
            }, reject);
        });
    }*/

    // --- File Operations ---
    async getFileUrl(id) {
	  const meta = this.downloadedFilesIndex[id];
	  if (typeof window.tizen !== "undefined") {
	    if (meta) return meta.file;
	    return null; // or try to reconstruct by scanning dir if you store a reverse index
	  }
	  return this.getFileUrlFromDB(id);
	}
    /*async getFileUrl(id) {
        if (typeof window.tizen !== "undefined") {
            if (this.downloadedFilesIndex[id]) {
                return this.downloadedFilesIndex[id].file;
            }
            return this.getFileFromStorage(this.downloadedFilesIndex[id].filename);
        }
        return this.getFileUrlFromDB(id);
    }*/

    async getFileUrlFromDB(id) {
        const blob = await this.getFileFromDB(id);
        return URL.createObjectURL(blob);
    }

    async getFileFromDB(id) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(["items"], "readonly");
            const request = transaction.objectStore("items").get(id);
            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event);
        });
    }

    async getFileFromStorage(fileName) {
        return new Promise((resolve, reject) => {
            tizen.filesystem.resolve('downloads', (dir) => {
                try {
                    const file = dir.resolve(fileName);
                    resolve(file.toURI());
                } catch (e) {
                    resolve(null);
                }
            }, reject);
        });
    }
    
    startQueueWorker() {
    	  if (this._workerRunning) return;          // re-entrancy guard
    	  this._workerRunning = true;
    	  this._workerStop = false;

    	  const waitForWork = () =>
    	    new Promise(res => { this._wakeResolver = res; });

    	  const nextJobId = () => {
    	    for (const id of Object.keys(this.downloadQueue)) {
    	      const job = this.downloadQueue[id];
    	      if (job && job.status === "pending" && !this.activeDownloads.has(job.filename)) {
    	        return id;
    	      }
    	    }
    	    return null;
    	  };

    	  (async () => {
    	    try {
    	      while (!this._workerStop) {
    	        const id = nextJobId();
    	        if (!id) {
    	          // sleep until queueDownload wakes us
    	          await waitForWork();
    	          if (this._workerStop) break;
    	          continue;
    	        }

    	        const job = this.downloadQueue[id];
    	        job.status = "downloading";
    	        this.activeDownloads.add(job.filename);

    	        try {
    	          console.log("Processing queued item", job);
    	          await this.downloadFile(job);   // your existing function
    	        } catch (err) {
    	          console.error("Download failed:", err);
    	          // (optional) requeue with backoff instead of dropping:
    	          // job.retryCount++; job.status = job.retryCount < 3 ? "pending" : "failed";
    	        } finally {
    	          this.activeDownloads.delete(job.filename);
    	          delete this.downloadQueue[id];
    	        }
    	      }
    	    } finally {
    	      this._workerRunning = false;
    	    }
    	  })();
    	}
    
    stopQueueWorker() {
    	  this._workerStop = true;
    	  if (this._wakeResolver) { this._wakeResolver(); this._wakeResolver = null; }
    	}

    // --- Download Management ---
    /*queueDownload(item) {
        if (!this.downloadQueue[item.id]) {
            this.downloadQueue[item.id] = {
                id: item.id,
                filename: item.path,
                url: `https://cdn.onedisplay.se/media/${item.path}`,
                status: "pending",
                retryCount: 0,
            };
            console.log("File placed in queue: " + item.path);
        }
    }*/
    queueDownload(item) {
    	  const id = item.id;
    	  const filename = item.path;

    	  // de-dupe: don't queue same id or start if currently downloading same file
    	  if (this.downloadQueue[id] || this.activeDownloads.has(filename)) return;

    	  this.downloadQueue[id] = {
    	    id,
    	    filename,
    	    url: `${cdnUrl}/media/${filename}`,
    	    status: "pending",
    	    retryCount: 0,
    	  };
    	  console.log("File placed in queue:", filename);

    	  // wake sleeping worker if any
    	  if (this._wakeResolver) {
    	    this._wakeResolver();
    	    this._wakeResolver = null;
    	  }
    	}

    /*async processDownloadQueue() {
        for (let id in this.downloadQueue) {
            if (this.downloadQueue[id].status === "pending") {
            	console.log("Processing queued item ", this.downloadQueue[id]);
                try {
                    await this.downloadFile(this.downloadQueue[id]);
                    delete this.downloadQueue[id];
                } catch (error) {
                    console.error(error);
                    delete this.downloadQueue[id];
                }
            }
        }
    }*/
    
    async processDownloadQueue() {
    	  if (this.isProcessingQueue) return;   // <— stop re-entrancy
    	  this.isProcessingQueue = true;

    	  try {
    	    // Process pending items sequentially to avoid race/duplicate writes
    	    for (const id of Object.keys(this.downloadQueue)) {
    	      const job = this.downloadQueue[id];
    	      if (!job || job.status !== "pending") continue;

    	      // mark active by filename (prevents duplicates if another instance queues same file)
    	      if (this.activeDownloads.has(job.filename)) {
    	        // already being downloaded elsewhere; just remove from our queue
    	        delete this.downloadQueue[id];
    	        continue;
    	      }
    	      this.activeDownloads.add(job.filename);

    	      console.log("Processing queued item ", job);
    	      try {
    	        await this.downloadFile(job);
    	      } catch (err) {
    	        console.error("Download failed:", err);
    	      } finally {
    	        this.activeDownloads.delete(job.filename);
    	        delete this.downloadQueue[id];
    	      }
    	    }
    	  } finally {
    	    this.isProcessingQueue = false;
    	  }
    	}

    /*async waitForDownloads() {
		let statusEl = document.getElementById("downloadStatus");
		while (Object.keys(this.downloadQueue).length > 0) {
			if (statusEl) {
				statusEl.textContent = `Waiting on ${Object.keys(this.downloadQueue).length} download(s)...`;
			}
			await new Promise((resolve) => setTimeout(resolve, 500));
		}
	}*/
    
    async waitForDownloads() {
    	  const statusEl = document.getElementById("downloadStatus");
    	  while (
    	    Object.keys(this.downloadQueue).length > 0 ||
    	    this.activeDownloads.size > 0
    	  ) {
    	    if (statusEl) {
    	      statusEl.textContent =
    	        `Waiting on ${Object.keys(this.downloadQueue).length} queued, ${this.activeDownloads.size} active...`;
    	    }
    	    await new Promise(r => setTimeout(r, 400));
    	  }
    	}
    

    

    async downloadFile(fileInfo) {
        const MAX_RETRIES = 3;
        const fileName = fileInfo.url.split("/").pop();
        
        if (await this.checkFileExists(fileInfo.id, fileInfo.filename, true)) {
           await this._ensureDeleteByIdOrRequested(fileInfo);
        }

        // Remove existing file if necessary
        /*if (await this.checkFileExists(fileInfo.id, fileInfo.filename, true)) {
            await this.deleteFile(fileInfo.filename);
        }*/

        if (typeof window.tizen !== 'undefined') {
            return this.downloadWithTizen(fileInfo, fileName);
        }
        return this.downloadWithAxios(fileInfo, fileName);
    }

    async downloadWithTizen(fileInfo, fileName) {
    	console.log("Attempting to download file: " + fileName);
        const MAX_RETRIES = 3;
        
		let statusEl = document.getElementById("downloadStatus");
		let progressEl = document.getElementById("downloadProgress");

		if (statusEl && progressEl) {
			progressEl.textContent = `${fileName}... 0%`;
		}
        
        return new Promise((resolve, reject) => {
            let retryCount = 0;

            const startDownload = () => {
                const req = new tizen.DownloadRequest(fileInfo.url, 'downloads', fileName, 'ALL');
                req.httpHeader['Pragma'] = 'no-cache';

                const listener = {
                    /*oncompleted: (id, fullPath) => {
                        this.storeFileMetaData(fileInfo.id, "file://" + fullPath, fileName);
                        resolve(fileInfo);
                    },*/
                		onprogress: function (id, receivedSize, totalSize) {
							if (totalSize && progressEl) {
								let percent = Math.floor((receivedSize / totalSize) * 100);
								progressEl.textContent = `${fileName}... ${percent}%`;
							}
						},
                		oncompleted: async (id, fullPath) => {
                		    // actual saved name on disk (may include "(1)")
                		    const actualName = fullPath.split('/').pop();
                		    const dirPath = fullPath.substring(0, fullPath.lastIndexOf('/'));
                		    
                		    // Optional: normalize to a safe name (no parentheses)
                		    const safeName = actualName.replace(/\(\d+\)(?=\.[^.]+$)/, '');
                		    console.log("Moving from " + actualName + " -> " + safeName);
                		    
                		    try {
                		        // rename if Tizen added (1)
                		        if (safeName !== actualName) {
                		            await new Promise((res, rej) => {
                		                tizen.filesystem.resolve('downloads', dir => {
                		                    // Get the source file
                		                    dir.resolve(actualName, file => {
                		                        // Move to destination
                		                        file.moveTo(dir, safeName, res, rej);
                		                    }, rej);
                		                }, rej, 'rw');
                		            });
                		        }
                		        // store metadata with the safe name
                		        await this.storeFileMetaData(fileInfo.id, await this.getFileFromStorage(safeName), safeName);
                		        resolve(fileInfo);
                		    } catch (e) {
                		        console.error("Error renaming file:", e);
                		        // if rename failed, fallback to actualName
                		        await this.storeFileMetaData(fileInfo.id, 'file://' + fullPath, actualName);
                		        resolve(fileInfo);
                		    }
                		},
                    onfailed: (error) => {
                    	console.log("An error occured when downloading file", error);
                        retryCount++;
                        if (retryCount < MAX_RETRIES) {
                        	console.log("Retrying...");
                            startDownload();
                        } else {
                        	console.log("Not retrying anymore...");
                            reject(new Error(`Download failed after ${MAX_RETRIES} attempts`));
                        }
                    }
                };

                tizen.download.start(req, listener);
            };

            startDownload();
        });
    }

    async downloadWithAxios(fileInfo, fileName) {
    	console.log("Attempting to download file: " + fileName);
        const MAX_RETRIES = 3;
		let statusEl = document.getElementById("downloadStatus");
		let progressEl = document.getElementById("downloadProgress");

		if (statusEl && progressEl) {
			progressEl.textContent = `${fileName}... 0%`;
		}
        return new Promise(async (resolve, reject) => {
            let retryCount = 0;

            const startDownload = async () => {
                try {

                    const response = await axios.get(fileInfo.url, {
						responseType: 'blob',
						onDownloadProgress: (progressEvent) => {
							if (progressEvent.total && progressEl) {
								let percent = Math.floor((progressEvent.loaded / progressEvent.total) * 100);
								progressEl.textContent = `${fileName}... ${percent}%`;
							}
						}
					});

                    if (response.data) {
                    	console.log("File downloaded: " + fileName);
                        await this.storeFileInDB(fileInfo.id, response.data);
                        await this.storeFileMetaData(fileInfo.id, fileInfo.id, fileName);
                        console.log("File stored locally with great success!");
                        resolve(fileInfo);
                    } else {
                        throw new Error("No data received");
                    }
                } catch (error) {
                	console.log("Failed to download file", error);
                    retryCount++;
                    if (retryCount < MAX_RETRIES) {
                        setTimeout(() => startDownload(), 1000 * retryCount);
                    } else {
                        reject(error);
                    }
                }
            };

            await startDownload();
        });
    }

    async storeFileInDB(id, blob) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(["items"], "readwrite");
            const request = transaction.objectStore("items").put(blob, id);
            request.onsuccess = () => resolve();
            request.onerror = (event) => reject(event);
        });
    }

    // --- File Metadata ---
    async storeFileMetaData(id, fileUri, name) {
        this.downloadedFilesIndex[id] = { id, file: fileUri, filename: name };
        this.saveToStorage();
    }

    // --- File Deletion ---
    async deleteFile(fileName) {
        if (typeof window.tizen !== 'undefined') {
            return this.deleteTizenFile(fileName);
        }
        return this.deleteDBFile(fileName);
    }
    
    // Prefer deleting by stored meta for this id (actual on-disk name), fallback to requested name
    async _ensureDeleteByIdOrRequested(fileInfo) {
    	const meta = this.downloadedFilesIndex[fileInfo.id];
    	const storedName = meta && meta.filename ? meta.filename : null;
    	const requestedName = fileInfo && fileInfo.filename ? fileInfo.filename : null;

    	if (typeof window.tizen !== 'undefined') {
	      if (storedName) {
	        await this.deleteFile(storedName);
	      } else if (requestedName) {
	        await this.deleteFile(requestedName);
	      }
	      return;
    	}

    	if (storedName) {
    		await this.deleteDBFile(storedName);
    	} else if (requestedName) {
    		await this.deleteDBFile(requestedName);
    	}
    }
    
    async deleteTizenFile(fileName) {
	  return new Promise((resolve) => {
	    if (typeof tizen.filesystem.deleteFile === 'function') {
	      // Global delete API takes a path relative to a virtual root or absolute; this is fine:
	      tizen.filesystem.deleteFile('downloads/' + fileName, resolve, resolve);
	    } else {
	      tizen.filesystem.resolve('downloads', (dir) => {
	        dir.deleteFile(fileName, resolve, resolve); // ← only the name here
	      }, resolve, 'rw');
	    }
	  });
	}
    

    /*async deleteTizenFile(fileName) {
        return new Promise((resolve) => {
            if (typeof tizen.filesystem.deleteFile === 'function') {
                tizen.filesystem.deleteFile('downloads/' + fileName, resolve, resolve);
            } else {
                tizen.filesystem.resolve('downloads', (dir) => {
                    dir.deleteFile("downloads/" + fileName, resolve, resolve);
                }, resolve, "rw");
            }
        });
    }*/

    async deleteDBFile(fileName) {
        try {
            let idToDelete = null;
            for (const [id, meta] of Object.entries(this.downloadedFilesIndex)) {
                if (meta.filename === fileName) {
                    idToDelete = id;
                    break;
                }
            }

            if (idToDelete) {
                const transaction = this.db.transaction(["items"], "readwrite");
                const store = transaction.objectStore("items");
                await new Promise((resolve, reject) => {
                    const request = store.delete(idToDelete);
                    request.onsuccess = () => {
                        delete this.downloadedFilesIndex[idToDelete];
                        this.saveToStorage();
                        resolve();
                    };
                    request.onerror = (event) => reject(event);
                });
            }
        } catch (error) {
            console.error("Error deleting file from IndexedDB:", error);
            throw error;
        }
    }

    // --- Media Usage Tracking ---
    updateMediaUsage(filename) {
        this.mediaUsage[filename] = { 
            lastUsed: Date.now(), 
            lastUsedDateTime: new Date().toLocaleString() 
        };
        this.saveToStorage();
    }

    findIdByFilename(filename) {
        const entry = Object.values(this.downloadedFilesIndex).find(
            file => file.filename === filename
        );
        return entry ? entry.id : null;
    }

    // --- Cleanup ---
    async cleanupMedia(threshold = 24 * 60 * 60 * 1000) {
        //if (!this.playlistPlayed) return;
        console.log("Cleanup media");
        const now = Date.now();
        for (const [filename, usage] of Object.entries(this.mediaUsage)) {
            if (usage.lastUsed && (now - usage.lastUsed) > threshold) {
                const fileId = this.findIdByFilename(filename);
                if (fileId) {
                    delete this.downloadedFilesIndex[fileId];
                }
                await this.deleteFile(filename);
                delete this.mediaUsage[filename];
            }
        }
        await this._initializeMediaUsage(); // Fetch files possibly not removed from storage
    }

    // --- File System Clearing ---
    async clearFilesystem() {
        if (typeof window.tizen !== 'undefined') {
            try {
                const dirEntry = await new Promise((resolve, reject) => {
                    tizen.filesystem.resolve('downloads', resolve, reject, "r");
                });

                const files = await new Promise((resolve, reject) => {
                    dirEntry.listFiles(resolve, reject);
                });

                for (const file of files) {
                	console.log("Deleting file " + file.name);
                    await this.deleteFile(file.name);
                }
            } catch (error) {
                console.error("Error clearing filesystem:", error);
            }
            return;
        }

        try {
            if (this.db && typeof this.db.close === 'function') {
                this.db.close();
            }
            this.db = null;

            await new Promise((resolve, reject) => {
                const request = window.indexedDB.deleteDatabase("media");
                request.onsuccess = function() { resolve(); };
                request.onerror = function(event) { reject(event); };
                request.onblocked = function() { resolve(); };
            });
        } catch (error) {
            console.error("Error clearing IndexedDB:", error);
        }

        this.downloadedFilesIndex = {};
        this.mediaUsage = {};
        this.downloadQueue = {};
        this.activeDownloads = new Set();
        this.saveToStorage();
    }
}
