Twitter/X Media Batch Downloader

Batch download all images and videos from a Twitter/X account, including withheld accounts, in original quality.

当前为 2025-09-10 提交的版本,查看 最新版本

// ==UserScript==
// @name         Twitter/X Media Batch Downloader
// @description  Batch download all images and videos from a Twitter/X account, including withheld accounts, in original quality.
// @icon         data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIKICAgIGlkPSJMYXllcl8xIiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjQgMjQ7IgogICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8ZyBmaWxsPSIjMWRhMGYxIj4KICAgICAgICA8cG9seWdvbgogICAgICAgICAgICBwb2ludHM9IjEyLjE1Mzk5MiwxMC43Mjk1NTMgOC4wODg2ODQsNS4wNDExOTkgNS45MjA0MSw1LjA0MTE5OSAxMC45NTYyOTksMTIuMDg3MDk3IDExLjU5MDIxLDEyLjk3MzQ1IDE1LjkwMDYzNSwxOS4wMDk1ODMgMTguMDY4OTA5LDE5LjAwOTU4MyAxMi43ODUyMTcsMTEuNjE1OTA2ICAiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICAgZD0iTTIxLjE1OTc5LDFIMi44NDAyMUMxLjgyMzg1MywxLDEsMS44MjM4NTMsMSwyLjg0MDIxdjE4LjMxOTU4QzEsMjIuMTc2MTQ3LDEuODIzODUzLDIzLDIuODQwMjEsMjNoMTguMzE5NTggICBDMjIuMTc2MTQ3LDIzLDIzLDIyLjE3NjE0NywyMywyMS4xNTk3OVYyLjg0MDIxQzIzLDEuODIzODUzLDIyLjE3NjE0NywxLDIxLjE1OTc5LDF6IE0xNS4yMzUzNTIsMjBsLTQuMzYyNTQ5LTYuMjEzMDEzICAgTDUuNDExNDM4LDIwSDRsNi4yNDY4ODctNy4xMDQ2NzVMNCw0aDQuNzY0NjQ4bDQuMTMwMTI3LDUuODgxOTU4TDE4LjA2OTU4LDRoMS40MTEzNzdsLTUuOTU2OTcsNi43NzU2MzVMMjAsMjBIMTUuMjM1MzUyeiIgLz4KICAgIDwvZz4KPC9zdmc+Cg==
// @namespace    https://xbatch.online
// @supportURL   https://www.patreon.com/exyezed
// @homepageURL  https://www.patreon.com/exyezed
// @version      5.4
// @author       afkarxyz
// @antifeature  payment Unlock access to the Twitter/X Media Batch Downloader script by becoming a paid member! Join the membership to receive your Patreon auth code.
// @license      MIT
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/dexie.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/dexie-export-import.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/umd/index.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/preact.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/hooks/dist/hooks.umd.js
// @require      https://cdn.jsdelivr.net/npm/@preact/[email protected]/dist/signals-core.min.js
// @connect      api.xbatch.online
// @connect      backup.xbatch.online
// @connect      pbs.twimg.com
// @connect      video.twimg.com
// ==/UserScript==

