class Dashboard{constructor(){this.state={mode:"torrents",torrents:[],nzbs:[],selectedItems:new Set,categories:new Set,filteredItems:[],selectedCategory:"",selectedState:"",sortBy:"added_on",itemsPerPage:20,currentPage:1,selectedItemContextMenu:null},this.refs={torrentsMode:document.getElementById("torrentsMode"),nzbsMode:document.getElementById("nzbsMode"),dataList:document.getElementById("dataList"),torrentsHeaders:document.getElementById("torrentsHeaders"),nzbsHeaders:document.getElementById("nzbsHeaders"),categoryFilter:document.getElementById("categoryFilter"),stateFilter:document.getElementById("stateFilter"),sortSelector:document.getElementById("sortSelector"),selectAll:document.getElementById("selectAll"),selectAllNzb:document.getElementById("selectAllNzb"),batchDeleteBtn:document.getElementById("batchDeleteBtn"),batchDeleteDebridBtn:document.getElementById("batchDeleteDebridBtn"),refreshBtn:document.getElementById("refreshBtn"),torrentContextMenu:document.getElementById("torrentContextMenu"),nzbContextMenu:document.getElementById("nzbContextMenu"),paginationControls:document.getElementById("paginationControls"),paginationInfo:document.getElementById("paginationInfo"),emptyState:document.getElementById("emptyState"),emptyStateTitle:document.getElementById("emptyStateTitle"),emptyStateMessage:document.getElementById("emptyStateMessage")},this.init()}init(){this.bindEvents(),this.loadModeFromURL(),this.loadData(),this.startAutoRefresh()}bindEvents(){this.refs.torrentsMode.addEventListener("click",()=>this.switchMode("torrents")),this.refs.nzbsMode.addEventListener("click",()=>this.switchMode("nzbs")),this.refs.refreshBtn.addEventListener("click",()=>this.loadData()),this.refs.batchDeleteBtn.addEventListener("click",()=>this.deleteSelectedItems()),this.refs.batchDeleteDebridBtn.addEventListener("click",()=>this.deleteSelectedItems(!0)),this.refs.selectAll.addEventListener("change",t=>this.toggleSelectAll(t.target.checked)),this.refs.selectAllNzb.addEventListener("change",t=>this.toggleSelectAll(t.target.checked)),this.refs.categoryFilter.addEventListener("change",t=>this.setFilter("category",t.target.value)),this.refs.stateFilter.addEventListener("change",t=>this.setFilter("state",t.target.value)),this.refs.sortSelector.addEventListener("change",t=>this.setSort(t.target.value)),this.bindContextMenu(),this.refs.dataList.addEventListener("change",t=>{t.target.classList.contains("item-select")&&this.toggleItemSelection(t.target.dataset.id,t.target.checked)})}switchMode(t){this.state.mode!==t&&(this.state.mode=t,this.state.selectedItems.clear(),this.updateURL(t),"torrents"===t?(this.refs.torrentsMode.classList.remove("btn-outline"),this.refs.torrentsMode.classList.add("btn-primary"),this.refs.nzbsMode.classList.remove("btn-primary"),this.refs.nzbsMode.classList.add("btn-outline"),this.refs.torrentsHeaders.classList.remove("hidden"),this.refs.nzbsHeaders.classList.add("hidden"),this.refs.emptyStateTitle.textContent="No Torrents Found",this.refs.emptyStateMessage.textContent="You haven't added any torrents yet. Start by adding your first download!",this.refs.batchDeleteDebridBtn.classList.remove("hidden")):(this.refs.nzbsMode.classList.remove("btn-outline"),this.refs.nzbsMode.classList.add("btn-primary"),this.refs.torrentsMode.classList.remove("btn-primary"),this.refs.torrentsMode.classList.add("btn-outline"),this.refs.nzbsHeaders.classList.remove("hidden"),this.refs.torrentsHeaders.classList.add("hidden"),this.refs.emptyStateTitle.textContent="No NZBs Found",this.refs.emptyStateMessage.textContent="You haven't added any NZB downloads yet. Start by adding your first NZB!",this.refs.batchDeleteDebridBtn.classList.add("hidden")),this.state.selectedCategory="",this.state.selectedState="",this.state.currentPage=1,this.refs.categoryFilter.value="",this.refs.stateFilter.value="",this.loadData(),this.updateBatchActions())}updateBatchActions(){const t=this.state.selectedItems.size>0;if(this.refs.batchDeleteBtn&&this.refs.batchDeleteBtn.classList.toggle("hidden",!t),this.refs.batchDeleteDebridBtn){const e=t&&"torrents"===this.state.mode;this.refs.batchDeleteDebridBtn.classList.toggle("hidden",!e)}if(t){const t=this.state.selectedItems.size,e="torrents"===this.state.mode?"Torrent":"NZB",s="torrents"===this.state.mode?"Torrents":"NZBs";if(this.refs.batchDeleteBtn){const a=1===t?`Delete ${e}`:`Delete ${t} ${s}`,r=this.refs.batchDeleteBtn.querySelector("span");r&&(r.textContent=a)}if(this.refs.batchDeleteDebridBtn&&"torrents"===this.state.mode){const e=1===t?"Remove From Debrid":`Remove ${t} From Debrid`,s=this.refs.batchDeleteDebridBtn.querySelector("span");s&&(s.textContent=e)}}else{if(this.refs.batchDeleteBtn){const t=this.refs.batchDeleteBtn.querySelector("span");t&&(t.textContent="Delete Selected")}if(this.refs.batchDeleteDebridBtn){const t=this.refs.batchDeleteDebridBtn.querySelector("span");t&&(t.textContent="Remove From Debrid")}}}loadData(){"torrents"===this.state.mode?this.loadTorrents():this.loadNZBs()}async loadNZBs(){try{const t=await window.decypharrUtils.fetcher("/api/nzbs");if(!t.ok)throw new Error("Failed to fetch NZBs");const e=await t.json();this.state.nzbs=e.nzbs||[],this.updateCategories(),this.applyFilters(),this.renderData()}catch(t){console.error("Error loading NZBs:",t),window.decypharrUtils.createToast("Error loading NZBs","error")}}updateCategories(){const t="torrents"===this.state.mode?this.state.torrents:this.state.nzbs;this.state.categories=new Set(t.map(t=>t.category).filter(Boolean))}applyFilters(){"torrents"===this.state.mode?this.filterTorrents():this.filterNZBs()}filterNZBs(){let t=[...this.state.nzbs];this.state.selectedCategory&&(t=t.filter(t=>t.category===this.state.selectedCategory)),this.state.selectedState&&(t=t.filter(t=>t.status===this.state.selectedState)),t.sort((t,e)=>{switch(this.state.sortBy){case"added_on":return new Date(e.added_on)-new Date(t.added_on);case"added_on_asc":return new Date(t.added_on)-new Date(e.added_on);case"name_asc":return t.name.localeCompare(e.name);case"name_desc":return e.name.localeCompare(t.name);case"size_desc":return(e.total_size||0)-(t.total_size||0);case"size_asc":return(t.total_size||0)-(e.total_size||0);case"progress_desc":return(e.progress||0)-(t.progress||0);case"progress_asc":return(t.progress||0)-(e.progress||0);default:return 0}}),this.state.filteredItems=t}renderData(){"torrents"===this.state.mode?this.renderTorrents():this.renderNZBs()}renderNZBs(){const t=(this.state.currentPage-1)*this.state.itemsPerPage,e=t+this.state.itemsPerPage,s=this.state.filteredItems.slice(t,e),a=this.refs.dataList;a.innerHTML="",0===s.length?this.refs.emptyState.classList.remove("hidden"):(this.refs.emptyState.classList.add("hidden"),s.forEach(t=>{const e=document.createElement("tr");e.className="hover cursor-pointer",e.setAttribute("data-id",t.id),e.setAttribute("data-name",t.name),e.setAttribute("data-category",t.category||"");const s=Math.round(t.progress||0),r=this.formatBytes(t.total_size||0),n=this.formatETA(t.eta||0),i=this.formatAge(t.date_posted),o=this.getStatusBadge(t.status);e.innerHTML=`\n
\n \n
\n
\n
${t.name}
\n
\n
${r}
\n
\n
\n
\n \n
\n ${s}%\n
\n
\n
${n}
\n
\n ${t.category||"N/A"}\n
\n
${o}
\n
${i}
\n
\n
\n \n
\n
\n `,a.appendChild(e)})),this.updatePagination(),this.updateSelectionUI()}getStatusBadge(t){return{downloading:'Downloading',completed:'Completed',paused:'Paused',failed:'Failed',queued:'Queued',processing:'Processing',verifying:'Verifying',repairing:'Repairing',extracting:'Extracting'}[t]||'Unknown'}formatETA(t){if(!t||t<=0)return"N/A";const e=Math.floor(t/3600),s=Math.floor(t%3600/60);return e>0?`${e}h ${s}m`:`${s}m`}formatAge(t){if(!t)return"N/A";const e=new Date-new Date(t),s=Math.floor(e/864e5);return 0===s?"Today":1===s?"1 day":`${s} days`}formatBytes(t){if(!t||0===t)return"0 B";const e=Math.floor(Math.log(t)/Math.log(1024));return parseFloat((t/Math.pow(1024,e)).toFixed(2))+" "+["B","KB","MB","GB","TB"][e]}bindContextMenu(){this.refs.dataList.addEventListener("contextmenu",t=>{const e=t.target.closest("tr[data-id]");e&&(t.preventDefault(),this.showContextMenu(t,e))}),document.addEventListener("click",t=>{const e=this.refs.torrentContextMenu,s=this.refs.nzbContextMenu;e.contains(t.target)||s.contains(t.target)||this.hideContextMenu()}),this.refs.torrentContextMenu.addEventListener("click",t=>{const e=t.target.closest("[data-action]")?.dataset.action;e&&(this.handleContextAction(e),this.hideContextMenu())}),this.refs.nzbContextMenu.addEventListener("click",t=>{const e=t.target.closest("[data-action]")?.dataset.action;e&&(this.handleContextAction(e),this.hideContextMenu())})}showContextMenu(t,e){const{pageX:s,pageY:a}=t,{clientWidth:r,clientHeight:n}=document.documentElement;if("torrents"===this.state.mode){this.state.selectedItemContextMenu={id:e.dataset.hash,name:e.dataset.name,category:e.dataset.category||"",type:"torrent"};const t=this.refs.torrentContextMenu;t.querySelector(".torrent-name").textContent=this.state.selectedItemContextMenu.name,t.style.left=`${Math.min(s,r-200)}px`,t.style.top=`${Math.min(a,n-150)}px`,t.classList.remove("hidden")}else{this.state.selectedItemContextMenu={id:e.dataset.id,name:e.dataset.name,category:e.dataset.category||"",type:"nzb"};const t=this.refs.nzbContextMenu;t.querySelector(".nzb-name").textContent=this.state.selectedItemContextMenu.name,t.style.left=`${Math.min(s,r-200)}px`,t.style.top=`${Math.min(a,n-150)}px`,t.classList.remove("hidden")}}hideContextMenu(){this.refs.torrentContextMenu.classList.add("hidden"),this.refs.nzbContextMenu.classList.add("hidden"),this.state.selectedItemContextMenu=null}async handleContextAction(t){const e=this.state.selectedItemContextMenu;e&&("torrent"===e.type?await this.handleTorrentAction(t,e):await this.handleNZBAction(t,e))}async handleTorrentAction(t,e){const s={"copy-magnet":async()=>{try{await navigator.clipboard.writeText(`magnet:?xt=urn:btih:${e.hash}`),window.decypharrUtils.createToast("Magnet link copied to clipboard")}catch(t){window.decypharrUtils.createToast("Failed to copy magnet link","error")}},"copy-name":async()=>{try{await navigator.clipboard.writeText(e.name),window.decypharrUtils.createToast("Torrent name copied to clipboard")}catch(t){window.decypharrUtils.createToast("Failed to copy torrent name","error")}},delete:async()=>{await this.deleteTorrent(e.hash,e.category,!1)}};s[t]&&await s[t]()}async handleNZBAction(t,e){const s={pause:async()=>{try{if(!(await window.decypharrUtils.fetcher(`/api/nzbs/${e.id}/pause`,{method:"POST"})).ok)throw new Error("Failed to pause NZB");window.decypharrUtils.createToast("NZB paused successfully"),this.loadData()}catch(t){window.decypharrUtils.createToast("Failed to pause NZB","error")}},resume:async()=>{try{if(!(await window.decypharrUtils.fetcher(`/api/nzbs/${e.id}/resume`,{method:"POST"})).ok)throw new Error("Failed to resume NZB");window.decypharrUtils.createToast("NZB resumed successfully"),this.loadData()}catch(t){window.decypharrUtils.createToast("Failed to resume NZB","error")}},retry:async()=>{try{if(!(await window.decypharrUtils.fetcher(`/api/nzbs/${e.id}/retry`,{method:"POST"})).ok)throw new Error("Failed to retry NZB");window.decypharrUtils.createToast("NZB retry started successfully"),this.loadData()}catch(t){window.decypharrUtils.createToast("Failed to retry NZB","error")}},"copy-name":async()=>{try{await navigator.clipboard.writeText(e.name),window.decypharrUtils.createToast("NZB name copied to clipboard")}catch(t){window.decypharrUtils.createToast("Failed to copy NZB name","error")}},delete:async()=>{await this.deleteNZB(e.id)}};s[t]&&await s[t]()}async deleteNZB(t){try{if(!(await window.decypharrUtils.fetcher(`/api/nzbs/${t}`,{method:"DELETE"})).ok)throw new Error("Failed to delete NZB");window.decypharrUtils.createToast("NZB deleted successfully"),this.loadData()}catch(t){window.decypharrUtils.createToast("Failed to delete NZB","error")}}async loadTorrents(){try{this.refs.refreshBtn.disabled=!0,this.refs.paginationInfo.textContent="Loading torrents...";const t=await window.decypharrUtils.fetcher("/api/torrents");if(!t.ok)throw new Error("Failed to fetch torrents");const e=await t.json();this.state.torrents=e,this.state.categories=new Set(e.map(t=>t.category).filter(Boolean)),this.updateUI()}catch(t){console.error("Error loading torrents:",t),window.decypharrUtils.createToast(`Error loading torrents: ${t.message}`,"error")}finally{this.refs.refreshBtn.disabled=!1}}updateUI(){this.applyFilters(),this.updateCategoryFilter(),this.renderData(),this.updatePagination(),this.updateSelectionUI(),this.toggleEmptyState()}filterTorrents(){let t=[...this.state.torrents];this.state.selectedCategory&&(t=t.filter(t=>t.category===this.state.selectedCategory)),this.state.selectedState&&(t=t.filter(t=>t.state?.toLowerCase()===this.state.selectedState.toLowerCase())),t=this.sortTorrents(t),this.state.filteredItems=t}sortTorrents(t){const[e,s]=this.state.sortBy.includes("_asc")||this.state.sortBy.includes("_desc")?[this.state.sortBy.split("_").slice(0,-1).join("_"),this.state.sortBy.endsWith("_asc")?"asc":"desc"]:[this.state.sortBy,"desc"];return t.sort((t,a)=>{let r,n;switch(e){case"name":r=t.name?.toLowerCase()||"",n=a.name?.toLowerCase()||"";break;case"size":r=t.size||0,n=a.size||0;break;case"progress":r=t.progress||0,n=a.progress||0;break;case"added_on":r=t.added_on||0,n=a.added_on||0;break;default:r=t[e]||0,n=a[e]||0}return"string"==typeof r?"asc"===s?r.localeCompare(n):n.localeCompare(r):"asc"===s?r-n:n-r})}renderTorrents(){const t=(this.state.currentPage-1)*this.state.itemsPerPage,e=Math.min(t+this.state.itemsPerPage,this.state.filteredItems.length),s=this.state.filteredItems.slice(t,e);this.refs.dataList.innerHTML=s.map(t=>this.torrentRowTemplate(t)).join("")}torrentRowTemplate(t){const e=(100*t.progress).toFixed(1),s=this.state.selectedItems.has(t.hash);new Date(t.added_on).toLocaleString();return`\n
\n `}getStateColor(t){return{downloading:"badge-primary",pausedup:"badge-success",error:"badge-error",completed:"badge-success"}[t?.toLowerCase()]||"badge-ghost"}updateCategoryFilter(){const t=Array.from(this.state.categories).sort(),e=[''].concat(t.map(t=>``));this.refs.categoryFilter.innerHTML=e.join("")}updatePagination(){const t=Math.ceil(this.state.filteredItems.length/this.state.itemsPerPage),e=(this.state.currentPage-1)*this.state.itemsPerPage,s=Math.min(e+this.state.itemsPerPage,this.state.filteredItems.length);if(this.refs.paginationInfo.textContent=`Showing ${this.state.filteredItems.length>0?e+1:0}-${s} of ${this.state.filteredItems.length} torrents`,this.refs.paginationControls.innerHTML="",t<=1)return;const a=this.createPaginationButton("❮",this.state.currentPage-1,1===this.state.currentPage);this.refs.paginationControls.appendChild(a);let r=Math.max(1,this.state.currentPage-Math.floor(2.5)),n=Math.min(t,r+5-1);n-r+1<5&&(r=Math.max(1,n-5+1));for(let t=r;t<=n;t++){const e=this.createPaginationButton(t,t,!1,t===this.state.currentPage);this.refs.paginationControls.appendChild(e)}const i=this.createPaginationButton("❯",this.state.currentPage+1,this.state.currentPage===t);this.refs.paginationControls.appendChild(i)}createPaginationButton(t,e,s=!1,a=!1){const r=document.createElement("button");return r.className=`join-item btn btn-sm ${a?"btn-active":""} ${s?"btn-disabled":""}`,r.textContent=t,r.disabled=s,s||r.addEventListener("click",()=>{this.state.currentPage=e,this.updateUI()}),r}updateSelectionUI(){const t=new Set(this.state.filteredItems.map(t=>t.hash));this.state.selectedItems.forEach(e=>{t.has(e)||this.state.selectedItems.delete(e)}),this.refs.batchDeleteBtn.classList.toggle("hidden",0===this.state.selectedItems.size),this.refs.batchDeleteDebridBtn.classList.toggle("hidden",0===this.state.selectedItems.size);const e=this.state.filteredItems.slice((this.state.currentPage-1)*this.state.itemsPerPage,this.state.currentPage*this.state.itemsPerPage);this.refs.selectAll.checked=e.length>0&&e.every(t=>this.state.selectedItems.has(t.hash)),this.refs.selectAll.indeterminate=e.some(t=>this.state.selectedItems.has(t.hash))&&!e.every(t=>this.state.selectedItems.has(t.hash))}toggleEmptyState(){const t=0===("torrents"===this.state.mode?this.state.torrents:this.state.nzbs).length;this.refs.emptyState&&this.refs.emptyState.classList.toggle("hidden",!t);const e=document.querySelector(".card:has(#dataList)");e&&e.classList.toggle("hidden",t)}setFilter(t,e){"category"===t?this.state.selectedCategory=e:"state"===t&&(this.state.selectedState=e),this.state.currentPage=1,this.updateUI()}setSort(t){this.state.sortBy=t,this.state.currentPage=1,this.updateUI()}toggleSelectAll(t){this.state.filteredItems.slice((this.state.currentPage-1)*this.state.itemsPerPage,this.state.currentPage*this.state.itemsPerPage).forEach(e=>{t?this.state.selectedItems.add(e.hash):this.state.selectedItems.delete(e.hash)}),this.updateUI()}toggleItemSelection(t,e){e?this.state.selectedItems.add(t):this.state.selectedItems.delete(t),this.updateSelectionUI(),this.updateBatchActions()}async deleteTorrent(t,e,s=!1){if(confirm(`Are you sure you want to delete this torrent${s?" from "+e:""}?`))try{const a=`/api/torrents/${encodeURIComponent(e)}/${t}?removeFromDebrid=${s}`,r=await window.decypharrUtils.fetcher(a,{method:"DELETE"});if(!r.ok)throw new Error(await r.text());window.decypharrUtils.createToast("Torrent deleted successfully"),await this.loadTorrents()}catch(t){console.error("Error deleting torrent:",t),window.decypharrUtils.createToast(`Failed to delete torrent: ${t.message}`,"error")}}async deleteSelectedItems(t=!1){const e=this.state.selectedItems.size;if(0===e){const t="torrents"===this.state.mode?"torrents":"NZBs";return void window.decypharrUtils.createToast(`No ${t} selected for deletion`,"warning")}const s="torrents"===this.state.mode?"torrent":"NZB",a="torrents"===this.state.mode?"torrents":"NZBs";if(confirm(`Are you sure you want to delete ${e} ${e>1?a:s}${t?" from debrid":""}?`))try{if("torrents"===this.state.mode){const e=Array.from(this.state.selectedItems).join(","),s=await window.decypharrUtils.fetcher(`/api/torrents/?hashes=${encodeURIComponent(e)}&removeFromDebrid=${t}`,{method:"DELETE"});if(!s.ok)throw new Error(await s.text())}else{const t=Array.from(this.state.selectedItems).map(t=>window.decypharrUtils.fetcher(`/api/nzbs/${t}`,{method:"DELETE"})),e=await Promise.all(t);for(const t of e)if(!t.ok)throw new Error(await t.text())}window.decypharrUtils.createToast(`${e} ${e>1?a:s} deleted successfully`),this.state.selectedItems.clear(),await this.loadData()}catch(t){console.error(`Error deleting ${a}:`,t),window.decypharrUtils.createToast(`Failed to delete some ${a}: ${t.message}`,"error")}}startAutoRefresh(){this.refreshInterval=setInterval(()=>{this.loadData()},5e3),window.addEventListener("beforeunload",()=>{this.refreshInterval&&clearInterval(this.refreshInterval)})}escapeHtml(t){const e={"&":"&","<":"<",">":">",'"':""","'":"'"};return t?t.replace(/[&<>"']/g,t=>e[t]):""}loadModeFromURL(){const t=new URLSearchParams(window.location.search).get("mode");this.state.mode="nzbs"===t||"torrents"===t?t:"torrents",this.setModeUI(this.state.mode)}setModeUI(t){"torrents"===t?(this.refs.torrentsMode.classList.remove("btn-outline"),this.refs.torrentsMode.classList.add("btn-primary"),this.refs.nzbsMode.classList.remove("btn-primary"),this.refs.nzbsMode.classList.add("btn-outline"),this.refs.torrentsHeaders.classList.remove("hidden"),this.refs.nzbsHeaders.classList.add("hidden"),this.refs.emptyStateTitle.textContent="No Torrents Found",this.refs.emptyStateMessage.textContent="You haven't added any torrents yet. Start by adding your first download!",this.refs.batchDeleteDebridBtn.classList.remove("hidden")):(this.refs.nzbsMode.classList.remove("btn-outline"),this.refs.nzbsMode.classList.add("btn-primary"),this.refs.torrentsMode.classList.remove("btn-primary"),this.refs.torrentsMode.classList.add("btn-outline"),this.refs.nzbsHeaders.classList.remove("hidden"),this.refs.torrentsHeaders.classList.add("hidden"),this.refs.emptyStateTitle.textContent="No NZBs Found",this.refs.emptyStateMessage.textContent="You haven't added any NZB downloads yet. Start by adding your first NZB!",this.refs.batchDeleteDebridBtn.classList.add("hidden"))}updateURL(t){const e=new URL(window.location);e.searchParams.set("mode",t),window.history.replaceState({},"",e)}}