(function () {
  "use strict";

  const { h, render } = preact;
  const { useState, useEffect, useRef } = preactHooks;
  const { signal, effect } = preactSignalsCore;

  const ICONS = {
    download: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>`,
    hardDriveDownload: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hard-drive-download-icon lucide-hard-drive-download"><path d="M12 2v8"/><path d="m16 6-4 4-4-4"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 18h.01"/><path d="M10 18h.01"/></svg>`,
    cloudCheck: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-check-icon lucide-cloud-check"><path d="m17 15-5.5 5.5L9 18"/><path d="M5 17.743A7 7 0 1 1 15.71 10h1.79a4.5 4.5 0 0 1 1.5 8.742"/></svg>`,
    send: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send-icon lucide-send"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>`,
    layers: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-layers-icon lucide-layers"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/></svg>`,
    betweenHorizontal: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-between-horizontal-start-icon lucide-between-horizontal-start"><rect width="13" height="7" x="8" y="3" rx="1"/><path d="m2 9 3 3-3 3"/><rect width="13" height="7" x="8" y="14" rx="1"/></svg>`,
    play: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play-icon lucide-play"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/></svg>`,
    stop: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-icon lucide-square"><rect width="18" height="18" x="3" y="3" rx="2"/></svg>`,
    images: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-images-icon lucide-images"><path d="m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16"/><path d="M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2"/><circle cx="13" cy="7" r="1" fill="currentColor"/><rect x="8" y="2" width="14" height="14" rx="2"/></svg>`,
    twitter: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-twitter-icon lucide-twitter"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"/></svg>`,
    rotateKey: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-key-icon lucide-rotate-ccw-key"><path d="m14.5 9.5 1 1"/><path d="m15.5 8.5-4 4"/><path d="M3 12a9 9 0 1 0 9-9 9.74 9.74 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><circle cx="10" cy="14" r="2"/></svg>`,
    checkCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big-icon lucide-circle-check-big"><path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/></svg>`,
    circleX: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
    rabbit: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rabbit-icon lucide-rabbit"><path d="M13 16a3 3 0 0 1 2.24 5"/><path d="M18 12h.01"/><path d="M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3"/><path d="M20 8.54V4a2 2 0 1 0-4 0v3"/><path d="M7.612 12.524a3 3 0 1 0-1.6 4.3"/></svg>`,
    authTokenIcon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key-icon lucide-key"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/></svg>`,
    patreonAuthIcon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-icon lucide-lock"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
    patreonAuthUnlockIcon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-open-icon lucide-lock-open"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></svg>`,
    shieldCheck: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-check-icon lucide-shield-check"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>`,
    shieldX: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-x-icon lucide-shield-x"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m14.5 9.5-5 5"/><path d="m9.5 9.5 5 5"/></svg>`,
    cloudDownload: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-download-icon lucide-cloud-download"><path d="M12 13v8l-4-4"/><path d="m12 21 4-4"/><path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"/></svg>`,
    spinner: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-circle-icon lucide-loader-circle"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>`,
    sun: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-icon lucide-sun"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>`,
    moon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon-icon lucide-moon"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>`,
    close: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,
    eye: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye-icon lucide-eye"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>`,
    eyeOff: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye-off-icon lucide-eye-off"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>`,
    alert: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-alert-icon lucide-circle-alert"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>`,
    triangleAlert: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,
    notepadText: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-notepad-text-icon lucide-notepad-text"><path d="M8 2v4"/><path d="M12 2v4"/><path d="M16 2v4"/><rect width="16" height="18" x="4" y="4" rx="2"/><path d="M8 10h6"/><path d="M8 14h8"/><path d="M8 18h5"/></svg>`,
    undo: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo2-icon lucide-undo-2"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/></svg>`,
    server: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server-icon lucide-server"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg>`,
    photo: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-icon lucide-image"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
    video: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-video-icon lucide-video"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>`,
    animatedGif: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-play-icon lucide-image-play"><path d="M15 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"/><path d="M21 12.17V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"/><path d="m6 21 5-5"/><circle cx="9" cy="9" r="2"/></svg>`,
    trash: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`,
    database: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-database-icon lucide-database"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></svg>`,
    chevronLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>`,
    chevronRight: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>`,
    chevronsLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-left-icon lucide-chevrons-left"><path d="m11 17-5-5 5-5"/><path d="m18 17-5-5 5-5"/></svg>`,
    chevronsRight: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-right-icon lucide-chevrons-right"><path d="m6 17 5-5-5-5"/><path d="m13 17 5-5-5-5"/></svg>`,
    shredder: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shredder-icon lucide-shredder"><path d="M10 22v-5"/><path d="M14 19v-2"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M18 20v-3"/><path d="M2 13h20"/><path d="M20 13V7l-5-5H6a2 2 0 0 0-2 2v9"/><path d="M6 20v-3"/></svg>`,
    frown: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-frown-icon lucide-frown"><circle cx="12" cy="12" r="10"/><path d="M16 16s-1.5-2-4-2-4 2-4 2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg>`,
    upload: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-upload-icon lucide-upload"><path d="M12 3v12"/><path d="m17 8-5-5-5 5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>`,
    resetIcon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-icon lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>`,
    fileOutput: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-output-icon lucide-file-output"><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4 7V4a2 2 0 0 1 2-2 2 2 0 0 0-2 2"/><path d="M4.063 20.999a2 2 0 0 0 2 1L18 22a2 2 0 0 0 2-2V7l-5-5H6"/><path d="m5 11-3 3"/><path d="m5 17-3-3h10"/></svg>`,
    fileInput: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-input-icon lucide-file-input"><path d="M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M2 15h10"/><path d="m9 18 3-3-3-3"/></svg>`,
  };

  const db = new Dexie("TwitterXMediaBatchDownloader");
  db.version(1).stores({
    settings: "key",
    mediaData: "username, data, timestamp",
  });

  db.version(2)
    .stores({
      settings: "key",
      mediaData: "cacheKey, username, timelineType, mediaType, data, timestamp",
    })
    .upgrade((tx) => {
      return tx.mediaData.toCollection().modify((item) => {
        item.cacheKey = `${item.username}_media_all`;
        item.timelineType = "media";
        item.mediaType = "all";
      });
    });

  const state = {
    isModalOpen: signal(false),
    activeTab: signal("dashboard"),
    authToken: signal(""),
    patreonAuth: signal(""),
    isVerified: signal(false),
    isLoading: signal(false),
    mediaData: signal(null),
    error: signal(null),
    errorType: signal("general"),
    success: signal(null),
    theme: signal("light"),
    downloadProgress: signal(0),
    currentUsername: signal(""),
    downloadedFiles: signal(0),
    totalFileSize: signal(0),
    selectedApi: signal("default"),
    fetchMode: signal("fresh"),
    selectedCacheUser: signal(null),
    cacheMediaPage: signal(1),
    mediaType: signal("all"),
    timelineType: signal("media"),
    isDownloading: signal(false),
    isDownloadingCurrent: signal(false),
    fetchType: signal("single"),
    batchSize: signal(100),
    startingBatch: signal(0),
    currentBatchPage: signal(0),
    isAutoBatch: signal(false),
    batchedMediaData: signal([]),
    currentBatchData: signal([]),
    loadingDirection: signal(null),
    concurrentLimit: signal(20),
    showBatchDatabase: signal(false),
    loadedFromDatabase: signal(false),
    loadedDatabaseConfig: signal(null),
    previewModalOpen: signal(false),
    previewMediaData: signal(null),
    previewCurrentIndex: signal(0),
    previewFilters: signal({
      photo: false,
      video: false,
      animatedGif: false,
    }),
  };

  async function loadSettings() {
    try {
      const authTokenDoc = await db.settings.get("authToken");
      const patreonAuthDoc = await db.settings.get("patreonAuth");
      const isVerifiedDoc = await db.settings.get("isVerified");
      const themeDoc = await db.settings.get("theme");
      const selectedApiDoc = await db.settings.get("selectedApi");
      const mediaTypeDoc = await db.settings.get("mediaType");
      const timelineTypeDoc = await db.settings.get("timelineType");
      const batchSizeDoc = await db.settings.get("batchSize");
      const startingBatchDoc = await db.settings.get("startingBatch");
      const concurrentLimitDoc = await db.settings.get("concurrentLimit");
      const showBatchDatabaseDoc = await db.settings.get("showBatchDatabase");

      if (authTokenDoc) state.authToken.value = authTokenDoc.value;
      if (patreonAuthDoc) state.patreonAuth.value = patreonAuthDoc.value;
      if (isVerifiedDoc) state.isVerified.value = isVerifiedDoc.value;
      if (themeDoc) state.theme.value = themeDoc.value;
      if (selectedApiDoc) state.selectedApi.value = selectedApiDoc.value;
      if (mediaTypeDoc) state.mediaType.value = mediaTypeDoc.value;
      if (timelineTypeDoc) state.timelineType.value = timelineTypeDoc.value;
      if (batchSizeDoc) state.batchSize.value = batchSizeDoc.value;
      if (startingBatchDoc) state.startingBatch.value = startingBatchDoc.value;
      if (concurrentLimitDoc)
        state.concurrentLimit.value = concurrentLimitDoc.value;
      if (showBatchDatabaseDoc)
        state.showBatchDatabase.value = showBatchDatabaseDoc.value;
    } catch (error) {
      console.error("Failed to load settings:", error);
    }
  }

  async function saveSettings() {
    try {
      await db.settings.bulkPut([
        { key: "authToken", value: state.authToken.value },
        { key: "patreonAuth", value: state.patreonAuth.value },
        { key: "isVerified", value: state.isVerified.value },
        { key: "theme", value: state.theme.value },
        { key: "selectedApi", value: state.selectedApi.value },
        { key: "mediaType", value: state.mediaType.value },
        { key: "timelineType", value: state.timelineType.value },
        { key: "batchSize", value: state.batchSize.value },
        { key: "startingBatch", value: state.startingBatch.value },
        { key: "concurrentLimit", value: state.concurrentLimit.value },
        { key: "showBatchDatabase", value: state.showBatchDatabase.value },
      ]);
    } catch (error) {
      console.error("Failed to save settings:", error);
    }
  }

  const styles = `
        .tmd-modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            backdrop-filter: blur(4px);
            z-index: 9999;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.2s ease-out;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        @keyframes slideUp {
            from { transform: translateY(20px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        .tmd-modal {
            width: 90%;
            max-width: 600px;
            max-height: 80vh;
            border-radius: 12px;
            overflow: hidden;
            animation: slideUp 0.3s ease-out;
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }

        .tmd-modal.dark {
            background: hsl(240 5.9% 10%);
            color: hsl(240 4.8% 95.9%);
            border: 1px solid hsl(240 5% 40% / 0.5);
            box-shadow: 0 0 0 1px hsl(240 5% 35% / 0.2), 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
        }

        .tmd-modal.light {
            background: white;
            color: hsl(240 5.9% 10%);
            border: 1px solid hsl(240 5.9% 90%);
        }

        .tmd-header {
            padding: 20px;
            border-bottom: 1px solid;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .dark .tmd-header {
            border-color: hsl(240 3.7% 15.9%);
        }

        .light .tmd-header {
            border-color: hsl(240 5.9% 90%);
        }

        .tmd-header-title {
            font-size: 18px;
            font-weight: 600;
            color: hsl(204.17deg 87.55% 52.75%);
        }

        .tmd-header-controls {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        .tmd-theme-toggle {
            padding: 8px;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .dark .tmd-theme-toggle {
            background: hsl(240 3.7% 15.9%);
        }

        .dark .tmd-theme-toggle:hover {
            background: hsl(240 5.3% 26.1%);
        }

        .light .tmd-theme-toggle {
            background: hsl(240 5.9% 95%);
        }

        .light .tmd-theme-toggle:hover {
            background: hsl(240 5.9% 90%);
        }

        .tmd-reset-toggle {
            color: inherit;
        }

        .dark .tmd-reset-toggle:hover {
            background: hsl(37.7deg 92.1% 50.2% / 0.2);
            color: hsl(37.7deg 92.1% 50.2%);
        }

        .light .tmd-reset-toggle:hover {
            background: hsl(37.7deg 92.1% 50.2% / 0.1);
            color: hsl(37.7deg 92.1% 50.2%);
        }

        .tmd-preview-modal {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.95);
            z-index: 10000;
            display: flex;
            flex-direction: column;
            animation: fadeIn 0.3s ease-out;
        }

        .tmd-preview-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 16px 20px;
            background: rgba(0, 0, 0, 0.8);
            backdrop-filter: blur(10px);
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .tmd-preview-account-info {
            display: flex;
            align-items: center;
            gap: 12px;
            color: white;
        }

        .tmd-preview-profile-img {
            width: 48px;
            height: 48px;
            border-radius: 50%;
            object-fit: cover;
            border: 2px solid rgba(255, 255, 255, 0.2);
        }

        .tmd-preview-account-details h3 {
            margin: 0;
            font-size: 16px;
            font-weight: 600;
        }

        .tmd-preview-account-details p {
            margin: 2px 0 0 0;
            font-size: 14px;
            opacity: 0.7;
        }

        .tmd-preview-stats {
            display: flex;
            gap: 16px;
            font-size: 12px;
            opacity: 0.8;
        }

        .tmd-preview-close {
            background: rgba(255, 255, 255, 0.1);
            border: none;
            color: white;
            padding: 8px;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .tmd-preview-close:hover {
            background: hsl(0 84.2% 60.2% / 0.2);
            color: hsl(0 84.2% 60.2%);
        }

        .tmd-preview-content {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            overflow: hidden;
        }

        .tmd-preview-media {
            max-width: 90%;
            max-height: 90%;
            object-fit: contain;
            user-select: none;
            border-radius: 8px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
        }

        .tmd-preview-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            background: rgba(0, 0, 0, 0.7);
            border: none;
            color: white;
            padding: 12px;
            border-radius: 50%;
            cursor: pointer;
            transition: all 0.2s;
            z-index: 10001;
        }

        .tmd-preview-nav:hover {
            background: rgba(0, 0, 0, 0.9);
            transform: translateY(-50%) scale(1.1);
        }

        .tmd-preview-nav:disabled {
            opacity: 0.3;
            cursor: not-allowed;
        }

        .tmd-preview-nav:disabled:hover {
            transform: translateY(-50%);
        }

        .tmd-preview-nav-prev {
            left: 20px;
        }

        .tmd-preview-nav-next {
            right: 20px;
        }

        .tmd-preview-counter {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 14px;
            backdrop-filter: blur(10px);
        }

        .tmd-preview-content {
            touch-action: pan-y;
            user-select: none;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            -webkit-touch-callout: none;
        }

        .tmd-preview-media {
            pointer-events: none;
            -webkit-user-drag: none;
            -khtml-user-drag: none;
            -moz-user-drag: none;
            -o-user-drag: none;
            user-drag: none;
        }

        .tmd-preview-content video {
            pointer-events: auto;
        }

        @media (max-width: 768px) {
            .tmd-preview-content {
                touch-action: manipulation;
            }
            
            .tmd-preview-modal {
                touch-action: manipulation;
            }
        }

        .tmd-preview-nav:focus {
            outline: 2px solid rgba(255, 255, 255, 0.5);
            outline-offset: 2px;
        }

        .tmd-preview-close:focus {
            outline: 2px solid rgba(255, 255, 255, 0.5);
            outline-offset: 2px;
        }

        .tmd-preview-filters {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        .tmd-preview-filter-bar {
            padding: 12px 20px;
            background: rgba(0, 0, 0, 0.8);
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 12px;
        }

        .tmd-preview-filter-btn {
            background: rgba(255, 255, 255, 0.1);
            border: 1px solid rgba(255, 255, 255, 0.2);
            color: white;
            padding: 4px 8px;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            font-size: 11px;
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .tmd-preview-filter-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .tmd-preview-filter-btn.active {
            background: hsl(204.17deg 87.55% 52.75%);
            border-color: hsl(204.17deg 87.55% 52.75%);
        }

        @media (max-width: 768px) {
            .tmd-preview-header {
                padding: 12px 16px;
            }

            .tmd-preview-account-info {
                gap: 8px;
            }

            .tmd-preview-profile-img {
                width: 40px;
                height: 40px;
            }

            .tmd-preview-account-details h3 {
                font-size: 14px;
            }

            .tmd-preview-account-details p {
                font-size: 12px;
            }

            .tmd-preview-stats {
                gap: 12px;
                font-size: 11px;
            }

            .tmd-preview-nav {
                padding: 8px;
            }

            .tmd-preview-nav-prev {
                left: 10px;
            }

            .tmd-preview-nav-next {
                right: 10px;
            }

            .tmd-preview-counter {
                bottom: 10px;
                font-size: 12px;
                padding: 6px 12px;
            }
        }

        .tmd-close-btn {
            padding: 8px;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .dark .tmd-close-btn {
            background: hsl(240 3.7% 15.9%);
        }

        .dark .tmd-close-btn:hover {
            background: hsl(0deg 84.2% 60.2% / 0.2);
        }

        .dark .tmd-close-btn:hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
        }

        .light .tmd-close-btn {
            background: hsl(240 5.9% 95%);
        }

        .light .tmd-close-btn:hover {
            background: hsl(0deg 84.2% 60.2% / 0.1);
        }

        .light .tmd-close-btn:hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
        }

        .tmd-tabs {
            display: flex;
            padding: 0 20px;
            gap: 16px;
            border-bottom: 1px solid;
        }

        .dark .tmd-tabs {
            border-color: hsl(240 3.7% 15.9%);
        }

        .light .tmd-tabs {
            border-color: hsl(240 5.9% 90%);
        }

        .tmd-tab {
            padding: 12px 0;
            cursor: pointer;
            border-bottom: 2px solid transparent;
            transition: all 0.2s;
            font-weight: 500;
        }

        .dark .tmd-tab {
            color: hsl(240 5% 64.9%);
        }

        .light .tmd-tab {
            color: hsl(240 3.8% 46.1%);
        }

        .tmd-tab:hover {
            color: hsl(204.17deg 87.55% 52.75%);
        }

        .tmd-tab.active {
            color: hsl(204.17deg 87.55% 52.75%);
            border-bottom-color: hsl(204.17deg 87.55% 52.75%);
        }

        .tmd-content {
            padding: 20px;
            min-height: 150px;
            max-height: calc(80vh - 150px);
            overflow-y: auto;
            display: flex;
            flex-direction: column;
        }

        .tmd-input-group {
            margin-bottom: 20px;
        }

        .tmd-label {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            font-weight: 500;
        }

        .tmd-input {
            width: 100%;
            padding: 10px 12px;
            padding-right: 40px;
            border-radius: 8px;
            border: 1px solid;
            font-size: 14px;
            transition: all 0.2s;
            font-family: monospace;
            box-sizing: border-box;
        }

        .tmd-input-wrapper {
            position: relative;
            width: 100%;
        }

        .tmd-input-toggle {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            cursor: pointer;
            padding: 4px;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0.5;
            transition: opacity 0.2s;
        }

        .tmd-input-toggle:hover {
            opacity: 1;
        }

        .dark .tmd-input {
            background: hsl(240 3.7% 15.9%);
            border-color: hsl(240 5.3% 26.1%);
            color: hsl(240 4.8% 95.9%);
        }

        .dark .tmd-input:focus {
            border-color: hsl(204.17deg 87.55% 52.75%);
            outline: none;
        }

        .light .tmd-input {
            background: white;
            border-color: hsl(240 5.9% 90%);
            color: hsl(240 5.9% 10%);
        }

        .light .tmd-input:focus {
            border-color: hsl(204.17deg 87.55% 52.75%);
            outline: none;
        }

        .tmd-button {
            padding: 10px 20px;
            border-radius: 8px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            border: none;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            margin: 0;
        }

        .tmd-button-container {
            display: flex;
            justify-content: center;
            margin-top: 15px;
        }

        .tmd-button-primary {
            background: hsl(204.17deg 87.55% 52.75%);
            color: white;
        }

        .tmd-button-primary:hover {
            background: hsl(204.17deg 87.55% 45%);
        }

        .tmd-button-primary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .tmd-button-secondary {
            background: hsl(142.1deg 76.2% 36.3%);
            color: white;
        }

        .tmd-button-secondary:hover {
            background: hsl(142.1deg 76.2% 30%);
        }

        .tmd-button-secondary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .tmd-button-outline {
            background: transparent;
            border: 1px solid;
        }

        .dark .tmd-button-outline {
            border-color: hsl(240 5.3% 26.1%);
            color: hsl(240 4.8% 95.9%);
        }

        .dark .tmd-button-outline:hover {
            background: hsl(240 3.7% 15.9%);
        }

        .light .tmd-button-outline {
            border-color: hsl(240 5.9% 85%);
            color: hsl(240 5.9% 10%);
        }

        .light .tmd-button-outline:hover {
            background: hsl(240 5.9% 95%);
        }

        .tmd-button-outline:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .tmd-button-outline:not(:disabled):hover {
            background: hsl(240 3.7% 15.9%);
        }

        .dark .tmd-button-outline:not(:disabled):hover {
            background: hsl(240 5.3% 26.1%);
            border-color: hsl(240 5.3% 35%);
        }

        .light .tmd-button-outline:not(:disabled):hover {
            background: hsl(240 5.9% 90%);
            border-color: hsl(240 5.9% 70%);
        }

        .tmd-spinner {
            animation: spin 1s linear infinite;
        }

        .tmd-error {
            padding: 12px;
            border-radius: 8px;
            margin-bottom: 20px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .tmd-error.auth {
            background: hsl(45deg 100% 51% / 0.1);
            color: hsl(45deg 100% 45%);
        }

        .tmd-error.api,
        .tmd-error.username {
            background: hsl(45deg 100% 51% / 0.1);
            color: hsl(45deg 100% 45%);
        }

        .tmd-error.general {
            background: hsl(0deg 84.2% 60.2% / 0.1);
            color: hsl(0deg 84.2% 60.2%);
        }

        .tmd-error.failed {
            background: hsl(0deg 84.2% 60.2% / 0.1);
            color: hsl(0deg 84.2% 60.2%);
        }

        .tmd-error-icon {
            flex-shrink: 0;
            display: flex;
            align-items: center;
        }

        .tmd-success {
            padding: 12px;
            border-radius: 8px;
            background: hsl(142.1deg 76.2% 36.3% / 0.1);
            color: hsl(142.1deg 76.2% 36.3%);
            margin-bottom: 20px;
            display: flex;
            align-items: flex-start;
            gap: 8px;
        }

        .tmd-success-icon {
            flex-shrink: 0;
            display: flex;
            align-items: center;
            margin-top: 2px;
        }

        .tmd-info-card {
            padding: 16px;
            border-radius: 8px;
            margin-bottom: 20px;
        }

        .dark .tmd-info-card {
            background: hsl(240 3.7% 15.9%);
            border: 1px solid hsl(240 5.3% 26.1%);
        }

        .light .tmd-info-card {
            background: hsl(240 4.8% 95.9%);
            border: 1px solid hsl(240 5.9% 90%);
        }

        .tmd-info-card.clickable {
            transition: all 0.2s ease;
            cursor: default;
            position: relative;
            z-index: 1;
        }

        .tmd-info-card.clickable:hover {
            z-index: 10;
        }

        .dark .tmd-info-card.clickable:hover {
            background: hsl(240 3.7% 18%);
            border-color: hsl(240 5.3% 30%);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
        }

        .light .tmd-info-card.clickable:hover {
            background: hsl(240 4.8% 98%);
            border-color: hsl(240 5.9% 80%);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
        }

        .tmd-info-row {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
        }

        .tmd-info-row:last-child {
            margin-bottom: 0;
        }

        .tmd-info-label {
            font-weight: 500;
        }

        .tmd-progress-bar {
            width: 100%;
            height: 8px;
            border-radius: 4px;
            overflow: hidden;
            margin-bottom: 0;
        }

        .dark .tmd-progress-bar {
            background: hsl(240 3.7% 15.9%);
        }

        .light .tmd-progress-bar {
            background: hsl(240 5.9% 90%);
        }

        .tmd-progress-fill {
            height: 100%;
            background: linear-gradient(90deg,
                hsl(204.17deg 87.55% 45%),
                hsl(204.17deg 87.55% 52.75%)
            );
            transition: width 0.3s ease;
        }

        .tmd-progress-info {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            font-size: 14px;
        }

        .dark .tmd-progress-info {
            color: hsl(240 4.8% 95.9%);
        }

        .light .tmd-progress-info {
            color: hsl(240 5.9% 10%);
        }

        .dl-icon {
            display: inline-flex;
            margin-left: 6px;
            padding: 4px;
            border-radius: 4px;
            transition: all 0.2s;
            cursor: pointer;
        }

        .tmd-radio-group {
            display: flex;
            gap: 20px;
            margin-top: 8px;
        }

        .tmd-radio-item {
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
        }

        .tmd-radio {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            border: 2px solid;
            position: relative;
            transition: all 0.2s;
        }

        .dark .tmd-radio {
            border-color: hsl(240 5.3% 26.1%);
            background: hsl(240 3.7% 15.9%);
        }

        .light .tmd-radio {
            border-color: hsl(240 5.9% 85%);
            background: white;
        }

        .tmd-radio.checked {
            border-color: hsl(204.17deg 87.55% 52.75%);
        }

        .tmd-radio.checked::after {
            content: '';
            position: absolute;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: hsl(204.17deg 87.55% 52.75%);
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        .tmd-radio-label {
            font-size: 14px;
            user-select: none;
        }

        .tmd-button-square {
            width: 40px;
            height: 40px;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 8px;
            flex-shrink: 0;
        }

        .tmd-icon-button {
            background: transparent;
            border: none;
            padding: 6px;
            cursor: pointer;
            border-radius: 6px;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            opacity: 0.7;
        }

        .tmd-icon-button:hover {
            opacity: 1;
            background: hsl(0deg 84.2% 60.2% / 0.1);
        }

        .tmd-icon-button:hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
            transition: stroke 0.3s ease;
        }

        .tmd-delete-button {
            transition: all 0.3s ease;
        }

        .tmd-delete-button:hover {
            background: hsl(0deg 84.2% 60.2% / 0.1) !important;
            border-color: hsl(0deg 84.2% 60.2%) !important;
        }

        .tmd-delete-button:hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
            transition: stroke 0.3s ease;
        }

        .tmd-load-button {
            transition: all 0.3s ease;
        }

        .tmd-load-button:hover {
            background: hsl(142.1deg 76.2% 36.3% / 0.1) !important;
            border-color: hsl(142.1deg 76.2% 36.3%) !important;
            color: hsl(142.1deg 76.2% 36.3%) !important;
        }

        .tmd-load-button:hover svg {
            stroke: hsl(142.1deg 76.2% 36.3%);
            transition: stroke 0.3s ease;
        }

        .tmd-download-current-button {
            transition: all 0.3s ease;
        }

        .tmd-download-current-button:hover {
            background: hsl(142.1deg 76.2% 36.3% / 0.1) !important;
            border-color: hsl(142.1deg 76.2% 36.3%) !important;
        }

        .tmd-download-current-button:hover svg {
            stroke: hsl(142.1deg 76.2% 36.3%);
            transition: stroke 0.3s ease;
        }

        .tmd-shred-button {
            transition: all 0.3s ease;
        }

        .tmd-shred-button:hover {
            color: hsl(0deg 84.2% 60.2%) !important;
            border-color: hsl(0deg 84.2% 60.2%) !important;
            background: hsl(0deg 84.2% 60.2% / 0.1) !important;
        }

        .tmd-shred-button:hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
            transition: stroke 0.3s ease;
        }

        .tmd-export-button {
            transition: all 0.3s ease;
        }

        .tmd-export-button:hover {
            background: hsl(204.17deg 87.55% 52.75% / 0.1) !important;
            color: hsl(204.17deg 87.55% 52.75%) !important;
            border-color: hsl(204.17deg 87.55% 52.75%) !important;
        }

        .tmd-export-button:hover svg {
            stroke: hsl(204.17deg 87.55% 52.75%);
            transition: stroke 0.3s ease;
        }

        .tmd-import-button {
            transition: all 0.3s ease;
        }

        .tmd-import-button:hover {
            background: hsl(270deg 60% 50% / 0.1) !important;
            color: hsl(270deg 60% 50%) !important;
            border-color: hsl(270deg 60% 50%) !important;
        }

        .tmd-import-button:hover svg {
            stroke: hsl(270deg 60% 50%);
            transition: stroke 0.3s ease;
        }

        .tmd-preview-button {
            transition: all 0.3s ease;
        }

        .tmd-preview-button:hover {
            background: hsl(204.17deg 87.55% 52.75% / 0.1) !important;
            border-color: hsl(204.17deg 87.55% 52.75%) !important;
        }

        .tmd-preview-button:hover svg {
            stroke: hsl(204.17deg 87.55% 52.75%);
            transition: stroke 0.3s ease;
        }

        .tmd-download-single-button {
            transition: all 0.3s ease;
        }

        .tmd-download-single-button:hover {
            background: hsl(142.1deg 76.2% 36.3% / 0.1) !important;
            border-color: hsl(142.1deg 76.2% 36.3%) !important;
        }

        .tmd-download-single-button:hover svg {
            stroke: hsl(142.1deg 76.2% 36.3%);
            transition: stroke 0.3s ease;
        }

        .tmd-batch-controls {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 15px;
        }

        .tmd-batch-controls-row {
            display: flex;
            gap: 8px;
            justify-content: center;
        }

        .tmd-button-stop:not(:disabled):hover {
            background: hsl(0deg 84.2% 60.2% / 0.1) !important;
            border-color: hsl(0deg 84.2% 60.2%) !important;
            color: hsl(0deg 84.2% 60.2%) !important;
        }

        .tmd-button-stop:not(:disabled):hover svg {
            stroke: hsl(0deg 84.2% 60.2%);
        }

        .tmd-button-start:not(:disabled):hover {
            background: hsl(142.1deg 76.2% 36.3% / 0.1) !important;
            border-color: hsl(142.1deg 76.2% 36.3%) !important;
            color: hsl(142.1deg 76.2% 36.3%) !important;
        }

        .tmd-button-start:not(:disabled):hover svg {
            stroke: hsl(142.1deg 76.2% 36.3%);
        }

        .tmd-tweet-link {
            text-decoration: none;
            cursor: pointer;
            transition: all 0.2s;
        }

        .tmd-tweet-link:hover {
            opacity: 0.8;
            text-decoration: underline;
            filter: brightness(1.2);
        }

        .tmd-filter-button {
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .tmd-filter-button.tmd-filter-photo:hover {
            background: hsl(142.1deg 76.2% 36.3% / 0.1) !important;
            border-color: hsl(142.1deg 76.2% 36.3%) !important;
        }

        .tmd-filter-button.tmd-filter-photo:hover svg {
            stroke: hsl(142.1deg 76.2% 36.3%);
        }

        .tmd-filter-button.tmd-filter-video:hover {
            background: hsl(37.7deg 92.1% 50.2% / 0.1) !important;
            border-color: hsl(37.7deg 92.1% 50.2%) !important;
        }

        .tmd-filter-button.tmd-filter-video:hover svg {
            stroke: hsl(37.7deg 92.1% 50.2%);
        }

        .tmd-filter-button.tmd-filter-gif:hover {
            background: hsl(270deg 60% 50% / 0.1) !important;
            border-color: hsl(270deg 60% 50%) !important;
        }

        .tmd-filter-button.tmd-filter-gif:hover svg {
            stroke: hsl(270deg 60% 50%);
        }

        .tmd-alert-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            backdrop-filter: blur(4px);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            animation: fadeIn 0.2s ease-out;
        }

        .tmd-alert {
            background: white;
            color: hsl(240 5.9% 10%);
            border-radius: 12px;
            padding: 24px;
            max-width: 400px;
            width: 90%;
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
            animation: slideUp 0.3s ease-out;
        }

        .tmd-alert.dark {
            background: hsl(240 5.9% 10%);
            color: hsl(240 4.8% 95.9%);
            border: 1px solid hsl(240 3.7% 15.9%);
        }

        .dark .tmd-alert {
            background: hsl(240 5.9% 10%);
            color: hsl(240 4.8% 95.9%);
            border: 1px solid hsl(240 3.7% 15.9%);
        }

        .tmd-alert-title {
            font-size: 18px;
            font-weight: 600;
            margin-bottom: 12px;
        }

        .tmd-alert-message {
            margin-bottom: 20px;
            opacity: 0.9;
        }

        .tmd-alert-buttons {
            display: flex;
            gap: 12px;
            justify-content: flex-end;
        }

        .tmd-alert-button {
            padding: 8px 16px;
            border-radius: 8px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            border: none;
        }

        .tmd-alert-button-cancel {
            background: transparent;
            border: 1px solid;
        }

        .dark .tmd-alert-button-cancel {
            border-color: hsl(240 5.3% 26.1%);
            color: hsl(240 4.8% 95.9%);
        }

        .dark .tmd-alert-button-cancel:hover {
            background: hsl(240 3.7% 15.9%);
        }

        .light .tmd-alert-button-cancel {
            border-color: hsl(240 5.9% 85%);
            color: hsl(240 5.9% 10%);
        }

        .light .tmd-alert-button-cancel:hover {
            background: hsl(240 5.9% 95%);
        }

        .tmd-alert-button-confirm {
            background: hsl(0deg 84.2% 60.2%);
            color: white;
        }

        .tmd-alert-button-confirm:hover {
            background: hsl(0deg 84.2% 50%);
        }

        input[type="number"]::-webkit-inner-spin-button,
        input[type="number"]::-webkit-outer-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        input[type="number"] {
            -moz-appearance: textfield;
        }

        .tmd-media-list-container {
            flex: 1;
            overflow-y: auto;
            overflow-x: hidden;
            margin-bottom: 16px;
            padding: 2px;
            position: relative;
        }

        .tmd-database-content {
            display: flex;
            flex-direction: column;
            height: 100%;
        }

        .tmd-media-list-wrapper {
            flex: 1;
            display: flex;
            flex-direction: column;
            min-height: 0;
        }

            @media (max-width: 480px) {
                .tmd-service-data-row {
                    flex-direction: column !important;
                    gap: 20px !important;
                }

                .tmd-service-data-row > div {
                    flex: none !important;
                    width: 100% !important;
                }

                .tmd-content {
                    max-height: calc(100vh - 180px);
                    padding: 15px;
                }

                .tmd-modal {
                    max-height: 90vh;
                }

                .tmd-media-list-container {
                    max-height: calc(100vh - 380px);
                }

                .tmd-download-current-button,
                .tmd-button-secondary {
                    padding: 8px 12px !important;
                    font-size: 13px !important;
                    white-space: nowrap !important;
                    min-width: auto !important;
                }

                .tmd-download-current-button span,
                .tmd-button-secondary span {
                    font-size: 13px !important;
                }

                .tmd-download-current-button svg,
                .tmd-button-secondary svg {
                    width: 16px !important;
                    height: 16px !important;
                }

                .tmd-button-container .tmd-button-primary,
                .tmd-button-container .tmd-button-secondary {
                    padding: 8px 12px !important;
                    font-size: 13px !important;
                    white-space: nowrap !important;
                    min-width: auto !important;
                    flex: 1 !important;
                    max-width: 150px !important;
                }

                .tmd-button-container {
                    display: flex !important;
                    gap: 8px !important;
                    justify-content: center !important;
                }

                .tmd-button-primary span,
                .tmd-button-secondary span {
                    font-size: 13px !important;
                }

                .tmd-button-primary svg,
                .tmd-button-secondary svg {
                    width: 16px !important;
                    height: 16px !important;
                }

            .tmd-database-content > div:first-child {
                flex-wrap: wrap !important;
                justify-content: flex-start !important;
            }
            
            .tmd-database-content .tmd-button-outline:not(.tmd-button-square) {
                padding: 6px 10px !important;
                font-size: 12px !important;
                min-width: auto !important;
            }
            
            .tmd-database-content .tmd-button-outline:not(.tmd-button-square) span {
                font-size: 12px !important;
            }
            
            .tmd-database-content .tmd-button-outline:not(.tmd-button-square) svg {
                width: 14px !important;
                height: 14px !important;
            }

            .tmd-database-content > div:first-child > div:last-child {
                margin-left: 0 !important;
                margin-top: 8px !important;
                width: 100% !important;
                justify-content: flex-start !important;
            }
            
            .tmd-database-content input[type="number"] {
                width: 45px !important;
                padding: 4px 6px !important;
            }
        }
    `;

  GM_addStyle(styles);

  function Modal() {
    const modalRef = useRef(null);
    const [showResetConfirm, setShowResetConfirm] = useState(false);

    useEffect(() => {
      function handleEscape(e) {
        const activeElement = document.activeElement;
        const isTyping =
          activeElement &&
          (activeElement.tagName === "INPUT" ||
            activeElement.tagName === "TEXTAREA" ||
            activeElement.tagName === "SELECT" ||
            activeElement.contentEditable === "true");

        if (e.key === "Escape" && state.isModalOpen.value && !isTyping) {
          state.isModalOpen.value = false;
        }
      }

      document.addEventListener("keydown", handleEscape);
      return () => document.removeEventListener("keydown", handleEscape);
    }, []);

    const handleOverlayClick = (e) => {
      if (e.target === e.currentTarget) {
        const activeElement = document.activeElement;
        const isInputFocused =
          activeElement &&
          (activeElement.tagName === "INPUT" ||
            activeElement.tagName === "TEXTAREA" ||
            activeElement.tagName === "SELECT");

        if (!isInputFocused) {
          state.isModalOpen.value = false;
        }
      }
    };

    const toggleTheme = () => {
      state.theme.value = state.theme.value === "dark" ? "light" : "dark";
      saveSettings();
    };

    const handleFactoryReset = async () => {
      try {
        await db.settings.clear();
        await db.mediaData.clear();

        state.authToken.value = "";
        state.patreonAuth.value = "";
        state.isVerified.value = false;
        state.theme.value = "light";
        state.selectedApi.value = "default";
        state.mediaType.value = "all";
        state.timelineType.value = "media";
        state.batchSize.value = 100;
        state.startingBatch.value = 0;
        state.concurrentLimit.value = 20;
        state.showBatchDatabase.value = false;
        state.mediaData.value = null;
        state.error.value = null;
        state.success.value = null;
        state.downloadProgress.value = 0;
        state.currentUsername.value = "";
        state.downloadedFiles.value = 0;
        state.totalFileSize.value = 0;
        state.fetchMode.value = "fresh";
        state.selectedCacheUser.value = null;
        state.cacheMediaPage.value = 1;
        state.isDownloading.value = false;
        state.isDownloadingCurrent.value = false;
        state.fetchType.value = "single";
        state.currentBatchPage.value = 0;
        state.isAutoBatch.value = false;
        state.batchedMediaData.value = [];
        state.currentBatchData.value = [];
        state.loadingDirection.value = null;

        setShowResetConfirm(false);

        state.success.value = "Factory reset completed successfully!";
        setTimeout(() => {
          if (state.success.value === "Factory reset completed successfully!") {
            state.success.value = null;
          }
        }, 2000);

        state.activeTab.value = "auth";
      } catch (error) {
        console.error("Failed to perform factory reset:", error);
        state.error.value =
          "Failed to perform factory reset. Please try again.";
        state.errorType.value = "general";
        setTimeout(() => {
          if (
            state.error.value ===
            "Failed to perform factory reset. Please try again."
          ) {
            state.error.value = null;
          }
        }, 2000);
      }
    };

    if (!state.isModalOpen.value) return null;

    return h(
      "div",
      null,
      showResetConfirm &&
        h(AlertDialog, {
          title: "Factory Reset",
          message:
            "WARNING: This will permanently delete ALL settings and cached data. The extension will be reset to its initial state. This action cannot be undone. Are you absolutely sure?",
          onConfirm: handleFactoryReset,
          onCancel: () => setShowResetConfirm(false),
          confirmLabel: "Reset",
        }),
      h(PreviewModal),
      h(
        "div",
        {
          className: "tmd-modal-overlay",
          onClick: handleOverlayClick,
        },
        h(
          "div",
          {
            className: `tmd-modal ${state.theme.value}`,
            ref: modalRef,
          },
          h(
            "div",
            { className: "tmd-header" },
            h(
              "div",
              { className: "tmd-header-title" },
              state.currentUsername.value
                ? `@${state.currentUsername.value}`
                : "No User Detected"
            ),
            h(
              "div",
              { className: "tmd-header-controls" },
              h("div", {
                className: "tmd-theme-toggle",
                onClick: toggleTheme,
                dangerouslySetInnerHTML: {
                  __html: state.theme.value === "dark" ? ICONS.sun : ICONS.moon,
                },
                title: "Toggle theme",
              }),
              h("div", {
                className: "tmd-theme-toggle tmd-reset-toggle",
                onClick: () => setShowResetConfirm(true),
                dangerouslySetInnerHTML: { __html: ICONS.resetIcon },
                title: "Factory reset - Clear all data and settings",
              }),
              h("div", {
                className: "tmd-close-btn",
                onClick: () => (state.isModalOpen.value = false),
                dangerouslySetInnerHTML: { __html: ICONS.close },
              })
            )
          ),
          h(
            "div",
            { className: "tmd-tabs" },
            h(
              "div",
              {
                className: `tmd-tab ${
                  state.activeTab.value === "dashboard" ? "active" : ""
                }`,
                onClick: () => (state.activeTab.value = "dashboard"),
              },
              "Dashboard"
            ),
            h(
              "div",
              {
                className: `tmd-tab ${
                  state.activeTab.value === "database" ? "active" : ""
                }`,
                onClick: () => (state.activeTab.value = "database"),
              },
              "Database"
            ),
            h(
              "div",
              {
                className: `tmd-tab ${
                  state.activeTab.value === "settings" ? "active" : ""
                }`,
                onClick: () => (state.activeTab.value = "settings"),
              },
              "Settings"
            ),
            h(
              "div",
              {
                className: `tmd-tab ${
                  state.activeTab.value === "auth" ? "active" : ""
                }`,
                onClick: () => (state.activeTab.value = "auth"),
              },
              "Auth"
            )
          ),
          h(
            "div",
            { className: "tmd-content" },
            state.success.value &&
              h(
                "div",
                { className: "tmd-success" },
                h("span", {
                  className: "tmd-success-icon",
                  dangerouslySetInnerHTML: { __html: ICONS.checkCircle },
                }),
                h("span", null, state.success.value)
              ),
            state.error.value &&
              state.errorType.value === "general" &&
              h(
                "div",
                { className: "tmd-error general" },
                h("span", {
                  className: "tmd-error-icon",
                  dangerouslySetInnerHTML: { __html: ICONS.alert },
                }),
                h("span", null, state.error.value)
              ),
            state.activeTab.value === "dashboard"
              ? h(DashboardTab)
              : state.activeTab.value === "database"
              ? h(DatabaseTab)
              : state.activeTab.value === "settings"
              ? h(SettingsTab)
              : h(AuthTab)
          )
        )
      )
    );
  }

  function DashboardTab() {
    const [currentFiles, setCurrentFiles] = useState(
      state.downloadedFiles.value
    );
    const [currentSize, setCurrentSize] = useState(state.totalFileSize.value);

    useEffect(() => {
      const cleanupDownloadedFiles = effect(() => {
        setCurrentFiles(state.downloadedFiles.value);
      });

      const cleanupTotalFileSize = effect(() => {
        setCurrentSize(state.totalFileSize.value);
      });

      return () => {
        if (typeof cleanupDownloadedFiles === "function") {
          cleanupDownloadedFiles();
        }
        if (typeof cleanupTotalFileSize === "function") {
          cleanupTotalFileSize();
        }
      };
    }, []);

    const fetchBatchMediaData = async (page = 0, _isRetry = false) => {
      if (state.fetchMode.value === "cache") {
        if (!state.currentUsername.value) {
          state.error.value =
            "No username detected. Please navigate to a user profile.";
          state.errorType.value = "username";
          setTimeout(() => {
            if (
              state.error.value ===
              "No username detected. Please navigate to a user profile."
            ) {
              state.error.value = null;
            }
          }, 2000);
          return null;
        }

        state.isLoading.value = true;
        state.error.value = null;
        state.errorType.value = "general";

        try {
          const normalizedUsername = state.currentUsername.value.toLowerCase();
          const cacheKey = `${normalizedUsername}_${state.timelineType.value}_${state.mediaType.value}`;
          const cachedData = await db.mediaData.get(cacheKey);

          if (cachedData) {
            const startIdx = page * state.batchSize.value;
            const endIdx = startIdx + state.batchSize.value;
            const batchTimeline = cachedData.data.timeline.slice(
              startIdx,
              endIdx
            );

            if (page === 0 || page === state.startingBatch.value) {
              state.batchedMediaData.value = batchTimeline;
              state.currentBatchData.value = batchTimeline;
              state.mediaData.value = {
                account_info: cachedData.data.account_info,
                timeline: batchTimeline,
                metadata: {
                  has_more: endIdx < cachedData.data.timeline.length,
                },
              };
            } else {
              const updatedTimeline = [
                ...state.batchedMediaData.value,
                ...batchTimeline,
              ];
              state.batchedMediaData.value = updatedTimeline;
              state.currentBatchData.value = batchTimeline;
              state.mediaData.value = {
                ...state.mediaData.value,
                timeline: updatedTimeline,
                metadata: {
                  has_more: endIdx < cachedData.data.timeline.length,
                },
              };
            }

            state.currentBatchPage.value = page;
            state.isLoading.value = false;
            return state.mediaData.value.metadata;
          } else {
            state.isLoading.value = false;
            state.error.value = `No cached data found for @${state.currentUsername.value}. Please fetch fresh data first.`;
            state.errorType.value = "general";
            setTimeout(() => {
              if (
                state.error.value ===
                `No cached data found for @${state.currentUsername.value}. Please fetch fresh data first.`
              ) {
                state.error.value = null;
              }
            }, 2000);
            return null;
          }
        } catch (error) {
          console.error("Failed to load cached data:", error);
          state.isLoading.value = false;
          state.error.value = `Failed to load cached data: ${
            error.message || "Unknown error"
          }`;
          state.errorType.value = "general";
          setTimeout(() => {
            if (
              state.error.value &&
              state.error.value.startsWith("Failed to load cached data:")
            ) {
              state.error.value = null;
            }
          }, 2000);
          return null;
        }
      }

      if (
        !state.patreonAuth.value ||
        !state.authToken.value ||
        !state.currentUsername.value
      ) {
        state.error.value =
          "Please configure authentication tokens and ensure username is detected";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (
            state.error.value ===
            "Please configure authentication tokens and ensure username is detected"
          ) {
            state.error.value = null;
          }
        }, 2000);
        return null;
      }

      state.isLoading.value = true;
      state.error.value = null;

      const api =
        state.selectedApi.value === "backup"
          ? "https://backup.xbatch.online"
          : "https://api.xbatch.online";

      try {
        const url = `${api}/metadata/${state.timelineType.value}/${state.batchSize.value}/${page}/${state.mediaType.value}/${state.currentUsername.value}/${state.authToken.value}/${state.patreonAuth.value}`;

        const response = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            timeout: 60000,
            onload: (res) => {
              if (res.status === 200) {
                try {
                  const data = JSON.parse(res.responseText);
                  resolve(data);
                } catch (parseError) {
                  reject(new Error("Invalid response format"));
                }
              } else {
                reject(new Error(`API error (${res.status})`));
              }
            },
            onerror: () => reject(new Error("Network error")),
            ontimeout: () => reject(new Error("Request timeout")),
          });
        });

        if (!response || !response.account_info || !response.timeline) {
          throw new Error("Invalid response format");
        }

        if (response.timeline.length === 0) {
          state.isLoading.value = false;
          const mediaTypeText =
            state.mediaType.value === "gif"
              ? "GIFs"
              : state.mediaType.value === "image"
              ? "images"
              : state.mediaType.value === "video"
              ? "videos"
              : "media";
          state.error.value = `@${state.currentUsername.value} doesn't have any ${mediaTypeText}. No data was cached.`;
          state.errorType.value = "username";
          setTimeout(() => {
            if (
              state.error.value &&
              state.error.value.includes("doesn't have any")
            ) {
              state.error.value = null;
            }
          }, 3000);
          return null;
        }

        if (page === 0 || page === state.startingBatch.value) {
          state.batchedMediaData.value = response.timeline;
          state.currentBatchData.value = response.timeline;
          state.mediaData.value = {
            account_info: response.account_info,
            timeline: response.timeline,
            metadata: response.metadata,
          };
        } else {
          const updatedTimeline = [
            ...state.batchedMediaData.value,
            ...response.timeline,
          ];
          state.batchedMediaData.value = updatedTimeline;
          state.currentBatchData.value = response.timeline;
          state.mediaData.value = {
            ...state.mediaData.value,
            timeline: updatedTimeline,
            metadata: response.metadata,
          };
        }

        state.currentBatchPage.value = page;
        state.isLoading.value = false;

        if (response.timeline.length > 0) {
          const normalizedUsername = state.currentUsername.value.toLowerCase();
          const isBatch =
            state.fetchType.value === "batch" ||
            state.fetchType.value === "autoBatch";
          const cacheKey = isBatch
            ? `${normalizedUsername}_${state.timelineType.value}_${state.mediaType.value}_batch`
            : `${normalizedUsername}_${state.timelineType.value}_${state.mediaType.value}`;

          if (isBatch) {
            const existingCache = await db.mediaData.get(cacheKey);

            if (
              page === 0 ||
              page === state.startingBatch.value ||
              !existingCache
            ) {
              await db.mediaData.put({
                cacheKey: cacheKey,
                username: normalizedUsername,
                timelineType: state.timelineType.value,
                mediaType: state.mediaType.value,
                data: response,
                timestamp: Date.now(),
                isBatch: true,
              });
            } else {
              const combinedTimeline = [
                ...existingCache.data.timeline,
                ...response.timeline,
              ];
              await db.mediaData.put({
                cacheKey: cacheKey,
                username: normalizedUsername,
                timelineType: state.timelineType.value,
                mediaType: state.mediaType.value,
                data: {
                  ...response,
                  timeline: combinedTimeline,
                },
                timestamp: Date.now(),
                isBatch: true,
              });
            }
          } else {
            await db.mediaData.put({
              cacheKey: cacheKey,
              username: normalizedUsername,
              timelineType: state.timelineType.value,
              mediaType: state.mediaType.value,
              data: response,
              timestamp: Date.now(),
              isBatch: false,
            });
          }
        }

        return response.metadata;
      } catch (error) {
        state.isLoading.value = false;
        state.error.value = error.message;
        state.errorType.value = "api";
        setTimeout(() => {
          if (state.error.value === error.message) {
            state.error.value = null;
          }
        }, 2000);
        return null;
      }
    };

    const handleNextBatch = async () => {
      state.loadingDirection.value = "next";
      const metadata = await fetchBatchMediaData(
        state.currentBatchPage.value + 1
      );
      if (metadata && !metadata.has_more) {
        state.success.value = "All batches fetched successfully!";
        setTimeout(() => {
          if (state.success.value === "All batches fetched successfully!") {
            state.success.value = null;
          }
        }, 2000);
      }
      state.loadingDirection.value = null;
    };

    const handlePreviousBatch = async () => {
      if (state.currentBatchPage.value > state.startingBatch.value) {
        state.loadingDirection.value = "prev";
        await fetchBatchMediaData(state.currentBatchPage.value - 1);
        state.loadingDirection.value = null;
      }
    };

    const startAutoBatch = async () => {
      state.isAutoBatch.value = true;
      let currentPage =
        state.currentBatchPage.value || state.startingBatch.value;

      while (state.isAutoBatch.value) {
        const metadata = await fetchBatchMediaData(currentPage);

        if (!metadata || !metadata.has_more) {
          state.isAutoBatch.value = false;
          state.success.value = "Auto batch completed!";
          setTimeout(() => {
            if (state.success.value === "Auto batch completed!") {
              state.success.value = null;
            }
          }, 2000);
          break;
        }

        currentPage++;

        await new Promise((resolve) => setTimeout(resolve, 500));
      }
    };

    const stopAutoBatch = () => {
      state.isAutoBatch.value = false;
    };

    const downloadCurrentBatch = async () => {
      if (
        !state.currentBatchData.value ||
        state.currentBatchData.value.length === 0
      ) {
        state.error.value = "No current batch data available";
        state.errorType.value = "general";
        setTimeout(() => {
          if (state.error.value === "No current batch data available") {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      if (state.isDownloadingCurrent.value) return;

      state.isDownloadingCurrent.value = true;

      const tempMediaData = state.mediaData.value;
      state.mediaData.value = {
        ...state.mediaData.value,
        timeline: state.currentBatchData.value,
      };

      await downloadMedia();

      state.mediaData.value = tempMediaData;
      state.isDownloadingCurrent.value = false;
    };

    const generateNewAuthToken = async () => {
      if (!state.patreonAuth.value) return null;

      const api =
        state.selectedApi.value === "backup"
          ? "https://backup.xbatch.online"
          : "https://api.xbatch.online";

      const url = `${api}/token/${state.patreonAuth.value}`;

      try {
        const response = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            timeout: 60000,
            onload: (res) => {
              if (res.status === 200) {
                try {
                  const data = JSON.parse(res.responseText);
                  resolve(data);
                } catch (parseError) {
                  reject(new Error("Invalid token response"));
                }
              } else {
                reject(new Error(`Token generation failed: ${res.status}`));
              }
            },
            onerror: () => reject(new Error("Network error")),
            ontimeout: () => reject(new Error("Request timeout")),
          });
        });

        if (response.auth_token) {
          state.authToken.value = response.auth_token;
          await saveSettings();
          console.log("✓ Auth token regenerated successfully");
          return response.auth_token;
        }
      } catch (error) {
        console.error("Failed to generate new auth token:", error);
      }
      return null;
    };

    const updateDatabase = async () => {
      if (!state.mediaData.value || !state.loadedFromDatabase.value) return;

      const originalFetchType = state.fetchType.value;
      const originalFetchMode = state.fetchMode.value;

      state.fetchMode.value = "fresh";

      if (state.loadedDatabaseConfig.value) {
        const { isBatch, timelineType, mediaType } =
          state.loadedDatabaseConfig.value;
        state.fetchType.value = isBatch ? "single" : "single";
        state.timelineType.value = timelineType;
        state.mediaType.value = mediaType;
      }

      state.isLoading.value = true;
      state.error.value = null;

      try {
        const api =
          state.selectedApi.value === "backup"
            ? "https://backup.xbatch.online"
            : "https://api.xbatch.online";

        const url = `${api}/metadata/${state.timelineType.value}/${state.mediaType.value}/${state.currentUsername.value}/${state.authToken.value}/${state.patreonAuth.value}`;

        const response = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            timeout: 60000,
            onload: (res) => {
              if (res.status === 200) {
                try {
                  const data = JSON.parse(res.responseText);
                  resolve(data);
                } catch (parseError) {
                  reject(new Error("Invalid response format"));
                }
              } else {
                reject(new Error(`API error (${res.status})`));
              }
            },
            onerror: () => reject(new Error("Network error")),
            ontimeout: () => reject(new Error("Request timeout")),
          });
        });

        if (response && response.timeline && response.timeline.length > 0) {
          state.mediaData.value = response;

          if (
            state.loadedDatabaseConfig.value &&
            state.loadedDatabaseConfig.value.cacheKey
          ) {
            const { cacheKey, isBatch } = state.loadedDatabaseConfig.value;
            await db.mediaData.put({
              cacheKey: cacheKey,
              username: state.currentUsername.value.toLowerCase(),
              timelineType: state.timelineType.value,
              mediaType: state.mediaType.value,
              data: response,
              timestamp: Date.now(),
              isBatch: isBatch || false,
            });
          }

          state.success.value = "Database updated successfully!";
          setTimeout(() => {
            if (state.success.value === "Database updated successfully!") {
              state.success.value = null;
            }
          }, 2000);
        } else {
          throw new Error("No data received from server");
        }
      } catch (error) {
        console.error("Failed to update database:", error);
        state.error.value = `Failed to update: ${error.message}`;
        state.errorType.value = "general";
        setTimeout(() => {
          if (
            state.error.value &&
            state.error.value.startsWith("Failed to update:")
          ) {
            state.error.value = null;
          }
        }, 2000);
      } finally {
        state.isLoading.value = false;
        state.fetchType.value = originalFetchType;
        state.fetchMode.value = originalFetchMode;
      }
    };

    const fetchMediaData = async (isRetry = false) => {
      if (state.fetchMode.value === "cache") {
        if (!state.currentUsername.value) {
          state.error.value =
            "No username detected. Please navigate to a user profile.";
          state.errorType.value = "username";
          setTimeout(() => {
            if (
              state.error.value ===
              "No username detected. Please navigate to a user profile."
            ) {
              state.error.value = null;
            }
          }, 2000);
          return;
        }

        state.isLoading.value = true;
        state.error.value = null;
        state.errorType.value = "general";

        try {
          const normalizedUsername = state.currentUsername.value.toLowerCase();
          const cacheKey = `${normalizedUsername}_${state.timelineType.value}_${state.mediaType.value}`;
          const cachedData = await db.mediaData.get(cacheKey);

          if (cachedData) {
            state.mediaData.value = cachedData.data;
            state.isLoading.value = false;
          } else {
            state.isLoading.value = false;
            state.error.value = `No cached data found for @${state.currentUsername.value} with ${state.mediaType.value} media from ${state.timelineType.value} timeline. Please fetch fresh data first.`;
            state.errorType.value = "general";
            setTimeout(() => {
              if (
                state.error.value &&
                state.error.value.includes("No cached data found for")
              ) {
                state.error.value = null;
              }
            }, 2000);
          }
        } catch (error) {
          console.error("Failed to load cached data:", error);
          state.isLoading.value = false;
          state.error.value = `Failed to load cached data: ${
            error.message || "Unknown error"
          }. Please try fresh fetch.`;
          state.errorType.value = "general";
          setTimeout(() => {
            if (
              state.error.value &&
              state.error.value.startsWith("Failed to load cached data:")
            ) {
              state.error.value = null;
            }
          }, 2000);
        }
        return;
      }

      if (!state.patreonAuth.value) {
        state.error.value =
          "Please configure Patreon Auth token in the Auth tab";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (
            state.error.value ===
            "Please configure Patreon Auth token in the Auth tab"
          ) {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      if (!state.authToken.value) {
        state.error.value = "Please configure Auth Token in the Auth tab";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (
            state.error.value === "Please configure Auth Token in the Auth tab"
          ) {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      if (!state.currentUsername.value) {
        state.error.value =
          "No username detected. Please navigate to a user profile.";
        state.errorType.value = "username";
        setTimeout(() => {
          if (
            state.error.value ===
            "No username detected. Please navigate to a user profile."
          ) {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      state.isLoading.value = true;
      state.error.value = null;
      state.errorType.value = "general";

      const api =
        state.selectedApi.value === "backup"
          ? "https://backup.xbatch.online"
          : "https://api.xbatch.online";

      try {
        const url =
          state.patreonAuth.value === "xbatchdemo" &&
          state.currentUsername.value === "xbatchdemo"
            ? `${api}/demo/media/all/xbatchdemo/${state.authToken.value}/xbatchdemo`
            : `${api}/metadata/${state.timelineType.value}/${state.mediaType.value}/${state.currentUsername.value}/${state.authToken.value}/${state.patreonAuth.value}`;

        let timeoutId;
        const response = await new Promise((resolve, reject) => {
          timeoutId = setTimeout(() => {
            state.isLoading.value = false;
            reject(new Error("Request timeout - API took too long to respond"));
          }, 60000);

          try {
            GM_xmlhttpRequest({
              method: "GET",
              url: url,
              timeout: 60000,
              onload: (res) => {
                clearTimeout(timeoutId);
                if (res.status === 200) {
                  try {
                    const data = JSON.parse(res.responseText);
                    resolve(data);
                  } catch (parseError) {
                    state.isLoading.value = false;
                    reject(new Error("Invalid response format"));
                  }
                } else if (res.status === 401) {
                  state.isLoading.value = false;
                  reject(new Error("Invalid authentication tokens"));
                } else if (res.status === 403) {
                  state.isLoading.value = false;
                  reject(
                    new Error("Access forbidden - check your Patreon auth")
                  );
                } else if (res.status === 404) {
                  state.isLoading.value = false;
                  reject(new Error("User not found or no media available"));
                } else if (res.status === 429) {
                  state.isLoading.value = false;
                  reject(
                    new Error("Rate limit exceeded - please try again later")
                  );
                } else if (res.status >= 500) {
                  state.isLoading.value = false;
                  reject(new Error("Server error - please try backup API"));
                } else {
                  state.isLoading.value = false;
                  reject(
                    new Error(
                      `API error (${res.status}): ${
                        res.responseText || "Unknown error"
                      }`.substring(0, 200)
                    )
                  );
                }
              },
              onerror: () => {
                clearTimeout(timeoutId);
                state.isLoading.value = false;
                reject(
                  new Error("Network error - please check your connection")
                );
              },
              ontimeout: () => {
                clearTimeout(timeoutId);
                state.isLoading.value = false;
                reject(
                  new Error("Request timeout - API took too long to respond")
                );
              },
            });
          } catch (err) {
            clearTimeout(timeoutId);
            state.isLoading.value = false;
            reject(new Error("Failed to make request"));
          }
        });

        if (timeoutId) clearTimeout(timeoutId);

        if (!response || !response.account_info || !response.timeline) {
          state.isLoading.value = false;
          state.error.value =
            "Invalid response format from API. Please check your authentication.";
          state.errorType.value = "api";
          setTimeout(() => {
            if (
              state.error.value ===
              "Invalid response format from API. Please check your authentication."
            ) {
              state.error.value = null;
            }
          }, 2000);
          return;
        }

        if (response.timeline.length === 0) {
          state.isLoading.value = false;
          const mediaTypeText =
            state.mediaType.value === "gif"
              ? "GIFs"
              : state.mediaType.value === "image"
              ? "images"
              : state.mediaType.value === "video"
              ? "videos"
              : "media";
          state.error.value = `@${state.currentUsername.value} doesn't have any ${mediaTypeText}. No data was cached.`;
          state.errorType.value = "username";
          setTimeout(() => {
            if (
              state.error.value &&
              state.error.value.includes("doesn't have any")
            ) {
              state.error.value = null;
            }
          }, 3000);
          return;
        }

        state.mediaData.value = response;

        if (response.timeline.length > 0) {
          const normalizedUsername = state.currentUsername.value.toLowerCase();
          const cacheKey = `${normalizedUsername}_${state.timelineType.value}_${state.mediaType.value}`;
          await db.mediaData.put({
            cacheKey: cacheKey,
            username: normalizedUsername,
            timelineType: state.timelineType.value,
            mediaType: state.mediaType.value,
            data: response,
            timestamp: Date.now(),
          });
        }

        state.isLoading.value = false;
      } catch (error) {
        console.error(`Failed with ${api}:`, error);

        if (
          !isRetry &&
          (error.message.includes("Invalid authentication") ||
            error.message.includes("Invalid response format") ||
            error.message.includes("Access forbidden") ||
            (error.message.includes("API error") &&
              error.message.includes("401")))
        ) {
          console.log(
            "Auth token might be expired. Attempting to regenerate..."
          );
          const newToken = await generateNewAuthToken();

          if (newToken) {
            console.log("Retrying fetch with new auth token...");
            return fetchMediaData(true);
          } else {
            state.isLoading.value = false;
            state.error.value =
              "Authentication failed. Unable to generate new auth token. Please check your Patreon Auth.";
            state.errorType.value = "auth";
            setTimeout(() => {
              if (
                state.error.value ===
                "Authentication failed. Unable to generate new auth token. Please check your Patreon Auth."
              ) {
                state.error.value = null;
              }
            }, 2000);
            return;
          }
        }

        state.isLoading.value = false;

        if (error.message.includes("Invalid authentication")) {
          state.error.value =
            "Invalid authentication tokens. Please check your Auth Token and Patreon Auth in the Auth tab.";
          state.errorType.value = "auth";
        } else if (error.message.includes("Access forbidden")) {
          state.error.value =
            "Access forbidden. Your Patreon auth may be invalid or expired.";
          state.errorType.value = "auth";
        } else if (error.message.includes("User not found")) {
          state.error.value = `User @${state.currentUsername.value} not found or has no media.`;
          state.errorType.value = "username";
        } else if (error.message.includes("Rate limit")) {
          state.error.value =
            "Rate limit exceeded. Please wait a moment and try again.";
          state.errorType.value = "api";
        } else if (error.message.includes("Server error")) {
          state.error.value =
            "Server error. Please try using the Backup API service.";
          state.errorType.value = "api";
        } else if (error.message.includes("timeout")) {
          state.error.value =
            "Request timed out. The API is taking too long to respond. Please try again.";
          state.errorType.value = "api";
        } else if (error.message.includes("Network error")) {
          state.error.value =
            "Network error. Please check your internet connection.";
          state.errorType.value = "api";
        } else {
          state.error.value =
            error.message ||
            "Failed to fetch media data. Please check your settings and try again.";
          state.errorType.value = "api";
        }

        setTimeout(() => {
          if (state.error.value) {
            state.error.value = null;
          }
        }, 2000);
      }
    };

    const downloadMedia = async () => {
      if (!state.mediaData.value) return;
      if (state.isDownloading.value) return;

      state.isDownloading.value = true;
      state.downloadProgress.value = 0;
      state.downloadedFiles.value = 0;
      state.totalFileSize.value = 0;

      if (
        !state.mediaData.value?.timeline ||
        !Array.isArray(state.mediaData.value.timeline)
      ) {
        state.error.value =
          "Invalid media data structure. Please refetch the data.";
        state.errorType.value = "general";
        state.isDownloading.value = false;
        setTimeout(() => {
          if (
            state.error.value ===
            "Invalid media data structure. Please refetch the data."
          ) {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      const { timeline } = state.mediaData.value;
      const totalItems = timeline.length;
      const zipFiles = {};
      let successCount = 0;
      let failedCount = 0;
      let totalSize = 0;
      let processedCount = 0;

      const CONCURRENT_LIMIT = state.concurrentLimit.value || 20;
      const BATCH_DELAY = 500;

      console.log(`Starting parallel download of ${totalItems} media files...`);
      console.log(`Concurrent limit: ${CONCURRENT_LIMIT} files`);

      const tweetGroups = {};
      timeline.forEach((item, idx) => {
        if (!tweetGroups[item.tweet_id]) {
          tweetGroups[item.tweet_id] = [];
        }
        tweetGroups[item.tweet_id].push({ item, originalIndex: idx });
      });

      const indexToFileNumber = {};
      Object.values(tweetGroups).forEach((group) => {
        group.forEach((entry, fileIndex) => {
          indexToFileNumber[entry.originalIndex] =
            group.length > 1 ? fileIndex + 1 : null;
        });
      });

      const downloadFile = async (item, index) => {
        try {
          const date = dayjs(item.date).format("YYYY-MM-DD_HHmmss");
          const ext =
            item.type === "video"
              ? "mp4"
              : item.type === "animated_gif"
              ? "mp4"
              : "jpg";
          const fileNumber = indexToFileNumber[index];
          const actualUsername =
            state.mediaData.value?.account_info?.name ||
            state.currentUsername.value;
          const baseFilename =
            fileNumber !== null
              ? `${date}_${actualUsername}_${item.tweet_id}_${fileNumber}.${ext}`
              : `${date}_${actualUsername}_${item.tweet_id}.${ext}`;

          let filename = baseFilename;
          if (state.mediaType.value === "all") {
            let subfolder = "";
            if (item.type === "photo") {
              subfolder = "images/";
            } else if (item.type === "video") {
              subfolder = "videos/";
            } else if (item.type === "animated_gif") {
              subfolder = "gif/";
            }
            filename = subfolder + baseFilename;
          }

          console.log(
            `[${index + 1}/${totalItems}] Starting download: ${item.url}`
          );

          const response = await new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
              console.warn(`Timeout for file ${index + 1}`);
              reject(new Error("Download timeout"));
            }, 60000);

            GM_xmlhttpRequest({
              method: "GET",
              url: item.url,
              responseType: "arraybuffer",
              onload: (res) => {
                clearTimeout(timeout);
                if (res.status === 200 && res.response) {
                  resolve(res);
                } else {
                  reject(new Error(`HTTP ${res.status}`));
                }
              },
              onerror: (err) => {
                clearTimeout(timeout);
                reject(err);
              },
              ontimeout: () => {
                clearTimeout(timeout);
                reject(new Error("Request timeout"));
              },
            });
          });

          if (!response.response || response.response.byteLength === 0) {
            throw new Error("Empty response");
          }

          const fileData = new Uint8Array(response.response);

          zipFiles[filename] = fileData;

          successCount++;
          totalSize += fileData.length;

          console.log(
            `✓ [${index + 1}/${totalItems}] Downloaded: ${filename} (${(
              fileData.length / 1024
            ).toFixed(2)} KB)`
          );

          return { success: true, size: fileData.length };
        } catch (error) {
          failedCount++;
          console.error(
            `✗ [${index + 1}/${totalItems}] Failed:`,
            item.url,
            error.message
          );
          return { success: false, error: error.message };
        } finally {
          processedCount++;
          state.downloadedFiles.value = successCount;
          state.totalFileSize.value = totalSize;
          state.downloadProgress.value = Math.round(
            (processedCount / totalItems) * 100
          );
        }
      };

      const processBatch = async (batch) => {
        const promises = batch.map(({ item, index }) =>
          downloadFile(item, index)
        );
        const results = await Promise.allSettled(promises);

        const batchSuccess = results.filter(
          (r) => r.status === "fulfilled" && r.value?.success
        ).length;
        const batchFailed = results.filter(
          (r) =>
            r.status === "rejected" ||
            (r.status === "fulfilled" && !r.value?.success)
        ).length;
        console.log(
          `Batch complete: ${batchSuccess} success, ${batchFailed} failed`
        );

        return results;
      };

      const batches = [];
      for (let i = 0; i < totalItems; i += CONCURRENT_LIMIT) {
        const batch = timeline
          .slice(i, Math.min(i + CONCURRENT_LIMIT, totalItems))
          .map((item, batchIndex) => ({ item, index: i + batchIndex }));
        batches.push(batch);
      }

      console.log(`Processing ${batches.length} batches...`);

      for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
        console.log(
          `\nProcessing batch ${batchIndex + 1}/${batches.length}...`
        );
        await processBatch(batches[batchIndex]);

        if (batchIndex < batches.length - 1) {
          console.log(`Waiting ${BATCH_DELAY}ms before next batch...`);
          await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY));
        }

        console.log(
          `Overall progress: ${processedCount}/${totalItems} files, ${(
            totalSize /
            (1024 * 1024)
          ).toFixed(2)} MB`
        );
      }

      console.log(`\n=== Download Summary ===`);
      console.log(`Total: ${totalItems} files`);
      console.log(`Success: ${successCount} files`);
      console.log(`Failed: ${failedCount} files`);
      console.log(`Total size: ${(totalSize / (1024 * 1024)).toFixed(2)} MB`);
      console.log(`Files in ZIP object: ${Object.keys(zipFiles).length}`);

      if (successCount > 0) {
        const SAFETY_CONFIG = {
          maxSizePerZip: 500 * 1024 * 1024,
          maxFilesPerZip: 500,
          warnThreshold: 300 * 1024 * 1024,
        };

        const needsSplit =
          totalSize > SAFETY_CONFIG.maxSizePerZip ||
          Object.keys(zipFiles).length > SAFETY_CONFIG.maxFilesPerZip;

        if (totalSize > SAFETY_CONFIG.warnThreshold) {
          console.warn(
            `⚠️ Large download detected: ${(totalSize / (1024 * 1024)).toFixed(
              2
            )} MB`
          );
        }

        try {
          if (needsSplit) {
            console.log(
              "📦 File size/count exceeds safe limits. Creating multiple ZIP files..."
            );

            const chunks = [];
            let currentChunk = {};
            let currentSize = 0;
            let currentCount = 0;

            for (const [filename, data] of Object.entries(zipFiles)) {
              if (
                (currentSize + data.length > SAFETY_CONFIG.maxSizePerZip ||
                  currentCount >= SAFETY_CONFIG.maxFilesPerZip) &&
                currentCount > 0
              ) {
                chunks.push({
                  files: currentChunk,
                  size: currentSize,
                  count: currentCount,
                });
                currentChunk = {};
                currentSize = 0;
                currentCount = 0;
              }

              currentChunk[filename] = data;
              currentSize += data.length;
              currentCount++;
            }

            if (currentCount > 0) {
              chunks.push({
                files: currentChunk,
                size: currentSize,
                count: currentCount,
              });
            }

            console.log(`Creating ${chunks.length} ZIP files...`);

            for (let i = 0; i < chunks.length; i++) {
              const chunk = chunks[i];
              const partNumber = i + 1;
              const totalParts = chunks.length;

              console.log(
                `Creating ZIP part ${partNumber}/${totalParts} (${
                  chunk.count
                } files, ${(chunk.size / (1024 * 1024)).toFixed(2)} MB)...`
              );

              const compressed = await new Promise((resolve, reject) => {
                fflate.zip(chunk.files, { level: 1 }, (err, data) => {
                  if (err) reject(err);
                  else resolve(data);
                });
              });

              const blob = new Blob([compressed], { type: "application/zip" });
              const actualUsername =
                state.mediaData.value?.account_info?.name ||
                state.currentUsername.value;
              const zipFilename =
                totalParts > 1
                  ? `${actualUsername}_${dayjs().format(
                      "YYYY-MM-DD_HHmmss"
                    )}_part${partNumber}of${totalParts}.zip`
                  : `${actualUsername}_${dayjs().format(
                      "YYYY-MM-DD_HHmmss"
                    )}.zip`;

              console.log(
                `ZIP part ${partNumber} created: ${(
                  blob.size /
                  (1024 * 1024)
                ).toFixed(2)} MB`
              );

              if (i > 0) {
                await new Promise((resolve) => setTimeout(resolve, 500));
              }

              saveAs(blob, zipFilename);
              console.log(`✓ ZIP file saved: ${zipFilename}`);
            }

            console.log(
              `✅ All ${chunks.length} ZIP files created successfully!`
            );

            if (failedCount > 0) {
              state.error.value = `Downloaded ${successCount.toLocaleString()}/${totalItems.toLocaleString()} files into ${
                chunks.length
              } ZIP files. ${failedCount.toLocaleString()} files failed.`;
              state.errorType.value = "failed";
              setTimeout(() => {
                if (
                  state.error.value &&
                  state.error.value.includes("files failed")
                ) {
                  state.error.value = null;
                }
              }, 2000);
            } else {
              state.success.value = `Successfully downloaded ${successCount.toLocaleString()} files into ${
                chunks.length
              } ZIP files.`;
              state.error.value = null;
              setTimeout(() => {
                if (
                  state.success.value &&
                  state.success.value.includes("Successfully downloaded")
                ) {
                  state.success.value = null;
                }
              }, 2000);
            }
          } else {
            console.log("Creating single ZIP file...");

            const fileList = Object.keys(zipFiles);
            console.log(`Zipping ${fileList.length} files...`);

            const compressed = await new Promise((resolve, reject) => {
              fflate.zip(zipFiles, { level: 1 }, (err, data) => {
                if (err) reject(err);
                else resolve(data);
              });
            });

            const blob = new Blob([compressed], { type: "application/zip" });
            const actualUsername =
              state.mediaData.value?.account_info?.name ||
              state.currentUsername.value;
            const zipFilename = `${actualUsername}_${dayjs().format(
              "YYYY-MM-DD_HHmmss"
            )}.zip`;

            console.log(
              `ZIP created: ${(blob.size / (1024 * 1024)).toFixed(2)} MB`
            );

            if (blob.size > 2 * 1024 * 1024 * 1024) {
              console.error("⚠️ ZIP file exceeds 2GB browser limit!");
              state.error.value =
                "ZIP file is too large for browser. Please try downloading fewer files.";
              state.errorType.value = "general";
              setTimeout(() => {
                if (
                  state.error.value ===
                  "ZIP file is too large for browser. Please try downloading fewer files."
                ) {
                  state.error.value = null;
                }
              }, 2000);
              return;
            }

            saveAs(blob, zipFilename);
            console.log(`✓ ZIP file saved: ${zipFilename}`);

            if (failedCount > 0) {
              state.error.value = `Downloaded ${successCount.toLocaleString()}/${totalItems.toLocaleString()} files. ${failedCount.toLocaleString()} files failed.`;
              state.errorType.value = "failed";
              setTimeout(() => {
                if (
                  state.error.value &&
                  state.error.value.includes("files failed")
                ) {
                  state.error.value = null;
                }
              }, 2000);
            } else {
              state.success.value = `Successfully downloaded ${successCount.toLocaleString()} files.`;
              state.error.value = null;
              setTimeout(() => {
                if (
                  state.success.value &&
                  state.success.value.includes("Successfully downloaded")
                ) {
                  state.success.value = null;
                }
              }, 2000);
            }
          }
        } catch (error) {
          console.error("Failed to create ZIP file:", error);

          if (error.message?.includes("memory")) {
            state.error.value =
              "Out of memory. Try downloading fewer files or use a device with more RAM.";
            state.errorType.value = "general";
          } else if (error.message?.includes("quota")) {
            state.error.value =
              "Storage quota exceeded. Please free up some space and try again.";
            state.errorType.value = "general";
          } else {
            state.error.value = `Failed to create ZIP file: ${
              error.message || "Unknown error"
            }`;
            state.errorType.value = "general";
          }
          setTimeout(() => {
            if (state.error.value) {
              state.error.value = null;
            }
          }, 2000);
        }
      } else {
        state.error.value =
          "No files were successfully downloaded. Please check your connection and try again.";
        state.errorType.value = "general";
        setTimeout(() => {
          if (
            state.error.value ===
            "No files were successfully downloaded. Please check your connection and try again."
          ) {
            state.error.value = null;
          }
        }, 2000);
      }

      state.isDownloading.value = false;
      state.downloadProgress.value = 0;
      state.downloadedFiles.value = 0;
      state.totalFileSize.value = 0;
    };

    return h(
      "div",
      null,
      state.mediaData.value &&
        h(
          "div",
          {
            style:
              "display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;",
          },
          h(
            "div",
            { style: "display: flex; gap: 8px;" },
            h(
              "button",
              {
                className: "tmd-button tmd-button-outline",
                style: "padding: 6px 12px;",
                onClick: () => {
                  state.mediaData.value = null;
                  state.error.value = null;
                  state.success.value = null;
                  state.loadedFromDatabase.value = false;
                  state.loadedDatabaseConfig.value = null;
                },
              },
              h("span", { dangerouslySetInnerHTML: { __html: ICONS.undo } }),
              "Back"
            ),

            state.loadedFromDatabase.value &&
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline",
                  style: "padding: 6px 12px;",
                  onClick: updateDatabase,
                  disabled:
                    state.isLoading.value ||
                    !state.authToken.value ||
                    !state.patreonAuth.value ||
                    !state.isVerified.value,
                  title: "Update database with fresh data from server",
                },
                state.isLoading.value
                  ? h("span", {
                      className: "tmd-spinner",
                      dangerouslySetInnerHTML: { __html: ICONS.spinner },
                    })
                  : h("span", {
                      dangerouslySetInnerHTML: { __html: ICONS.cloudCheck },
                    }),
                state.isLoading.value ? "Updating..." : "Update"
              )
          ),

          state.fetchType.value !== "single" &&
            h(
              "div",
              {
                style: "display: flex; gap: 8px; align-items: center;",
              },
              state.fetchType.value === "autoBatch"
                ? !state.isAutoBatch.value
                  ? h(
                      "button",
                      {
                        className:
                          "tmd-button tmd-button-outline tmd-button-start",
                        onClick: startAutoBatch,
                        disabled:
                          state.isLoading.value ||
                          (state.mediaData.value?.metadata &&
                            !state.mediaData.value.metadata.has_more),
                        style: "padding: 6px 12px;",
                      },
                      h("span", {
                        dangerouslySetInnerHTML: { __html: ICONS.play },
                      }),
                      "Start"
                    )
                  : h(
                      "button",
                      {
                        className:
                          "tmd-button tmd-button-outline tmd-button-stop",
                        onClick: stopAutoBatch,
                        style: "padding: 6px 12px;",
                      },
                      h("span", {
                        dangerouslySetInnerHTML: { __html: ICONS.stop },
                      }),
                      "Stop"
                    )
                : [
                    h("button", {
                      className:
                        "tmd-button tmd-button-outline tmd-button-square",
                      onClick: handlePreviousBatch,
                      disabled:
                        state.currentBatchPage.value <=
                          state.startingBatch.value ||
                        state.isLoading.value ||
                        !state.isVerified.value,
                      title: "Previous batch",
                      dangerouslySetInnerHTML: {
                        __html:
                          state.loadingDirection.value === "prev"
                            ? ICONS.spinner.replace(
                                'class="',
                                'class="tmd-spinner '
                              )
                            : ICONS.chevronLeft,
                      },
                    }),
                    h("button", {
                      className:
                        "tmd-button tmd-button-outline tmd-button-square",
                      onClick: handleNextBatch,
                      disabled:
                        state.isLoading.value ||
                        (state.mediaData.value?.metadata &&
                          !state.mediaData.value.metadata.has_more) ||
                        !state.isVerified.value,
                      title: "Next batch",
                      dangerouslySetInnerHTML: {
                        __html:
                          state.loadingDirection.value === "next"
                            ? ICONS.spinner.replace(
                                'class="',
                                'class="tmd-spinner '
                              )
                            : ICONS.chevronRight,
                      },
                    }),
                  ]
            )
        ),

      !state.mediaData.value &&
        h(
          "div",
          {
            className: "tmd-service-data-row",
            style:
              "display: flex; gap: 20px; margin-bottom: 20px; flex-wrap: wrap;",
          },
          h(
            "div",
            { style: "flex: 1; min-width: 280px;" },
            h(
              "label",
              { className: "tmd-label" },
              h("span", { dangerouslySetInnerHTML: { __html: ICONS.send } }),
              "Fetch Type"
            ),
            h(
              "div",
              {
                className: "tmd-radio-group",
                style: "white-space: nowrap; flex-wrap: nowrap;",
              },
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.fetchType.value = "single";
                    state.batchedMediaData.value = [];
                    state.currentBatchPage.value = state.startingBatch.value;
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.fetchType.value === "single" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Single")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.fetchType.value = "batch";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.fetchType.value === "batch" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Batch")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.fetchType.value = "autoBatch";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.fetchType.value === "autoBatch" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Auto Batch")
              )
            )
          ),

          h(
            "div",
            { style: "flex: 1; min-width: 200px;" },
            h(
              "label",
              { className: "tmd-label" },
              h("span", {
                dangerouslySetInnerHTML: { __html: ICONS.database },
              }),
              "Data Source"
            ),
            h(
              "div",
              { className: "tmd-radio-group" },
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => (state.fetchMode.value = "fresh"),
                },
                h("div", {
                  className: `tmd-radio ${
                    state.fetchMode.value === "fresh" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Fresh")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => (state.fetchMode.value = "cache"),
                },
                h("div", {
                  className: `tmd-radio ${
                    state.fetchMode.value === "cache" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Cache")
              )
            )
          ),

          h(
            "div",
            { style: "flex: 1; min-width: 200px;" },
            h(
              "label",
              { className: "tmd-label" },
              h("span", { dangerouslySetInnerHTML: { __html: ICONS.images } }),
              "Media Type"
            ),
            h(
              "div",
              { className: "tmd-radio-group" },
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.mediaType.value = "all";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.mediaType.value === "all" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "All")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.mediaType.value = "image";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.mediaType.value === "image" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Image")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.mediaType.value = "video";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.mediaType.value === "video" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "Video")
              ),
              h(
                "div",
                {
                  className: "tmd-radio-item",
                  onClick: () => {
                    state.mediaType.value = "gif";
                    saveSettings();
                  },
                },
                h("div", {
                  className: `tmd-radio ${
                    state.mediaType.value === "gif" ? "checked" : ""
                  }`,
                }),
                h("span", { className: "tmd-radio-label" }, "GIF")
              )
            )
          )
        ),

      !state.isVerified.value &&
        state.fetchMode.value === "fresh" &&
        !state.mediaData.value &&
        h(
          "div",
          {
            className: "tmd-error auth",
          },
          h("span", {
            className: "tmd-error-icon",
            dangerouslySetInnerHTML: { __html: ICONS.triangleAlert },
          }),
          h(
            "span",
            null,
            "Please verify your Patreon Auth in the Auth tab to unlock fetch and convert features"
          )
        ),

      !state.mediaData.value &&
        h(
          "div",
          {
            className: "tmd-button-container",
            style: "padding-top: 10px; gap: 10px;",
          },
          h(
            "button",
            {
              className: "tmd-button tmd-button-primary",
              onClick: () => {
                if (state.fetchType.value === "single") {
                  fetchMediaData();
                } else if (state.fetchType.value === "batch") {
                  fetchBatchMediaData(state.startingBatch.value || 0);
                } else if (state.fetchType.value === "autoBatch") {
                  fetchBatchMediaData(state.startingBatch.value || 0).then(
                    () => {
                      if (state.mediaData.value?.metadata?.has_more) {
                        startAutoBatch();
                      }
                    }
                  );
                }
              },
              disabled:
                state.isLoading.value ||
                !state.currentUsername.value ||
                !state.isVerified.value,
              style: "font-size: 16px; padding: 12px 24px;",
            },
            state.isLoading.value
              ? h("span", {
                  className: "tmd-spinner",
                  dangerouslySetInnerHTML: {
                    __html: ICONS.spinner
                      .replace('width="16"', 'width="20"')
                      .replace('height="16"', 'height="20"'),
                  },
                })
              : !state.isVerified.value
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.patreonAuthIcon
                      .replace('width="16"', 'width="20"')
                      .replace('height="16"', 'height="20"'),
                  },
                })
              : h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.cloudDownload
                      .replace('width="16"', 'width="20"')
                      .replace('height="16"', 'height="20"'),
                  },
                }),
            state.isLoading.value ? "Fetching..." : "Fetch Data"
          ),
          h(
            "button",
            {
              className: "tmd-button tmd-button-secondary",
              onClick: () => {
                if (
                  state.patreonAuth.value &&
                  state.authToken.value &&
                  state.currentUsername.value
                ) {
                  const url = `https://convert.xbatch.online/${state.patreonAuth.value}/${state.authToken.value}/${state.currentUsername.value}`;
                  window.open(url, "_blank");
                } else {
                  state.error.value =
                    "Please configure authentication tokens and ensure username is detected";
                  state.errorType.value = "auth";
                  setTimeout(() => {
                    if (
                      state.error.value ===
                      "Please configure authentication tokens and ensure username is detected"
                    ) {
                      state.error.value = null;
                    }
                  }, 2000);
                }
              },
              disabled: !state.currentUsername.value || !state.isVerified.value,
              style: "font-size: 16px; padding: 12px 24px;",
            },
            !state.isVerified.value
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.patreonAuthIcon
                      .replace('width="16"', 'width="20"')
                      .replace('height="16"', 'height="20"'),
                  },
                })
              : h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.animatedGif
                      .replace('width="16"', 'width="20"')
                      .replace('height="16"', 'height="20"')
                      .replace('stroke="currentColor"', 'stroke="white"'),
                  },
                }),
            "Convert to GIF"
          )
        ),

      state.mediaData.value &&
        h(
          "div",
          null,
          h(
            "div",
            { className: "tmd-info-card" },
            h(
              "div",
              { className: "tmd-info-row" },
              h("span", { className: "tmd-info-label" }, "Username:"),
              h(
                "span",
                null,
                state.mediaData.value?.account_info?.name || "N/A"
              )
            ),
            h(
              "div",
              { className: "tmd-info-row" },
              h("span", { className: "tmd-info-label" }, "Display Name:"),
              h(
                "span",
                null,
                state.mediaData.value?.account_info?.nick || "N/A"
              )
            ),
            h(
              "div",
              { className: "tmd-info-row" },
              h("span", { className: "tmd-info-label" }, "Joined:"),
              h(
                "span",
                null,
                state.mediaData.value?.account_info?.date
                  ? dayjs(state.mediaData.value.account_info.date).format(
                      "DD MMM YYYY - HH:mm:ss"
                    )
                  : "N/A"
              )
            ),
            h(
              "div",
              { className: "tmd-info-row" },
              h("span", { className: "tmd-info-label" }, "Total Media:"),
              h(
                "span",
                null,
                (() => {
                  if (state.fetchType.value === "single") {
                    return (
                      state.mediaData.value?.timeline?.length?.toLocaleString() ||
                      "0"
                    );
                  } else {
                    return (
                      state.batchedMediaData.value?.length?.toLocaleString() ||
                      "0"
                    );
                  }
                })()
              )
            ),

            state.fetchType.value !== "single" && [
              h("hr", {
                style:
                  "margin: 12px 0; border: none; border-top: 1px solid; opacity: 0.2;",
              }),
              h(
                "div",
                { className: "tmd-info-row" },
                h("span", { className: "tmd-info-label" }, "Batch:"),
                h("span", null, `${state.currentBatchPage.value + 1}`)
              ),
              h(
                "div",
                { className: "tmd-info-row" },
                h("span", { className: "tmd-info-label" }, "Current Batch:"),
                h(
                  "span",
                  null,
                  (() => {
                    const currentBatchLength =
                      state.currentBatchData.value?.length || 0;
                    return currentBatchLength.toLocaleString();
                  })()
                )
              ),
            ]
          ),

          state.isDownloading.value &&
            h(
              "div",
              null,
              h(
                "div",
                {
                  style:
                    "display: flex; align-items: center; gap: 8px; margin-bottom: 8px;",
                },
                h(
                  "div",
                  { className: "tmd-progress-bar", style: "flex: 1;" },
                  h("div", {
                    className: "tmd-progress-fill",
                    style: `width: ${state.downloadProgress.value}%`,
                  })
                ),
                h(
                  "span",
                  {
                    style:
                      "font-weight: 500; min-width: 45px; text-align: right;",
                  },
                  `${Math.round(state.downloadProgress.value)}%`
                )
              ),
              h(
                "div",
                { className: "tmd-progress-info" },
                h(
                  "span",
                  null,
                  `Files: ${currentFiles.toLocaleString()}/${state.mediaData.value.timeline.length.toLocaleString()}`
                ),
                h(
                  "span",
                  null,
                  `Size: ${(currentSize / (1024 * 1024)).toFixed(2)} MB`
                )
              )
            ),

          h(
            "div",
            {
              className: "tmd-button-container",
              style: "gap: 8px; justify-content: center;",
            },
            (state.fetchType.value === "batch" ||
              state.fetchType.value === "autoBatch") &&
              h(
                "button",
                {
                  className:
                    "tmd-button tmd-button-outline tmd-download-current-button",
                  onClick: downloadCurrentBatch,
                  disabled:
                    state.isDownloadingCurrent.value ||
                    state.isDownloading.value ||
                    !state.mediaData.value ||
                    !state.currentBatchData.value ||
                    !state.isVerified.value,
                  style: "padding: 10px 20px;",
                },
                state.isDownloadingCurrent.value
                  ? h("span", {
                      className: "tmd-spinner",
                      dangerouslySetInnerHTML: {
                        __html: ICONS.spinner
                          .replace('width="16"', 'width="20"')
                          .replace('height="16"', 'height="20"'),
                      },
                    })
                  : h("span", {
                      dangerouslySetInnerHTML: {
                        __html: ICONS.download
                          .replace('width="16"', 'width="20"')
                          .replace('height="16"', 'height="20"'),
                      },
                    }),
                state.isDownloadingCurrent.value
                  ? "Downloading..."
                  : "Download Current"
              ),
            h(
              "button",
              {
                className: "tmd-button tmd-button-secondary",
                onClick: downloadMedia,
                disabled:
                  state.isDownloading.value || state.isDownloadingCurrent.value,
              },
              state.isDownloading.value && !state.isDownloadingCurrent.value
                ? h("span", {
                    className: "tmd-spinner",
                    dangerouslySetInnerHTML: {
                      __html: ICONS.spinner
                        .replace('width="16"', 'width="20"')
                        .replace('height="16"', 'height="20"'),
                    },
                  })
                : h("span", {
                    dangerouslySetInnerHTML: {
                      __html: ICONS.download
                        .replace('width="16"', 'width="20"')
                        .replace('height="16"', 'height="20"'),
                    },
                  }),
              state.isDownloading.value && !state.isDownloadingCurrent.value
                ? "Downloading..."
                : "Download All"
            )
          )
        )
    );
  }

  function AlertDialog({
    title,
    message,
    onConfirm,
    onCancel,
    confirmLabel = "Delete",
  }) {
    return h(
      "div",
      { className: "tmd-alert-overlay", onClick: onCancel },
      h(
        "div",
        {
          className: `tmd-alert ${state.theme.value}`,
          onClick: (e) => e.stopPropagation(),
        },
        h("div", { className: "tmd-alert-title" }, title),
        h("div", {
          className: "tmd-alert-message",
          dangerouslySetInnerHTML: { __html: message },
        }),
        h(
          "div",
          { className: "tmd-alert-buttons" },
          h(
            "button",
            {
              className: "tmd-alert-button tmd-alert-button-cancel",
              onClick: onCancel,
            },
            "Cancel"
          ),
          h(
            "button",
            {
              className: "tmd-alert-button tmd-alert-button-confirm",
              onClick: onConfirm,
            },
            confirmLabel
          )
        )
      )
    );
  }

  function PreviewModal() {
    const [touchStart, setTouchStart] = useState(null);
    const [touchEnd, setTouchEnd] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    const minSwipeDistance = 50;

    const handleTouchStart = (e) => {
      setTouchEnd(null);
      setTouchStart(e.targetTouches[0].clientX);
    };

    const handleTouchMove = (e) => {
      setTouchEnd(e.targetTouches[0].clientX);
      if (Math.abs(e.targetTouches[0].clientX - touchStart) > 10) {
        e.preventDefault();
      }
    };

    const handleTouchEnd = () => {
      if (!touchStart || !touchEnd) return;
      const distance = touchStart - touchEnd;
      const isLeftSwipe = distance > minSwipeDistance;
      const isRightSwipe = distance < -minSwipeDistance;

      const filteredMedia = getFilteredMedia();
      const currentIndex = state.previewCurrentIndex.value;

      if (isLeftSwipe && currentIndex < filteredMedia.length - 1) {
        nextMedia();
      } else if (isRightSwipe && currentIndex > 0) {
        prevMedia();
      }
    };

    const nextMedia = () => {
      const filteredMedia = getFilteredMedia();
      if (!filteredMedia.length) return;

      const currentIndex = state.previewCurrentIndex.value;
      if (currentIndex < filteredMedia.length - 1) {
        setIsLoading(true);
        state.previewCurrentIndex.value = currentIndex + 1;
      }
    };

    const prevMedia = () => {
      const currentIndex = state.previewCurrentIndex.value;
      if (currentIndex > 0) {
        setIsLoading(true);
        state.previewCurrentIndex.value = currentIndex - 1;
      }
    };

    const closeModal = () => {
      state.previewModalOpen.value = false;
      state.previewMediaData.value = null;
      state.previewCurrentIndex.value = 0;
      state.previewFilters.value = {
        photo: false,
        video: false,
        animatedGif: false,
      };
    };

    const getMediaType = (media) => {
      if (media.type === "animated_gif") {
        return "animatedGif";
      } else if (media.type === "video") {
        return "video";
      } else if (media.type === "photo") {
        return "photo";
      }

      const url = media.url;
      if (
        url.includes(".mp4") ||
        url.includes(".webm") ||
        url.includes(".mov")
      ) {
        return "video";
      } else if (url.includes(".gif")) {
        return "animatedGif";
      } else {
        return "photo";
      }
    };

    const toggleFilter = (filterType) => {
      const currentFilters = state.previewFilters.value;
      state.previewFilters.value = {
        ...currentFilters,
        [filterType]: !currentFilters[filterType],
      };
    };

    const getFilteredMedia = () => {
      const mediaData = state.previewMediaData.value;
      if (!mediaData || !mediaData.timeline) return [];

      const filters = state.previewFilters.value;
      const hasActiveFilters =
        filters.photo || filters.video || filters.animatedGif;

      if (!hasActiveFilters) {
        return mediaData.timeline;
      }

      return mediaData.timeline.filter((media) => {
        const mediaType = getMediaType(media);
        return filters[mediaType];
      });
    };

    const getMediaCounts = () => {
      const mediaData = state.previewMediaData.value;
      if (!mediaData || !mediaData.timeline)
        return { photo: 0, video: 0, animatedGif: 0 };

      const counts = { photo: 0, video: 0, animatedGif: 0 };
      mediaData.timeline.forEach((media) => {
        const type = getMediaType(media);
        counts[type]++;
      });
      return counts;
    };

    const handleKeyDown = (e) => {
      if (e.key === "Escape") {
        closeModal();
      } else if (e.key === "ArrowLeft") {
        prevMedia();
      } else if (e.key === "ArrowRight") {
        nextMedia();
      }
    };

    useEffect(() => {
      if (state.previewModalOpen.value) {
        document.addEventListener("keydown", handleKeyDown);
        document.body.style.overflow = "hidden";
        return () => {
          document.removeEventListener("keydown", handleKeyDown);
          document.body.style.overflow = "";
        };
      }
    }, [state.previewModalOpen.value]);

    useEffect(() => {
      if (state.previewModalOpen.value) {
        setIsLoading(true);

        const timeout = setTimeout(() => {
          setIsLoading(false);
        }, 5000);

        return () => clearTimeout(timeout);
      }
    }, [state.previewCurrentIndex.value]);

    useEffect(() => {
      if (state.previewModalOpen.value) {
        state.previewCurrentIndex.value = 0;
        setIsLoading(true);

        const timeout = setTimeout(() => {
          setIsLoading(false);
        }, 5000);

        return () => clearTimeout(timeout);
      }
    }, [state.previewFilters.value]);

    useEffect(() => {
      if (state.previewModalOpen.value && isLoading) {
        const checkMediaLoaded = () => {
          const mediaElement = document.querySelector(".tmd-preview-media");
          if (mediaElement) {
            if (mediaElement.tagName === "IMG") {
              if (mediaElement.complete && mediaElement.naturalHeight !== 0) {
                setIsLoading(false);
              }
            } else if (mediaElement.tagName === "VIDEO") {
              if (mediaElement.readyState >= 3) {
                setIsLoading(false);
              }
            }
          }
        };

        checkMediaLoaded();

        const checkTimeout = setTimeout(checkMediaLoaded, 100);

        return () => clearTimeout(checkTimeout);
      }
    }, [state.previewCurrentIndex.value, isLoading]);

    if (!state.previewModalOpen.value || !state.previewMediaData.value) {
      return null;
    }

    const mediaData = state.previewMediaData.value;
    const filteredMedia = getFilteredMedia();
    const currentIndex = state.previewCurrentIndex.value;
    const currentMedia = filteredMedia[currentIndex];
    const accountInfo = mediaData.account_info;

    if (!currentMedia) {
      if (
        filteredMedia.length === 0 &&
        (state.previewFilters.value.photo ||
          state.previewFilters.value.video ||
          state.previewFilters.value.animatedGif)
      ) {
        return h(
          "div",
          {
            className: "tmd-preview-modal",
            onClick: (e) => {
              if (e.target === e.currentTarget) {
                closeModal();
              }
            },
          },
          h(
            "div",
            { className: "tmd-preview-header" },
            h(
              "div",
              { className: "tmd-preview-account-info" },
              accountInfo.profile_image &&
                h("img", {
                  src: accountInfo.profile_image,
                  className: "tmd-preview-profile-img",
                  alt: "Profile",
                }),
              h(
                "div",
                { className: "tmd-preview-account-details" },
                h("h3", null, accountInfo.nick || accountInfo.name),
                h("p", null, `@${accountInfo.name}`)
              )
            ),
            h("button", {
              className: "tmd-preview-close",
              onClick: closeModal,
              dangerouslySetInnerHTML: { __html: ICONS.close },
            })
          ),
          h(
            "div",
            { className: "tmd-preview-filter-bar" },
            (() => {
              const counts = getMediaCounts();
              return [
                h(
                  "button",
                  {
                    className: `tmd-preview-filter-btn ${
                      state.previewFilters.value.photo ? "active" : ""
                    }`,
                    onClick: () => toggleFilter("photo"),
                    title: "Show only photos",
                  },
                  h("span", {
                    dangerouslySetInnerHTML: { __html: ICONS.photo },
                  }),
                  counts.photo
                ),
                h(
                  "button",
                  {
                    className: `tmd-preview-filter-btn ${
                      state.previewFilters.value.video ? "active" : ""
                    }`,
                    onClick: () => toggleFilter("video"),
                    title: "Show only videos",
                  },
                  h("span", {
                    dangerouslySetInnerHTML: { __html: ICONS.video },
                  }),
                  counts.video
                ),
                h(
                  "button",
                  {
                    className: `tmd-preview-filter-btn ${
                      state.previewFilters.value.animatedGif ? "active" : ""
                    }`,
                    onClick: () => toggleFilter("animatedGif"),
                    title: "Show only GIFs",
                  },
                  h("span", {
                    dangerouslySetInnerHTML: { __html: ICONS.animatedGif },
                  }),
                  counts.animatedGif
                ),
              ];
            })()
          ),
          h(
            "div",
            {
              style: `
              flex: 1;
              display: flex;
              align-items: center;
              justify-content: center;
              color: white;
              text-align: center;
              padding: 40px;
            `,
            },
            h(
              "div",
              null,
              h("div", {
                style:
                  "font-size: 48px; margin-bottom: 16px; opacity: 0.5; display: flex; justify-content: center; align-items: center;",
                dangerouslySetInnerHTML: { __html: ICONS.frown },
              }),
              h(
                "h3",
                { style: "margin: 0 0 8px 0; font-size: 18px;" },
                "No media found"
              ),
              h(
                "p",
                { style: "margin: 0; opacity: 0.7; font-size: 14px;" },
                "Try adjusting your filters or clear them to see all media."
              )
            )
          )
        );
      }
      return null;
    }

    const formatNumber = (num) => {
      if (num >= 1000000) return (num / 1000000).toFixed(1) + "M";
      if (num >= 1000) return (num / 1000).toFixed(1) + "K";
      return num.toString();
    };

    return h(
      "div",
      {
        className: "tmd-preview-modal",
        onTouchStart: handleTouchStart,
        onTouchMove: handleTouchMove,
        onTouchEnd: handleTouchEnd,
        onClick: (e) => {
          if (e.target === e.currentTarget) {
            closeModal();
          }
        },
      },
      h(
        "div",
        { className: "tmd-preview-header" },
        h(
          "div",
          { className: "tmd-preview-account-info" },
          accountInfo.profile_image &&
            h("img", {
              src: accountInfo.profile_image,
              className: "tmd-preview-profile-img",
              alt: "Profile",
            }),
          h(
            "div",
            { className: "tmd-preview-account-details" },
            h("h3", null, accountInfo.nick || accountInfo.name),
            h("p", null, `@${accountInfo.name}`),
            h(
              "div",
              { className: "tmd-preview-stats" },
              h("span", null, [
                h(
                  "strong",
                  null,
                  formatNumber(accountInfo.followers_count || 0)
                ),
                " Followers",
              ]),
              h("span", null, [
                h("strong", null, formatNumber(accountInfo.friends_count || 0)),
                " Following",
              ]),
              h("span", null, [
                h("strong", null, filteredMedia.length),
                filteredMedia.length !== mediaData.timeline.length
                  ? ` / ${mediaData.timeline.length} Media`
                  : " Media",
              ])
            )
          )
        ),
        h("button", {
          className: "tmd-preview-close",
          onClick: closeModal,
          dangerouslySetInnerHTML: { __html: ICONS.close },
        })
      ),

      h(
        "div",
        { className: "tmd-preview-filter-bar" },
        (() => {
          const counts = getMediaCounts();
          return [
            h(
              "button",
              {
                className: `tmd-preview-filter-btn ${
                  state.previewFilters.value.photo ? "active" : ""
                }`,
                onClick: () => toggleFilter("photo"),
                title: "Show only photos",
              },
              h("span", { dangerouslySetInnerHTML: { __html: ICONS.photo } }),
              counts.photo
            ),
            h(
              "button",
              {
                className: `tmd-preview-filter-btn ${
                  state.previewFilters.value.video ? "active" : ""
                }`,
                onClick: () => toggleFilter("video"),
                title: "Show only videos",
              },
              h("span", { dangerouslySetInnerHTML: { __html: ICONS.video } }),
              counts.video
            ),
            h(
              "button",
              {
                className: `tmd-preview-filter-btn ${
                  state.previewFilters.value.animatedGif ? "active" : ""
                }`,
                onClick: () => toggleFilter("animatedGif"),
                title: "Show only GIFs",
              },
              h("span", {
                dangerouslySetInnerHTML: { __html: ICONS.animatedGif },
              }),
              counts.animatedGif
            ),
          ];
        })()
      ),

      h(
        "div",
        { className: "tmd-preview-content" },
        h("button", {
          className: "tmd-preview-nav tmd-preview-nav-prev",
          onClick: prevMedia,
          disabled: currentIndex === 0,
          dangerouslySetInnerHTML: { __html: ICONS.chevronLeft },
        }),

        (() => {
          const mediaUrl = currentMedia.url;
          const isVideo =
            currentMedia.type === "video" ||
            mediaUrl.includes(".mp4") ||
            mediaUrl.includes(".webm") ||
            mediaUrl.includes(".mov");

          if (isVideo) {
            return h("video", {
              className: "tmd-preview-media",
              src: mediaUrl,
              controls: true,
              autoPlay: false,
              muted: true,
              onLoadedData: () => setIsLoading(false),
              onCanPlay: () => setIsLoading(false),
              onLoadStart: () => {
                const video = document.querySelector(".tmd-preview-media");
                if (video && video.readyState >= 3) {
                  setIsLoading(false);
                }
              },
              onError: (e) => {
                console.error("Video load error:", e);
                setIsLoading(false);
              },
              style: isLoading
                ? "opacity: 0;"
                : "opacity: 1; transition: opacity 0.3s;",
            });
          } else {
            return h("img", {
              className: "tmd-preview-media",
              src: mediaUrl,
              alt: "Media preview",
              onLoad: () => setIsLoading(false),
              onError: (e) => {
                console.error("Image load error:", e);
                setIsLoading(false);
                e.target.style.display = "none";
              },
              ref: (img) => {
                if (img && img.complete && img.naturalHeight !== 0) {
                  setIsLoading(false);
                }
              },
              style: isLoading
                ? "opacity: 0;"
                : "opacity: 1; transition: opacity 0.3s;",
            });
          }
        })(),

        h("button", {
          className: "tmd-preview-nav tmd-preview-nav-next",
          onClick: nextMedia,
          disabled: currentIndex === filteredMedia.length - 1,
          dangerouslySetInnerHTML: { __html: ICONS.chevronRight },
        }),

        isLoading &&
          h(
            "div",
            {
              style: `
                        position: absolute;
                        top: 50%;
                        left: 50%;
                        transform: translate(-50%, -50%);
                        color: white;
                        display: flex;
                        align-items: center;
                        gap: 8px;
                        background: rgba(0, 0, 0, 0.8);
                        padding: 12px 20px;
                        border-radius: 8px;
                        backdrop-filter: blur(10px);
                    `,
            },
            h("div", {
              style: "animation: spin 1s linear infinite;",
              dangerouslySetInnerHTML: { __html: ICONS.spinner },
            }),
            "Loading..."
          ),

        h(
          "div",
          { className: "tmd-preview-counter" },
          `${currentIndex + 1} / ${filteredMedia.length}`
        )
      )
    );
  }

  function DatabaseTab() {
    const [cachedUsers, setCachedUsers] = useState([]);
    const [selectedUser, setSelectedUser] = useState(null);
    const [currentPage, setCurrentPage] = useState(1);
    const [currentAccountPage, setCurrentAccountPage] = useState(1);
    const [showDeleteAlert, setShowDeleteAlert] = useState(false);
    const [deleteTarget, setDeleteTarget] = useState(null);
    const [jumpToPage, setJumpToPage] = useState("");
    const [jumpToAccountPage, setJumpToAccountPage] = useState("");
    const [showClearAllAlert, setShowClearAllAlert] = useState(false);
    const [showShredListAlert, setShowShredListAlert] = useState(false);
    const [hasBatchDatabases, setHasBatchDatabases] = useState(false);
    const [hasAnyDatabase, setHasAnyDatabase] = useState(false);
    const [mediaFilters, setMediaFilters] = useState({
      photo: false,
      video: false,
      animated_gif: false,
    });
    const itemsPerPage = 5;
    const accountsPerPage = 3;

    useEffect(() => {
      loadCachedUsers();
    }, []);

    const loadCachedUsers = async () => {
      try {
        const allCaches = await db.mediaData.toArray();

        const batchDatabases = allCaches.filter(
          (cache) => cache.cacheKey && cache.cacheKey.endsWith("_batch")
        );
        setHasBatchDatabases(batchDatabases.length > 0);
        setHasAnyDatabase(allCaches.length > 0);

        const userMap = new Map();

        allCaches.forEach((cache) => {
          const isBatchCache =
            cache.cacheKey && cache.cacheKey.endsWith("_batch");
          if (state.showBatchDatabase.value && !isBatchCache) return;

          const mapKey = isBatchCache
            ? `${cache.username}_batch`
            : `${cache.username}_regular`;

          if (!userMap.has(mapKey)) {
            userMap.set(mapKey, {
              username: cache.username,
              configs: [],
              latestTimestamp: cache.timestamp,
              totalMedia: 0,
              data: cache.data,
              isBatchGroup: isBatchCache,
            });
          }

          const user = userMap.get(mapKey);
          user.configs.push({
            timelineType: cache.timelineType,
            mediaType: cache.mediaType,
            timestamp: cache.timestamp,
            mediaCount: cache.data.timeline.length,
            cacheKey: cache.cacheKey,
            isBatch: cache.isBatch || isBatchCache,
          });

          if (cache.timestamp > user.latestTimestamp) {
            user.latestTimestamp = cache.timestamp;
            user.data = cache.data;
          }
          user.totalMedia += cache.data.timeline.length;
        });

        const users = Array.from(userMap.values());
        setCachedUsers(
          users.sort((a, b) => b.latestTimestamp - a.latestTimestamp)
        );
      } catch (error) {
        console.error("Failed to load cached users:", error);
      }
    };

    const handleDeleteClick = (type, target) => {
      if (type === "media") {
        handleDirectMediaDelete(target);
      } else {
        setDeleteTarget({ type, target });
        setShowDeleteAlert(true);
      }
    };

    const handleDirectMediaDelete = async (target) => {
      try {
        const { cacheKey, index } = target;

        if (!cacheKey) {
          console.error("No cacheKey provided for delete operation");
          return;
        }

        if (index === undefined || index === null || index < 0) {
          console.error("Invalid index provided for delete operation");
          return;
        }

        const userData = await db.mediaData.get(cacheKey);
        if (userData) {
          userData.data.timeline.splice(index, 1);
          await db.mediaData.put(userData);

          await loadCachedUsers();

          if (selectedUser?.cacheKey === cacheKey) {
            const updatedUser = await db.mediaData.get(cacheKey);
            setSelectedUser(updatedUser);

            const activeFilters = Object.values(mediaFilters).some((v) => v);
            const timelineToCheck = activeFilters
              ? updatedUser.data.timeline.filter((media) => {
                  if (mediaFilters.photo && media.type === "photo") return true;
                  if (mediaFilters.video && media.type === "video") return true;
                  if (
                    mediaFilters.animated_gif &&
                    media.type === "animated_gif"
                  )
                    return true;
                  return false;
                })
              : updatedUser.data.timeline;

            const newTotalPages = Math.ceil(
              timelineToCheck.length / itemsPerPage
            );
            if (currentPage > newTotalPages && newTotalPages > 0) {
              setCurrentPage(newTotalPages);
            }
          }
        }
      } catch (error) {
        console.error("Failed to delete media:", error);
      }
    };

    const handleDeleteConfirm = async () => {
      if (!deleteTarget) return;

      try {
        if (deleteTarget.type === "user") {
          const targetUsername =
            typeof deleteTarget.target === "string"
              ? deleteTarget.target
              : deleteTarget.target.username;
          const targetIsBatch =
            typeof deleteTarget.target === "object"
              ? deleteTarget.target.isBatchGroup
              : undefined;

          const allCaches = await db.mediaData
            .where("username")
            .equals(targetUsername)
            .toArray();

          const cachesToDelete = allCaches.filter((cache) => {
            const isBatchCache =
              cache.cacheKey && cache.cacheKey.endsWith("_batch");
            return (
              targetIsBatch === undefined || targetIsBatch === isBatchCache
            );
          });

          for (const cache of cachesToDelete) {
            await db.mediaData.delete(cache.cacheKey);
          }
          await loadCachedUsers();
          if (selectedUser?.username === targetUsername) {
            setSelectedUser(null);
            setCurrentPage(1);
          }
        } else if (deleteTarget.type === "config") {
          await db.mediaData.delete(deleteTarget.target);
          await loadCachedUsers();
          setSelectedUser(null);
          setCurrentPage(1);
        } else if (deleteTarget.type === "media") {
          const { cacheKey, index } = deleteTarget.target;
          const userData = await db.mediaData.get(cacheKey);
          if (userData) {
            userData.data.timeline.splice(index, 1);
            await db.mediaData.put(userData);

            await loadCachedUsers();

            if (selectedUser?.cacheKey === cacheKey) {
              const updatedUser = await db.mediaData.get(cacheKey);
              setSelectedUser(updatedUser);

              const activeFilters = Object.values(mediaFilters).some((v) => v);
              const timelineToCheck = activeFilters
                ? updatedUser.data.timeline.filter((media) => {
                    if (mediaFilters.photo && media.type === "photo")
                      return true;
                    if (mediaFilters.video && media.type === "video")
                      return true;
                    if (
                      mediaFilters.animated_gif &&
                      media.type === "animated_gif"
                    )
                      return true;
                    return false;
                  })
                : updatedUser.data.timeline;

              const newTotalPages = Math.ceil(
                timelineToCheck.length / itemsPerPage
              );
              if (currentPage > newTotalPages && newTotalPages > 0) {
                setCurrentPage(newTotalPages);
              }
            }
          }
        }
      } catch (error) {
        console.error("Failed to delete:", error);
      }

      setShowDeleteAlert(false);
      setDeleteTarget(null);
    };

    const handleClearAllConfirm = async () => {
      try {
        await db.mediaData.clear();
        await loadCachedUsers();
        setSelectedUser(null);
        setCurrentPage(1);
        setCurrentAccountPage(1);
        state.mediaData.value = null;
        state.selectedCacheUser.value = null;
      } catch (error) {
        console.error("Failed to clear cached media data:", error);
      }
      setShowClearAllAlert(false);
    };

    const handleShredListConfirm = async () => {
      try {
        if (selectedUser && selectedUser.cacheKey) {
          const cacheKey = selectedUser.cacheKey;

          await db.mediaData.delete(cacheKey);

          await loadCachedUsers();
          setSelectedUser(null);
          setCurrentPage(1);
        }
      } catch (error) {
        console.error("Failed to shred media list:", error);
      }
      setShowShredListAlert(false);
    };

    const handleDeleteCancel = () => {
      setShowDeleteAlert(false);
      setDeleteTarget(null);
    };

    const downloadSingleMedia = async (media, index) => {
      try {
        const date = dayjs(media.date).format("YYYY-MM-DD_HHmmss");
        const ext =
          media.type === "video"
            ? "mp4"
            : media.type === "animated_gif"
            ? "mp4"
            : "jpg";
        const actualUsername =
          selectedUser.data?.account_info?.name ||
          selectedUser.username ||
          "unknown";
        const filename = `${date}_${actualUsername}_${media.tweet_id}_${index}.${ext}`;

        console.log(`Downloading single file: ${filename}`);

        const response = await new Promise((resolve, reject) => {
          const timeout = setTimeout(() => {
            reject(new Error("Download timeout"));
          }, 60000);

          GM_xmlhttpRequest({
            method: "GET",
            url: media.url,
            responseType: "blob",
            onload: (res) => {
              clearTimeout(timeout);
              if (res.status === 200 && res.response) {
                resolve(res.response);
              } else {
                reject(new Error(`HTTP ${res.status}`));
              }
            },
            onerror: (err) => {
              clearTimeout(timeout);
              reject(err);
            },
            ontimeout: () => {
              clearTimeout(timeout);
              reject(new Error("Request timeout"));
            },
          });
        });

        saveAs(response, filename);
        console.log(`✓ Downloaded: ${filename}`);
      } catch (error) {
        console.error("Failed to download single media:", error);
        alert(`Failed to download media: ${error.message}`);
      }
    };

    const formatNumber = (num) => {
      return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    };

    const getTimeAgo = (timestamp) => {
      const now = Date.now();
      const diff = now - timestamp;
      const seconds = Math.floor(diff / 1000);
      const minutes = Math.floor(seconds / 60);
      const hours = Math.floor(minutes / 60);
      const days = Math.floor(hours / 24);

      if (days > 0) {
        const remainingHours = hours % 24;
        return `${days}d ${remainingHours}h ago`;
      } else if (hours > 0) {
        const remainingMinutes = minutes % 60;
        return `${hours}h ${remainingMinutes}m ago`;
      } else if (minutes > 0) {
        return `${minutes}m ago`;
      } else {
        return "just now";
      }
    };

    const getMediaIcon = (type) => {
      switch (type) {
        case "photo":
          return ICONS.photo.replace(
            'stroke="currentColor"',
            'stroke="hsl(142.1deg 76.2% 36.3%)"'
          );
        case "video":
          return ICONS.video.replace(
            'stroke="currentColor"',
            'stroke="hsl(37.7deg 92.1% 50.2%)"'
          );
        case "animated_gif":
          return ICONS.animatedGif.replace(
            'stroke="currentColor"',
            'stroke="hsl(270deg 60% 50%)"'
          );
        default:
          return ICONS.photo.replace(
            'stroke="currentColor"',
            'stroke="hsl(142.1deg 76.2% 36.3%)"'
          );
      }
    };

    const filteredTimeline = selectedUser
      ? (() => {
          const hasActiveFilter = Object.values(mediaFilters).some((v) => v);

          if (!hasActiveFilter) {
            return selectedUser.data.timeline;
          }

          return selectedUser.data.timeline.filter((media) => {
            if (mediaFilters.photo && media.type === "photo") return true;
            if (mediaFilters.video && media.type === "video") return true;
            if (mediaFilters.animated_gif && media.type === "animated_gif")
              return true;
            return false;
          });
        })()
      : [];

    const paginatedMedia = filteredTimeline.slice(
      (currentPage - 1) * itemsPerPage,
      currentPage * itemsPerPage
    );

    const totalPages = Math.ceil(filteredTimeline.length / itemsPerPage) || 0;

    const toggleFilter = (type) => {
      setMediaFilters((prev) => ({
        ...prev,
        [type]: !prev[type],
      }));
      setCurrentPage(1);
    };

    return h(
      "div",
      null,
      showDeleteAlert &&
        h(AlertDialog, {
          title:
            deleteTarget?.type === "user"
              ? "Delete User Cache"
              : "Delete Media Entry",
          message:
            deleteTarget?.type === "user"
              ? `Are you sure you want to delete all cached data for @<strong>${
                  typeof deleteTarget.target === "string"
                    ? deleteTarget.target
                    : deleteTarget.target?.username || "unknown"
                }</strong>?`
              : "Are you sure you want to delete this media entry?",
          onConfirm: handleDeleteConfirm,
          onCancel: handleDeleteCancel,
        }),

      showClearAllAlert &&
        h(AlertDialog, {
          title: "Shred All Cache",
          message:
            "WARNING: This will permanently delete ALL cached media data. This action cannot be undone. Are you absolutely sure?",
          onConfirm: handleClearAllConfirm,
          onCancel: () => setShowClearAllAlert(false),
        }),

      showShredListAlert &&
        h(AlertDialog, {
          title: "Shred Media List",
          message:
            "WARNING: This will permanently delete ALL media items in this cached list. This action cannot be undone. Are you absolutely sure?",
          onConfirm: handleShredListConfirm,
          onCancel: () => setShowShredListAlert(false),
        }),

      !selectedUser
        ? h(
            "div",
            null,
            h(
              "div",
              {
                style:
                  "display: flex; align-items: center; gap: 8px; margin-bottom: 16px;",
              },
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline tmd-import-button",
                  style: "padding: 6px 12px;",
                  title:
                    "Import database from .db files (supports multiple files)",
                  onClick: () => {
                    const input = document.createElement("input");
                    input.type = "file";
                    input.accept = ".db";
                    input.multiple = true;
                    input.onchange = async (e) => {
                      const files = Array.from(e.target.files);
                      if (files.length > 0) {
                        try {
                          let successCount = 0;
                          let errorCount = 0;
                          const errors = [];

                          state.isLoading.value = true;

                          for (let i = 0; i < files.length; i++) {
                            const file = files[i];
                            try {
                              await db.import(file, {
                                acceptMissingTables: true,
                                acceptVersionDiff: true,
                                acceptNameDiff: true,
                                overwriteValues: true,
                              });
                              successCount++;
                            } catch (error) {
                              errorCount++;
                              errors.push(`${file.name}: ${error.message}`);
                              console.error(
                                `Failed to import ${file.name}:`,
                                error
                              );
                            }
                          }

                          state.isLoading.value = false;

                          await loadCachedUsers();

                          if (errorCount === 0) {
                            state.success.value = `Successfully imported ${successCount} database${
                              successCount > 1 ? "s" : ""
                            }!`;
                          } else if (successCount === 0) {
                            state.error.value = `Failed to import all ${errorCount} files. ${errors.join(
                              "; "
                            )}`;
                            state.errorType.value = "general";
                          } else {
                            state.success.value = `Imported ${successCount} database${
                              successCount > 1 ? "s" : ""
                            } successfully. ${errorCount} failed: ${errors.join(
                              "; "
                            )}`;
                          }

                          const currentSuccessMessage = state.success.value;
                          const currentErrorMessage = state.error.value;

                          setTimeout(() => {
                            if (state.success.value === currentSuccessMessage) {
                              state.success.value = null;
                            }
                          }, 3000);

                          setTimeout(() => {
                            if (state.error.value === currentErrorMessage) {
                              state.error.value = null;
                            }
                          }, 3000);
                        } catch (error) {
                          state.isLoading.value = false;
                          console.error("Failed to import databases:", error);
                          const errorMessage = `Failed to import: ${error.message}`;
                          state.error.value = errorMessage;
                          state.errorType.value = "general";

                          setTimeout(() => {
                            if (state.error.value === errorMessage) {
                              state.error.value = null;
                            }
                          }, 3000);
                        }
                      }
                    };
                    input.click();
                  },
                },
                h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.fileInput,
                  },
                }),
                "Import"
              ),

              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline tmd-shred-button",
                  style: "padding: 6px 12px;",
                  onClick: () => setShowClearAllAlert(true),
                  title: "Shred all cached data",
                  disabled: !hasAnyDatabase,
                },
                h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.shredder,
                  },
                }),
                "Shred"
              ),

              h(
                "button",
                {
                  className: `tmd-button tmd-button-outline ${
                    state.showBatchDatabase.value
                      ? "tmd-batch-toggle-active"
                      : ""
                  }`,
                  style: `padding: 6px 12px; ${
                    state.showBatchDatabase.value
                      ? "background: hsl(270deg 60% 50% / 0.15); border-color: hsl(270deg 60% 50%); color: hsl(270deg 60% 50%);"
                      : ""
                  }${
                    !hasBatchDatabases
                      ? " opacity: 0.5; cursor: not-allowed;"
                      : ""
                  }`,
                  onClick: () => {
                    if (hasBatchDatabases) {
                      state.showBatchDatabase.value =
                        !state.showBatchDatabase.value;
                      saveSettings();
                      loadCachedUsers();
                    }
                  },
                  disabled: !hasBatchDatabases,
                  title: !hasBatchDatabases
                    ? "No batch databases available"
                    : state.showBatchDatabase.value
                    ? "Filter enabled: Showing only batch databases. Click to show all databases"
                    : "Filter disabled: Showing all databases. Click to filter batch databases only",
                },
                h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.layers,
                  },
                }),
                "Batch"
              )
            ),

            cachedUsers.length === 0
              ? h(
                  "div",
                  {
                    style:
                      "text-align: center; padding: 40px 20px; opacity: 0.6;",
                  },
                  h("div", {
                    dangerouslySetInnerHTML: {
                      __html: ICONS.frown
                        .replace('width="24"', 'width="48"')
                        .replace('height="24"', 'height="48"'),
                    },
                    style:
                      "display: flex; justify-content: center; margin-bottom: 16px; opacity: 0.5;",
                  }),
                  h(
                    "p",
                    { style: "font-size: 16px;" },
                    state.showBatchDatabase.value
                      ? "No batch databases available"
                      : "No cached data available"
                  )
                )
              : (() => {
                  const paginatedAccounts = cachedUsers.slice(
                    (currentAccountPage - 1) * accountsPerPage,
                    currentAccountPage * accountsPerPage
                  );
                  const totalAccountPages = Math.ceil(
                    cachedUsers.length / accountsPerPage
                  );

                  return h(
                    "div",
                    null,
                    h(
                      "div",
                      {
                        style: "display: flex; gap: 8px; margin-bottom: 16px;",
                      },
                      totalAccountPages > 1 &&
                        h(
                          "button",
                          {
                            className: "tmd-button tmd-button-outline",
                            style: "padding: 6px 12px;",
                            disabled:
                              !jumpToAccountPage ||
                              parseInt(jumpToAccountPage) < 1 ||
                              parseInt(jumpToAccountPage) > totalAccountPages,
                            onClick: () => {
                              const page = parseInt(jumpToAccountPage);
                              if (page >= 1 && page <= totalAccountPages) {
                                setCurrentAccountPage(page);
                                setJumpToAccountPage("");
                              }
                            },
                          },
                          h("span", {
                            dangerouslySetInnerHTML: { __html: ICONS.rabbit },
                          }),
                          "Jump"
                        ),
                      totalAccountPages > 1 &&
                        h("input", {
                          type: "number",
                          className: "tmd-input",
                          value: jumpToAccountPage,
                          onInput: (e) => setJumpToAccountPage(e.target.value),
                          placeholder: "",
                          min: 1,
                          max: totalAccountPages,
                          style:
                            "width: 50px; padding: 6px 8px; text-align: center;",
                        })
                    ),

                    h(
                      "div",
                      { style: "margin-bottom: 20px;" },
                      paginatedAccounts.map((user) =>
                        h(
                          "div",
                          {
                            className: "tmd-info-card clickable",
                            style: "margin-bottom: 12px;",
                          },
                          h(
                            "div",
                            {
                              style:
                                "display: flex; align-items: center; gap: 12px;",
                            },
                            user.data.account_info.profile_image &&
                              h("img", {
                                src: user.data.account_info.profile_image,
                                style:
                                  "width: 56px; height: 56px; border-radius: 50%; object-fit: cover;",
                              }),

                            h(
                              "div",
                              { style: "flex: 1;" },
                              h(
                                "div",
                                {
                                  style:
                                    "font-weight: 600; display: flex; align-items: center; gap: 8px;",
                                },
                                user.data.account_info.nick,
                                user.isBatchGroup &&
                                  h(
                                    "span",
                                    {
                                      style:
                                        "background: hsl(270deg 60% 50% / 0.2); color: hsl(270deg 60% 50%); padding: 2px 8px; border-radius: 4px; font-weight: 500; font-size: 11px;",
                                    },
                                    "BATCH"
                                  )
                              ),
                              h(
                                "a",
                                {
                                  href: `https://x.com/${user.username}`,
                                  target: "_blank",
                                  rel: "noopener noreferrer",
                                  style:
                                    "font-size: 14px; opacity: 0.7; color: inherit; text-decoration: none; display: inline-block; transition: all 0.2s;",
                                  onMouseEnter: (e) => {
                                    e.target.style.opacity = "1";
                                    e.target.style.color =
                                      "hsl(204.17deg 87.55% 52.75%)";
                                    e.target.style.textDecoration = "underline";
                                  },
                                  onMouseLeave: (e) => {
                                    e.target.style.opacity = "0.7";
                                    e.target.style.color = "inherit";
                                    e.target.style.textDecoration = "none";
                                  },
                                  onClick: (e) => e.stopPropagation(),
                                },
                                `@${user.username}`
                              ),
                              h(
                                "div",
                                {
                                  style:
                                    "font-size: 12px; opacity: 0.5; margin-top: 4px;",
                                },
                                `Cached: ${dayjs(user.latestTimestamp).format(
                                  "DD MMM YYYY HH:mm"
                                )} • ${getTimeAgo(user.latestTimestamp)}`
                              ),
                              h(
                                "div",
                                {
                                  style:
                                    "display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px;",
                                },
                                user.configs.map((config) =>
                                  h(
                                    "span",
                                    {
                                      style: `
                                                        display: inline-flex;
                                                        align-items: center;
                                                        gap: 3px;
                                                        padding: 2px 6px;
                                                        font-size: 11px;
                                                        font-weight: 500;
                                                        border-radius: 4px;
                                                        background: ${
                                                          config.timelineType ===
                                                          "media"
                                                            ? "hsl(204.17deg 87.55% 52.75% / 0.15)"
                                                            : config.timelineType ===
                                                              "timeline"
                                                            ? "hsl(142.1deg 76.2% 36.3% / 0.15)"
                                                            : config.timelineType ===
                                                              "tweets"
                                                            ? "hsl(37.7deg 92.1% 50.2% / 0.15)"
                                                            : "hsl(270deg 60% 50% / 0.15)"
                                                        };
                                                        color: ${
                                                          config.timelineType ===
                                                          "media"
                                                            ? "hsl(204.17deg 87.55% 52.75%)"
                                                            : config.timelineType ===
                                                              "timeline"
                                                            ? "hsl(142.1deg 76.2% 36.3%)"
                                                            : config.timelineType ===
                                                              "tweets"
                                                            ? "hsl(37.7deg 92.1% 50.2%)"
                                                            : "hsl(270deg 60% 50%)"
                                                        };
                                                        border: 1px solid ${
                                                          config.timelineType ===
                                                          "media"
                                                            ? "hsl(204.17deg 87.55% 52.75% / 0.3)"
                                                            : config.timelineType ===
                                                              "timeline"
                                                            ? "hsl(142.1deg 76.2% 36.3% / 0.3)"
                                                            : config.timelineType ===
                                                              "tweets"
                                                            ? "hsl(37.7deg 92.1% 50.2% / 0.3)"
                                                            : "hsl(270deg 60% 50% / 0.3)"
                                                        };
                                                        cursor: pointer;
                                                        transition: all 0.2s;
                                                    `,
                                      onClick: async (e) => {
                                        e.stopPropagation();
                                        const cacheData =
                                          await db.mediaData.get(
                                            config.cacheKey
                                          );
                                        if (cacheData) {
                                          setSelectedUser(cacheData);
                                          setCurrentPage(1);
                                        }
                                      },
                                      onMouseEnter: (e) => {
                                        e.target.style.opacity = "0.8";
                                      },
                                      onMouseLeave: (e) => {
                                        e.target.style.opacity = "1";
                                      },
                                      title: `Load ${
                                        config.timelineType
                                      } with ${config.mediaType} media (${
                                        config.mediaCount
                                      } items)${
                                        config.isBatch ? " - Batch" : ""
                                      }`,
                                    },
                                    h(
                                      "span",
                                      null,
                                      config.timelineType === "media"
                                        ? "Media"
                                        : config.timelineType === "timeline"
                                        ? "Posts"
                                        : config.timelineType === "tweets"
                                        ? "Tweets"
                                        : "Replies"
                                    ),
                                    config.mediaType !== "all" &&
                                      h(
                                        "span",
                                        {
                                          style:
                                            "opacity: 0.8; font-weight: 400;",
                                        },
                                        config.mediaType === "image"
                                          ? "[IMG]"
                                          : config.mediaType === "video"
                                          ? "[VID]"
                                          : "[GIF]"
                                      ),
                                    h(
                                      "span",
                                      {
                                        style:
                                          "opacity: 0.9; font-weight: 600;",
                                      },
                                      `(${config.mediaCount})`
                                    )
                                  )
                                )
                              )
                            ),

                            h(
                              "div",
                              { style: "display: flex; gap: 8px;" },
                              user.data &&
                                user.data.timeline &&
                                user.data.timeline.length > 0 &&
                                h("button", {
                                  className:
                                    "tmd-button tmd-button-outline tmd-button-square",
                                  style: "height: 40px;",
                                  title: "Preview media gallery",
                                  onClick: (e) => {
                                    e.stopPropagation();
                                    state.previewMediaData.value = user.data;
                                    state.previewCurrentIndex.value = 0;
                                    state.previewModalOpen.value = true;
                                  },
                                  dangerouslySetInnerHTML: {
                                    __html: ICONS.eye,
                                  },
                                }),
                              h("button", {
                                className:
                                  "tmd-button tmd-button-outline tmd-button-square tmd-delete-button",
                                style: "height: 40px;",
                                title: "Delete cache",
                                onClick: (e) => {
                                  e.stopPropagation();
                                  handleDeleteClick("user", {
                                    username: user.username,
                                    isBatchGroup: user.isBatchGroup,
                                  });
                                },
                                dangerouslySetInnerHTML: {
                                  __html: ICONS.trash,
                                },
                              })
                            )
                          )
                        )
                      )
                    ),

                    totalAccountPages > 1 &&
                      h(
                        "div",
                        {
                          style:
                            "display: flex; justify-content: center; gap: 8px; align-items: center;",
                        },
                        h("button", {
                          className:
                            "tmd-button tmd-button-outline tmd-button-square",
                          disabled: currentAccountPage === 1,
                          onClick: () => setCurrentAccountPage(1),
                          title: "First page",
                          dangerouslySetInnerHTML: {
                            __html: ICONS.chevronsLeft,
                          },
                        }),
                        h("button", {
                          className:
                            "tmd-button tmd-button-outline tmd-button-square",
                          disabled: currentAccountPage === 1,
                          onClick: () =>
                            setCurrentAccountPage(currentAccountPage - 1),
                          title: "Previous page",
                          dangerouslySetInnerHTML: {
                            __html: ICONS.chevronLeft,
                          },
                        }),
                        h(
                          "span",
                          {
                            style:
                              "padding: 0 12px; display: flex; align-items: center; font-weight: 500;",
                          },
                          `${currentAccountPage} / ${totalAccountPages}`
                        ),
                        h("button", {
                          className:
                            "tmd-button tmd-button-outline tmd-button-square",
                          disabled: currentAccountPage === totalAccountPages,
                          onClick: () =>
                            setCurrentAccountPage(currentAccountPage + 1),
                          title: "Next page",
                          dangerouslySetInnerHTML: {
                            __html: ICONS.chevronRight,
                          },
                        }),
                        h("button", {
                          className:
                            "tmd-button tmd-button-outline tmd-button-square",
                          disabled: currentAccountPage === totalAccountPages,
                          onClick: () =>
                            setCurrentAccountPage(totalAccountPages),
                          title: "Last page",
                          dangerouslySetInnerHTML: {
                            __html: ICONS.chevronsRight,
                          },
                        })
                      )
                  );
                })()
          )
        : h(
            "div",
            { className: "tmd-database-content" },
            h(
              "div",
              {
                style:
                  "display: flex; align-items: center; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;",
              },
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline",
                  style: "padding: 6px 12px;",
                  onClick: () => {
                    setSelectedUser(null);
                    setCurrentPage(1);
                    setJumpToPage("");
                  },
                  title: "Back to user list",
                },
                h("span", { dangerouslySetInnerHTML: { __html: ICONS.undo } }),
                "Back"
              ),
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline tmd-load-button",
                  style: "padding: 6px 12px;",
                  title: "Load this cached data to Dashboard",
                  onClick: async () => {
                    if (selectedUser) {
                      state.mediaData.value = selectedUser.data;
                      state.currentUsername.value = selectedUser.username;

                      state.loadedFromDatabase.value = true;
                      state.loadedDatabaseConfig.value = {
                        cacheKey: selectedUser.cacheKey,
                        isBatch:
                          selectedUser.isBatch ||
                          (selectedUser.cacheKey &&
                            selectedUser.cacheKey.endsWith("_batch")),
                        timelineType: selectedUser.timelineType || parts[1],
                        mediaType: selectedUser.mediaType || parts[2],
                      };

                      if (selectedUser.cacheKey) {
                        const parts = selectedUser.cacheKey.split("_");
                        if (parts.length >= 3) {
                          state.timelineType.value = parts[1];
                          state.mediaType.value = parts[2];

                          state.loadedDatabaseConfig.value.timelineType =
                            parts[1];
                          state.loadedDatabaseConfig.value.mediaType =
                            parts[2] === "batch" ? parts[2 - 1] : parts[2];
                        }
                      } else if (
                        selectedUser.timelineType &&
                        selectedUser.mediaType
                      ) {
                        state.timelineType.value = selectedUser.timelineType;
                        state.mediaType.value = selectedUser.mediaType;
                      }

                      state.activeTab.value = "dashboard";
                    }
                  },
                },
                h("span", {
                  dangerouslySetInnerHTML: { __html: ICONS.upload },
                }),
                "Load"
              ),
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline tmd-export-button",
                  style: "padding: 6px 12px;",
                  title: "Export this database to .db file",
                  onClick: async () => {
                    if (selectedUser) {
                      try {
                        const tempDb = new Dexie("TempExportDB");
                        tempDb.version(1).stores({
                          mediaData:
                            "cacheKey, username, timelineType, mediaType, data, timestamp",
                        });

                        await tempDb.open();

                        await tempDb.mediaData.put({
                          cacheKey: selectedUser.cacheKey,
                          username: selectedUser.username,
                          timelineType: selectedUser.timelineType,
                          mediaType: selectedUser.mediaType,
                          data: selectedUser.data,
                          timestamp: selectedUser.timestamp,
                          isBatch:
                            selectedUser.isBatch ||
                            (selectedUser.cacheKey &&
                              selectedUser.cacheKey.endsWith("_batch")),
                        });

                        const blob = await tempDb.export();
                        const filename = `${selectedUser.username}_${
                          selectedUser.timelineType || "media"
                        }_${selectedUser.mediaType || "all"}_${dayjs(
                          selectedUser.timestamp
                        ).format("YYYY-MM-DD_HHmmss")}.db`;

                        saveAs(blob, filename);

                        await tempDb.delete();

                        state.success.value = "Database exported successfully!";
                        setTimeout(() => {
                          if (
                            state.success.value ===
                            "Database exported successfully!"
                          ) {
                            state.success.value = null;
                          }
                        }, 3000);
                      } catch (error) {
                        console.error("Failed to export database:", error);
                        state.error.value = `Failed to export: ${error.message}`;
                        state.errorType.value = "general";
                      }
                    }
                  },
                },
                h("span", {
                  dangerouslySetInnerHTML: { __html: ICONS.fileOutput },
                }),
                "Export"
              ),
              h(
                "button",
                {
                  className: "tmd-button tmd-button-outline tmd-shred-button",
                  style: "padding: 6px 12px;",
                  title: "Shred: delete all media in this cached list",
                  onClick: () => setShowShredListAlert(true),
                },
                h("span", {
                  dangerouslySetInnerHTML: { __html: ICONS.shredder },
                }),
                "Shred"
              ),
              h(
                "div",
                {
                  style:
                    "margin-left: auto; display: flex; align-items: center; gap: 8px;",
                },
                h(
                  "button",
                  {
                    className: "tmd-button tmd-button-outline",
                    style: "padding: 6px 12px;",
                    disabled:
                      !jumpToPage ||
                      parseInt(jumpToPage) < 1 ||
                      parseInt(jumpToPage) > totalPages,
                    onClick: () => {
                      const page = parseInt(jumpToPage);
                      if (page >= 1 && page <= totalPages) {
                        setCurrentPage(page);
                        setJumpToPage("");
                      }
                    },
                  },
                  h("span", {
                    dangerouslySetInnerHTML: { __html: ICONS.rabbit },
                  }),
                  "Jump"
                ),
                h("input", {
                  type: "number",
                  className: "tmd-input",
                  value: jumpToPage,
                  onInput: (e) => setJumpToPage(e.target.value),
                  placeholder: "",
                  min: 1,
                  max: totalPages,
                  style: "width: 50px; padding: 6px 8px; text-align: center;",
                })
              )
            ),
            h(
              "div",
              {
                className: "tmd-info-card",
                style:
                  "margin-bottom: 8px; background: hsl(204.17deg 87.55% 52.75% / 0.1);",
              },
              h(
                "div",
                {
                  style:
                    "display: flex; align-items: center; justify-content: space-between;",
                },
                h(
                  "div",
                  { style: "display: flex; align-items: center; gap: 8px;" },
                  h("span", {
                    dangerouslySetInnerHTML: {
                      __html: ICONS.database.replace(
                        'stroke="currentColor"',
                        'stroke="hsl(204.17deg 87.55% 52.75%)"'
                      ),
                    },
                    style: "opacity: 0.8;",
                  }),
                  h(
                    "div",
                    null,
                    h(
                      "div",
                      { style: "font-size: 14px; font-weight: 600;" },
                      `${selectedUser.data.account_info.nick}'s ${(() => {
                        if (selectedUser.mediaType) {
                          if (selectedUser.mediaType === "image")
                            return "Images";
                          if (selectedUser.mediaType === "video")
                            return "Videos";
                          if (selectedUser.mediaType === "gif") return "GIFs";
                        }
                        if (selectedUser.cacheKey) {
                          const parts = selectedUser.cacheKey.split("_");
                          if (parts.length >= 3) {
                            const mediaType = parts[2];
                            if (mediaType === "image") return "Images";
                            if (mediaType === "video") return "Videos";
                            if (mediaType === "gif") return "GIFs";
                          }
                        }
                        return "Media";
                      })()}`
                    ),
                    h(
                      "div",
                      { style: "font-size: 12px; opacity: 0.6;" },
                      filteredTimeline.length === 0
                        ? "No items match the selected filters"
                        : `Showing ${formatNumber(
                            Math.min(
                              (currentPage - 1) * itemsPerPage + 1,
                              filteredTimeline.length
                            )
                          )}-${formatNumber(
                            Math.min(
                              currentPage * itemsPerPage,
                              filteredTimeline.length
                            )
                          )} of ${formatNumber(filteredTimeline.length)} items${
                            Object.values(mediaFilters).some((v) => v)
                              ? " (filtered)"
                              : ""
                          }`
                    )
                  )
                ),

                (selectedUser.mediaType === "all" || !selectedUser.mediaType) &&
                  h(
                    "div",
                    { style: "display: flex; gap: 4px;" },
                    h("button", {
                      className: `tmd-button tmd-button-outline tmd-button-square tmd-filter-button tmd-filter-photo`,
                      style: `width: 32px; height: 32px; ${
                        mediaFilters.photo
                          ? "background: hsl(142.1deg 76.2% 36.3% / 0.15); border-color: hsl(142.1deg 76.2% 36.3%);"
                          : ""
                      }`,
                      onClick: () => toggleFilter("photo"),
                      title: "Filter photos",
                      dangerouslySetInnerHTML: {
                        __html: ICONS.photo.replace(
                          'stroke="currentColor"',
                          mediaFilters.photo
                            ? 'stroke="hsl(142.1deg 76.2% 36.3%)"'
                            : 'stroke="currentColor"'
                        ),
                      },
                    }),

                    h("button", {
                      className: `tmd-button tmd-button-outline tmd-button-square tmd-filter-button tmd-filter-video`,
                      style: `width: 32px; height: 32px; ${
                        mediaFilters.video
                          ? "background: hsl(37.7deg 92.1% 50.2% / 0.15); border-color: hsl(37.7deg 92.1% 50.2%);"
                          : ""
                      }`,
                      onClick: () => toggleFilter("video"),
                      title: "Filter videos",
                      dangerouslySetInnerHTML: {
                        __html: ICONS.video.replace(
                          'stroke="currentColor"',
                          mediaFilters.video
                            ? 'stroke="hsl(37.7deg 92.1% 50.2%)"'
                            : 'stroke="currentColor"'
                        ),
                      },
                    }),

                    h("button", {
                      className: `tmd-button tmd-button-outline tmd-button-square tmd-filter-button tmd-filter-gif`,
                      style: `width: 32px; height: 32px; ${
                        mediaFilters.animated_gif
                          ? "background: hsl(270deg 60% 50% / 0.15); border-color: hsl(270deg 60% 50%);"
                          : ""
                      }`,
                      onClick: () => toggleFilter("animated_gif"),
                      title: "Filter animated GIFs",
                      dangerouslySetInnerHTML: {
                        __html: ICONS.animatedGif.replace(
                          'stroke="currentColor"',
                          mediaFilters.animated_gif
                            ? 'stroke="hsl(270deg 60% 50%)"'
                            : 'stroke="currentColor"'
                        ),
                      },
                    })
                  )
              )
            ),

            h(
              "div",
              { className: "tmd-media-list-wrapper" },
              h(
                "div",
                { className: "tmd-media-list-container" },
                filteredTimeline.length === 0
                  ? h(
                      "div",
                      {
                        style:
                          "text-align: center; padding: 40px 20px; opacity: 0.6;",
                      },
                      h("div", {
                        dangerouslySetInnerHTML: {
                          __html: ICONS.frown
                            .replace('width="24"', 'width="32"')
                            .replace('height="24"', 'height="32"'),
                        },
                        style:
                          "display: flex; justify-content: center; margin-bottom: 12px; opacity: 0.5;",
                      }),
                      h(
                        "p",
                        { style: "font-size: 14px;" },
                        Object.values(mediaFilters).some((v) => v)
                          ? "No media matches the selected filters"
                          : "No media available"
                      )
                    )
                  : paginatedMedia.map((media) => {
                      const originalIndex =
                        selectedUser.data.timeline.indexOf(media);
                      return h(
                        "div",
                        {
                          className: "tmd-info-card clickable",
                          style: "margin-bottom: 8px;",
                        },
                        h(
                          "div",
                          {
                            style:
                              "display: flex; align-items: center; justify-content: space-between;",
                          },
                          h(
                            "div",
                            {
                              style:
                                "display: flex; align-items: center; gap: 8px;",
                            },
                            h("span", {
                              dangerouslySetInnerHTML: {
                                __html: getMediaIcon(media.type),
                              },
                              style: "opacity: 0.7;",
                            }),
                            h(
                              "div",
                              null,
                              h(
                                "a",
                                {
                                  className: "tmd-tweet-link",
                                  href: `https://x.com/${selectedUser.username}/status/${media.tweet_id}`,
                                  target: "_blank",
                                  rel: "noopener noreferrer",
                                  style: `font-size: 14px; display: block; color: ${
                                    media.type === "photo"
                                      ? "hsl(142.1deg 76.2% 36.3%)"
                                      : media.type === "video"
                                      ? "hsl(37.7deg 92.1% 50.2%)"
                                      : media.type === "animated_gif"
                                      ? "hsl(270deg 60% 50%)"
                                      : "hsl(204.17deg 87.55% 52.75%)"
                                  };`,
                                  title: `View tweet ${media.tweet_id}`,
                                },
                                media.tweet_id
                              ),
                              h(
                                "div",
                                { style: "font-size: 12px; opacity: 0.6;" },
                                dayjs(media.date).format("DD MMM YYYY HH:mm")
                              )
                            )
                          ),
                          h(
                            "div",
                            { style: "display: flex; gap: 4px;" },
                            h("button", {
                              className:
                                "tmd-button tmd-button-outline tmd-button-square tmd-preview-button",
                              style: "width: 32px; height: 32px;",
                              title: "Preview media",
                              onClick: () => window.open(media.url, "_blank"),
                              dangerouslySetInnerHTML: { __html: ICONS.eye },
                            }),
                            h("button", {
                              className:
                                "tmd-button tmd-button-outline tmd-button-square tmd-download-single-button",
                              style: "width: 32px; height: 32px;",
                              title: "Download media",
                              onClick: () =>
                                downloadSingleMedia(media, originalIndex),
                              dangerouslySetInnerHTML: {
                                __html: ICONS.download,
                              },
                            }),
                            h("button", {
                              className:
                                "tmd-button tmd-button-outline tmd-button-square tmd-delete-button",
                              style: "width: 32px; height: 32px;",
                              title: "Delete media",
                              onClick: () =>
                                handleDeleteClick("media", {
                                  cacheKey: selectedUser.cacheKey,
                                  index: originalIndex,
                                }),
                              dangerouslySetInnerHTML: { __html: ICONS.trash },
                            })
                          )
                        )
                      );
                    })
              )
            ),

            totalPages > 1 &&
              h(
                "div",
                {
                  style:
                    "display: flex; justify-content: center; gap: 8px; align-items: center;",
                },
                h("button", {
                  className: "tmd-button tmd-button-outline tmd-button-square",
                  disabled: currentPage === 1,
                  onClick: () => setCurrentPage(1),
                  title: "First page",
                  dangerouslySetInnerHTML: { __html: ICONS.chevronsLeft },
                }),
                h("button", {
                  className: "tmd-button tmd-button-outline tmd-button-square",
                  disabled: currentPage === 1,
                  onClick: () => setCurrentPage(currentPage - 1),
                  title: "Previous page",
                  dangerouslySetInnerHTML: { __html: ICONS.chevronLeft },
                }),
                h(
                  "span",
                  {
                    style:
                      "padding: 0 12px; display: flex; align-items: center; font-weight: 500;",
                  },
                  `${currentPage} / ${totalPages}`
                ),
                h("button", {
                  className: "tmd-button tmd-button-outline tmd-button-square",
                  disabled: currentPage === totalPages,
                  onClick: () => setCurrentPage(currentPage + 1),
                  title: "Next page",
                  dangerouslySetInnerHTML: { __html: ICONS.chevronRight },
                }),
                h("button", {
                  className: "tmd-button tmd-button-outline tmd-button-square",
                  disabled: currentPage === totalPages,
                  onClick: () => setCurrentPage(totalPages),
                  title: "Last page",
                  dangerouslySetInnerHTML: { __html: ICONS.chevronsRight },
                })
              )
          )
    );
  }

  function SettingsTab() {
    return h(
      "div",
      null,
      h(
        "div",
        {
          style: "display: flex; gap: 20px; margin-bottom: 20px;",
        },
        h(
          "div",
          {
            className: "tmd-input-group",
            style: "width: 180px; margin-bottom: 0;",
          },
          h(
            "label",
            { className: "tmd-label" },
            h("span", { dangerouslySetInnerHTML: { __html: ICONS.layers } }),
            "Batch Size"
          ),
          h("input", {
            type: "number",
            className: "tmd-input",
            value: state.batchSize.value,
            onInput: (e) => {
              const value = parseInt(e.target.value);
              if (value > 0 && value <= 200) {
                state.batchSize.value = value;
                saveSettings();
              }
            },
            placeholder: "1-200",
            min: 1,
            max: 200,
            style: "padding-right: 12px; width: 100%;",
          })
        ),

        h(
          "div",
          {
            className: "tmd-input-group",
            style: "width: 180px; margin-bottom: 0;",
          },
          h(
            "label",
            { className: "tmd-label" },
            h("span", {
              dangerouslySetInnerHTML: { __html: ICONS.betweenHorizontal },
            }),
            "Starting Batch"
          ),
          h("input", {
            type: "number",
            className: "tmd-input",
            value: state.startingBatch.value,
            onInput: (e) => {
              const value = parseInt(e.target.value);
              if (value >= 0) {
                state.startingBatch.value = value;
                state.currentBatchPage.value = value;
                saveSettings();
              }
            },
            placeholder: "0-based",
            min: 0,
            style: "padding-right: 12px; width: 100%;",
          })
        )
      ),

      h(
        "div",
        { className: "tmd-input-group" },
        h(
          "label",
          { className: "tmd-label" },
          h("span", { dangerouslySetInnerHTML: { __html: ICONS.twitter } }),
          "Timeline Type"
        ),
        h(
          "div",
          {
            className: "tmd-radio-group",
            style:
              "display: flex; gap: 20px; margin-top: 12px; flex-wrap: wrap;",
          },
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.timelineType.value = "media";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.timelineType.value === "media" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Media")
          ),
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.timelineType.value = "timeline";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.timelineType.value === "timeline" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Posts")
          ),
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.timelineType.value = "tweets";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.timelineType.value === "tweets" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Tweets")
          ),
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.timelineType.value = "with_replies";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.timelineType.value === "with_replies" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Replies")
          )
        )
      ),

      h(
        "div",
        { className: "tmd-input-group" },
        h(
          "label",
          { className: "tmd-label" },
          h("span", {
            dangerouslySetInnerHTML: { __html: ICONS.hardDriveDownload },
          }),
          "Concurrent Limit"
        ),
        h(
          "div",
          {
            className: "tmd-radio-group",
            style:
              "display: flex; gap: 20px; margin-top: 12px; flex-wrap: wrap;",
          },
          [5, 10, 20, 50, 100].map((limit) =>
            h(
              "div",
              {
                className: "tmd-radio-item",
                onClick: () => {
                  state.concurrentLimit.value = limit;
                  saveSettings();
                },
              },
              h("div", {
                className: `tmd-radio ${
                  state.concurrentLimit.value === limit ? "checked" : ""
                }`,
              }),
              h("span", { className: "tmd-radio-label" }, limit.toString())
            )
          )
        )
      ),

      h(
        "div",
        { className: "tmd-input-group" },
        h(
          "label",
          { className: "tmd-label" },
          h("span", { dangerouslySetInnerHTML: { __html: ICONS.server } }),
          "Service"
        ),
        h(
          "div",
          {
            className: "tmd-radio-group",
            style:
              "display: flex; gap: 20px; margin-top: 12px; flex-wrap: wrap;",
          },
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.selectedApi.value = "default";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.selectedApi.value === "default" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Default")
          ),
          h(
            "div",
            {
              className: "tmd-radio-item",
              onClick: () => {
                state.selectedApi.value = "backup";
                saveSettings();
              },
            },
            h("div", {
              className: `tmd-radio ${
                state.selectedApi.value === "backup" ? "checked" : ""
              }`,
            }),
            h("span", { className: "tmd-radio-label" }, "Backup")
          )
        )
      ),

      h(
        "div",
        { className: "tmd-success" },
        h("span", {
          className: "tmd-success-icon",
          dangerouslySetInnerHTML: { __html: ICONS.notepadText },
        }),
        h(
          "div",
          null,
          h(
            "div",
            { style: "margin-bottom: 8px;" },
            "• For accounts with thousands of media: Use ",
            h(
              "strong",
              { style: "color: hsl(204.17deg 87.55% 52.75%);" },
              "Batch/Auto Batch"
            ),
            " mode if single fetch fails."
          ),
          h(
            "div",
            { style: "margin-bottom: 8px;" },
            "• If Default service fails: Switch to ",
            h(
              "strong",
              { style: "color: hsl(142.1deg 76.2% 36.3%);" },
              "Backup"
            ),
            " service."
          ),
          h(
            "div",
            null,
            h(
              "strong",
              { style: "color: hsl(0deg 84.2% 60.2%);" },
              "• Warning:"
            ),
            " Using more than 20 concurrent downloads may cause some files to fail. Use ",
            h(
              "strong",
              { style: "color: hsl(142.1deg 76.2% 36.3%);" },
              "20 or below"
            ),
            " for better reliability."
          )
        )
      )
    );
  }

  function AuthTab() {
    const [showAuthToken, setShowAuthToken] = useState(false);
    const [showPatreonAuth, setShowPatreonAuth] = useState(false);
    const [generateStatus, setGenerateStatus] = useState("idle");
    const [verifyStatus, setVerifyStatus] = useState("idle");

    const handleAuthTokenChange = (e) => {
      state.authToken.value = e.target.value;
      saveSettings();
    };

    const handlePatreonAuthChange = (e) => {
      state.patreonAuth.value = e.target.value;
      state.isVerified.value = false;
      saveSettings();
    };

    const verifyPatreonAuth = async () => {
      if (!state.patreonAuth.value) {
        state.error.value = "Please enter your Patreon Auth code first";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (
            state.error.value === "Please enter your Patreon Auth code first"
          ) {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      if (state.patreonAuth.value === "xbatchdemo") {
        state.isVerified.value = true;
        setVerifyStatus("success");
        await saveSettings();
        setTimeout(() => {
          setVerifyStatus("idle");
        }, 1000);
        return;
      }

      setVerifyStatus("loading");
      state.error.value = null;

      const api =
        state.selectedApi.value === "backup"
          ? "https://backup.xbatch.online"
          : "https://api.xbatch.online";

      const url = `${api}/verify/${state.patreonAuth.value}`;

      try {
        const response = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            timeout: 10000,
            onload: (res) => {
              if (res.status === 200) {
                try {
                  const data = JSON.parse(res.responseText);
                  resolve(data);
                } catch (parseError) {
                  reject(new Error("Invalid response format"));
                }
              } else {
                reject(new Error(`Verification failed: ${res.status}`));
              }
            },
            onerror: () => reject(new Error("Network error")),
            ontimeout: () => reject(new Error("Request timeout")),
          });
        });

        if (response.valid === true) {
          state.isVerified.value = true;
          await saveSettings();
          setVerifyStatus("success");
          setTimeout(() => {
            setVerifyStatus("idle");
          }, 1000);
        } else {
          state.isVerified.value = false;
          await saveSettings();
          setVerifyStatus("error");
          state.error.value = "Invalid Patreon Auth code";
          state.errorType.value = "auth";
          setTimeout(() => {
            if (state.error.value === "Invalid Patreon Auth code") {
              state.error.value = null;
            }
            setVerifyStatus("idle");
          }, 2000);
        }
      } catch (error) {
        console.error("Verification failed:", error);
        state.isVerified.value = false;
        await saveSettings();
        setVerifyStatus("error");
        state.error.value = "Verification failed. Please try again.";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (state.error.value === "Verification failed. Please try again.") {
            state.error.value = null;
          }
          setVerifyStatus("idle");
        }, 2000);
      }
    };

    const generateAuthToken = async () => {
      if (!state.patreonAuth.value) {
        state.error.value = "Please enter Patreon Auth first";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (state.error.value === "Please enter Patreon Auth first") {
            state.error.value = null;
          }
        }, 2000);
        return;
      }

      if (state.patreonAuth.value === "xbatchdemo") {
        state.error.value =
          "Demo code cannot generate auth tokens. For full access, please use a valid Patreon auth code.";
        state.errorType.value = "auth";
        setTimeout(() => {
          if (
            state.error.value ===
            "Demo code cannot generate auth tokens. For full access, please use a valid Patreon auth code."
          ) {
            state.error.value = null;
          }
        }, 3000);
        return;
      }

      setGenerateStatus("loading");
      state.error.value = null;

      const api =
        state.selectedApi.value === "backup"
          ? "https://backup.xbatch.online"
          : "https://api.xbatch.online";

      const url = `${api}/token/${state.patreonAuth.value}`;

      try {
        const response = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            timeout: 60000,
            onload: (res) => {
              if (res.status === 200) {
                try {
                  const data = JSON.parse(res.responseText);
                  resolve(data);
                } catch (parseError) {
                  reject(new Error("Invalid response format"));
                }
              } else if (res.status === 401) {
                reject(new Error("Invalid Patreon Auth"));
              } else if (res.status === 403) {
                reject(new Error("Access forbidden"));
              } else if (res.status === 429) {
                reject(new Error("Rate limit exceeded"));
              } else {
                reject(new Error(`API error: ${res.status}`));
              }
            },
            onerror: () => {
              reject(new Error("Network error"));
            },
            ontimeout: () => {
              reject(new Error("Request timeout"));
            },
          });
        });

        if (response.auth_token) {
          state.authToken.value = response.auth_token;
          await saveSettings();
          setGenerateStatus("success");

          setTimeout(() => {
            setGenerateStatus("idle");
          }, 1000);
        } else {
          throw new Error("No auth token in response");
        }
      } catch (error) {
        console.error("Failed to generate auth token:", error);
        state.error.value = error.message || "Failed to generate auth token";
        state.errorType.value = "auth";
        setGenerateStatus("error");

        setTimeout(() => {
          if (state.error.value) {
            state.error.value = null;
          }
        }, 2000);

        setTimeout(() => {
          setGenerateStatus("idle");
        }, 2000);
      }
    };

    return h(
      "div",
      null,
      state.error.value &&
        state.errorType.value === "auth" &&
        h(
          "div",
          { className: "tmd-error auth" },
          h("span", {
            className: "tmd-error-icon",
            dangerouslySetInnerHTML: { __html: ICONS.triangleAlert },
          }),
          h("span", null, state.error.value)
        ),

      h(
        "div",
        { className: "tmd-input-group" },
        h(
          "div",
          {
            style:
              "display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;",
          },
          h(
            "label",
            { className: "tmd-label", style: "margin-bottom: 0;" },
            h("span", {
              dangerouslySetInnerHTML: {
                __html: state.isVerified.value
                  ? ICONS.patreonAuthUnlockIcon
                  : ICONS.patreonAuthIcon,
              },
            }),
            "Patreon Auth"
          ),
          h(
            "button",
            {
              className: `tmd-button tmd-button-outline`,
              onClick: verifyPatreonAuth,
              disabled:
                verifyStatus === "loading" ||
                !state.patreonAuth.value ||
                state.patreonAuth.value.trim() === "",
              style: `padding: 6px 12px; font-size: 13px; ${
                verifyStatus === "success"
                  ? "background: hsl(142.1deg 76.2% 36.3% / 0.15); border-color: hsl(142.1deg 76.2% 36.3%); color: hsl(142.1deg 76.2% 36.3%);"
                  : verifyStatus === "error"
                  ? "background: hsl(0deg 84.2% 60.2% / 0.15); border-color: hsl(0deg 84.2% 60.2%); color: hsl(0deg 84.2% 60.2%);"
                  : !state.patreonAuth.value ||
                    state.patreonAuth.value.trim() === ""
                  ? "opacity: 0.5; cursor: not-allowed;"
                  : ""
              }`,
              title:
                state.patreonAuth.value && state.patreonAuth.value.trim() !== ""
                  ? "Verify your Patreon Auth"
                  : "Enter Patreon Auth first",
            },
            verifyStatus === "loading"
              ? h("span", {
                  className: "tmd-spinner",
                  dangerouslySetInnerHTML: { __html: ICONS.spinner },
                })
              : verifyStatus === "success"
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.checkCircle.replace(
                      'stroke="currentColor"',
                      'stroke="hsl(142.1deg 76.2% 36.3%)"'
                    ),
                  },
                })
              : verifyStatus === "error"
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.shieldX.replace(
                      'stroke="currentColor"',
                      'stroke="hsl(0deg 84.2% 60.2%)"'
                    ),
                  },
                })
              : h("span", {
                  dangerouslySetInnerHTML: { __html: ICONS.shieldCheck },
                }),
            verifyStatus === "loading"
              ? "Verifying..."
              : verifyStatus === "success"
              ? "Verified"
              : verifyStatus === "error"
              ? "Failed"
              : "Verify"
          )
        ),
        h(
          "div",
          { className: "tmd-input-wrapper" },
          h("input", {
            type: showPatreonAuth ? "text" : "password",
            className: "tmd-input",
            value: state.patreonAuth.value,
            onInput: handlePatreonAuthChange,
            placeholder: "Enter your Patreon auth",
          }),
          h("div", {
            className: "tmd-input-toggle",
            onClick: () => setShowPatreonAuth(!showPatreonAuth),
            dangerouslySetInnerHTML: {
              __html: showPatreonAuth ? ICONS.eyeOff : ICONS.eye,
            },
          })
        )
      ),

      h(
        "div",
        { className: "tmd-input-group" },
        h(
          "div",
          {
            style:
              "display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;",
          },
          h(
            "label",
            { className: "tmd-label", style: "margin-bottom: 0;" },
            h("span", {
              dangerouslySetInnerHTML: { __html: ICONS.authTokenIcon },
            }),
            "Auth Token"
          ),
          h(
            "button",
            {
              className: `tmd-button tmd-button-outline`,
              onClick: generateAuthToken,
              disabled:
                generateStatus === "loading" ||
                !state.patreonAuth.value ||
                state.patreonAuth.value.trim() === "" ||
                !state.isVerified.value ||
                state.patreonAuth.value === "xbatchdemo",
              style: `padding: 6px 12px; font-size: 13px; ${
                generateStatus === "success"
                  ? "background: hsl(142.1deg 76.2% 36.3% / 0.15); border-color: hsl(142.1deg 76.2% 36.3%); color: hsl(142.1deg 76.2% 36.3%);"
                  : generateStatus === "error"
                  ? "background: hsl(0deg 84.2% 60.2% / 0.15); border-color: hsl(0deg 84.2% 60.2%); color: hsl(0deg 84.2% 60.2%);"
                  : !state.patreonAuth.value ||
                    state.patreonAuth.value.trim() === "" ||
                    !state.isVerified.value ||
                    state.patreonAuth.value === "xbatchdemo"
                  ? "opacity: 0.5; cursor: not-allowed;"
                  : ""
              }`,
              title:
                !state.patreonAuth.value ||
                state.patreonAuth.value.trim() === ""
                  ? "Enter Patreon Auth first"
                  : state.patreonAuth.value === "xbatchdemo"
                  ? "Demo code cannot generate auth tokens"
                  : !state.isVerified.value
                  ? "Please verify Patreon Auth first"
                  : "Generate new auth token",
            },
            generateStatus === "loading"
              ? h("span", {
                  className: "tmd-spinner",
                  dangerouslySetInnerHTML: { __html: ICONS.spinner },
                })
              : generateStatus === "success"
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.checkCircle.replace(
                      'stroke="currentColor"',
                      'stroke="hsl(142.1deg 76.2% 36.3%)"'
                    ),
                  },
                })
              : generateStatus === "error"
              ? h("span", {
                  dangerouslySetInnerHTML: {
                    __html: ICONS.circleX.replace(
                      'stroke="currentColor"',
                      'stroke="hsl(0deg 84.2% 60.2%)"'
                    ),
                  },
                })
              : h("span", {
                  dangerouslySetInnerHTML: { __html: ICONS.rotateKey },
                }),
            generateStatus === "loading"
              ? "Generating..."
              : generateStatus === "success"
              ? "Generated"
              : generateStatus === "error"
              ? "Failed"
              : "Generate"
          )
        ),
        h(
          "div",
          { className: "tmd-input-wrapper" },
          h("input", {
            type: showAuthToken ? "text" : "password",
            className: "tmd-input",
            value: state.authToken.value,
            onInput: handleAuthTokenChange,
            placeholder: "Enter your auth token",
          }),
          h("div", {
            className: "tmd-input-toggle",
            onClick: () => setShowAuthToken(!showAuthToken),
            dangerouslySetInnerHTML: {
              __html: showAuthToken ? ICONS.eyeOff : ICONS.eye,
            },
          })
        )
      ),

      h(
        "div",
        { className: "tmd-success" },
        h("span", {
          className: "tmd-success-icon",
          dangerouslySetInnerHTML: { __html: ICONS.notepadText },
        }),
        h(
          "div",
          null,
          h(
            "div",
            { style: "margin-bottom: 8px;" },
            "• Use code ",
            h(
              "code",
              {
                style:
                  "background: hsl(142.1deg 70% 29% / 0.2); color: hsl(142.1deg 76.2% 36.3%); padding: 2px 6px; border-radius: 4px;",
              },
              "xbatchdemo"
            ),
            " for Patreon Auth, then click ",
            h(
              "strong",
              { style: "color: hsl(142.1deg 76.2% 36.3%);" },
              "Verify"
            ),
            " to unlock demo access. Visit ",
            h(
              "a",
              {
                href: "https://x.com/xbatchdemo",
                target: "_blank",
                rel: "noopener noreferrer",
                style:
                  "color: hsl(204.17deg 87.55% 52.75%); text-decoration: none;",
                onMouseEnter: (e) =>
                  (e.target.style.textDecoration = "underline"),
                onMouseLeave: (e) => (e.target.style.textDecoration = "none"),
              },
              "@xbatchdemo"
            ),
            " to test."
          ),
          h(
            "div",
            { style: "margin-bottom: 8px;" },
            "• Need help getting Auth Token? ",
            h(
              "a",
              {
                href: "https://www.patreon.com/posts/how-to-obtain-127206894",
                target: "_blank",
                rel: "noopener noreferrer",
                style:
                  "color: hsl(204.17deg 87.55% 52.75%); text-decoration: none;",
                onMouseEnter: (e) =>
                  (e.target.style.textDecoration = "underline"),
                onMouseLeave: (e) => (e.target.style.textDecoration = "none"),
              },
              "View the guide here."
            )
          ),
          h(
            "div",
            { style: "margin-bottom: 8px;" },
            h(
              "a",
              {
                href: "https://www.patreon.com/exyezed/membership",
                target: "_blank",
                rel: "noopener noreferrer",
                style:
                  "color: hsl(204.17deg 87.55% 52.75%); text-decoration: none;",
                onMouseEnter: (e) =>
                  (e.target.style.textDecoration = "underline"),
                onMouseLeave: (e) => (e.target.style.textDecoration = "none"),
              },
              "• Subscribe now"
            ),
            " to receive your Patreon auth code and start downloading with ease!"
          ),
          h(
            "div",
            null,
            "• To report bugs or request features, please contact us at ",
            h(
              "a",
              {
                href: "mailto:[email protected]",
                style:
                  "color: hsl(204.17deg 87.55% 52.75%); text-decoration: none;",
                onMouseEnter: (e) =>
                  (e.target.style.textDecoration = "underline"),
                onMouseLeave: (e) => (e.target.style.textDecoration = "none"),
              },
              "[email protected]"
            )
          )
        )
      )
    );
  }

  function createIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("width", "18");
    svg.setAttribute("height", "18");
    svg.setAttribute("viewBox", "0 0 24 24");
    svg.setAttribute("fill", "none");
    svg.setAttribute("stroke", "currentColor");
    svg.setAttribute("stroke-width", "2");
    svg.style.cursor = "pointer";
    svg.style.transition = "color 0.2s";

    const paths = [
      "M12 15V3",
      "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
      "m7 10 5 5 5-5",
    ];
    paths.forEach((d) => {
      const path = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
      );
      path.setAttribute("d", d);
      svg.appendChild(path);
    });

    return svg;
  }

  function insertIcons() {
    document.querySelectorAll('[data-testid="UserName"]').forEach((div) => {
      if (!div.querySelector(".dl-icon")) {
        const target =
          div.querySelector('[aria-label*="verified"]')?.closest("button")
            ?.parentElement ||
          div.querySelector(".css-1jxf684")?.closest("span");
        if (target) {
          const icon = createIcon();
          const wrapper = document.createElement("div");
          wrapper.className = "dl-icon";
          wrapper.style.cssText = `
                        display:inline-flex;
                        margin-left:6px;
                        padding:4px;
                        background:hsl(240 3.7% 15.9%);
                        border-radius:4px;
                        transition:background 0.2s;
                    `;
          wrapper.appendChild(icon);
          wrapper.onmouseenter = () => {
            icon.style.color = "hsl(204.17deg 87.55% 52.75%)";
            wrapper.style.background = "hsl(240 3.7% 20%)";
          };
          wrapper.onmouseleave = () => {
            icon.style.color = "";
            wrapper.style.background = "hsl(240 3.7% 15.9%)";
          };
          wrapper.onclick = (e) => {
            e.stopPropagation();
            e.preventDefault();

            let username = null;

            const urlMatch = window.location.pathname.match(
              /^\/([^\/?]+)(?:\/|\?|$)/
            );
            if (
              urlMatch &&
              ![
                "home",
                "explore",
                "notifications",
                "messages",
                "bookmarks",
                "lists",
                "communities",
                "premium",
                "verified-orgs-signup",
                "settings",
                "search",
                "compose",
                "i",
              ].includes(urlMatch[1])
            ) {
              username = urlMatch[1];
            }

            if (!username) {
              const profileLink = div.closest('a[href*="/"]');
              if (profileLink) {
                const usernameMatch = profileLink.href.match(
                  /(?:twitter\.com|x\.com)\/([^\/\?]+)/
                );
                if (
                  usernameMatch &&
                  ![
                    "home",
                    "explore",
                    "notifications",
                    "messages",
                    "bookmarks",
                    "lists",
                    "communities",
                    "premium",
                    "verified-orgs-signup",
                    "settings",
                    "search",
                    "compose",
                    "i",
                  ].includes(usernameMatch[1])
                ) {
                  username = usernameMatch[1];
                }
              }
            }

            if (username) {
              state.currentUsername.value = username;
            }

            state.isModalOpen.value = true;
          };

          target.parentNode.insertBefore(wrapper, target.nextSibling);
        }
      }
    });
  }

  async function init() {
    await loadSettings();

    const modalContainer = document.createElement("div");
    modalContainer.id = "tmd-modal-root";
    document.body.appendChild(modalContainer);

    const renderModal = () => {
      render(h(Modal), modalContainer);
    };

    effect(() => {
      renderModal();
    });

    const floatingButton = document.createElement("div");
    floatingButton.id = "tmd-floating-button";
    floatingButton.style.cssText = `
            position: fixed;
            top: 50%;
            left: -20px;
            width: 48px;
            height: 48px;
            cursor: pointer;
            z-index: 9998;
            transition: all 0.3s ease;
            opacity: 0.5;
        `;

    floatingButton.innerHTML = ICONS.twitter
      .replace('width="16"', 'width="48"')
      .replace('height="16"', 'height="48"')
      .replace('stroke="currentColor"', 'stroke="hsl(204.17deg 87.55% 52.75%)"')
      .replace(
        "<svg",
        '<svg style="transform-origin: bottom center; transition: all 0.3s ease;"'
      );

    floatingButton.onmouseenter = () => {
      floatingButton.style.transform = "translateX(25px) rotate(20deg)";
      floatingButton.style.opacity = "0.9";
      const svg = floatingButton.querySelector("svg");
      if (svg) {
        svg.style.transform = "scale(1.1)";
      }
    };

    floatingButton.onmouseleave = () => {
      floatingButton.style.transform = "translateX(0) rotate(0)";
      floatingButton.style.opacity = "0.5";
      const svg = floatingButton.querySelector("svg");
      if (svg) {
        svg.style.transform = "scale(1)";
      }
    };

    floatingButton.onclick = (e) => {
      e.stopPropagation();
      e.preventDefault();

      let username = null;
      const urlMatch = window.location.pathname.match(
        /^\/([^\/?]+)(?:\/|\?|$)/
      );
      if (
        urlMatch &&
        ![
          "home",
          "explore",
          "notifications",
          "messages",
          "bookmarks",
          "lists",
          "communities",
          "premium",
          "verified-orgs-signup",
          "settings",
          "search",
          "compose",
          "i",
        ].includes(urlMatch[1])
      ) {
        username = urlMatch[1];
      }

      if (username) {
        state.currentUsername.value = username;
      }

      state.isModalOpen.value = true;
    };

    document.body.appendChild(floatingButton);

    insertIcons();
    const observer = new MutationObserver(insertIcons);
    observer.observe(document.body, { childList: true, subtree: true });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址