Database open request error: [object DOMException]
 Error getting theme setting: TypeError: Cannot read properties of undefined (reading 'transaction')
 Error loading novels: TypeError: Cannot read properties of undefined (reading 'transaction')
 Error initializing application: [object DOMException]

'use strict';
(async function() {

  /**
   * IndexedDB Utilities
   */
  let db;
  const DB_VERSION = 2;

  const openDatabase = () => {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('MonochromeNovelsDB', DB_VERSION);

      request.onupgradeneeded = function(event) {
        const db = request.result;
        if (event.oldVersion < 1) {
          // Initial creation of object stores
          const novelsStore = db.createObjectStore('novels', { keyPath: 'id', autoIncrement: true });
          novelsStore.createIndex('name', 'name', { unique: true });
          novelsStore.createIndex('nameLower', 'nameLower', { unique: false });
          novelsStore.createIndex('coverUrl', 'coverUrl', { unique: true });
          novelsStore.createIndex('pdfUrl', 'pdfUrl', { unique: true });
          novelsStore.createIndex('timestamp', 'timestamp', { unique: false });

          const settingsStore = db.createObjectStore('settings', { keyPath: 'key' });

        } else if (event.oldVersion < 2) {
          // Upgrade logic for version 2
          const transaction = request.transaction;
          const novelsStore = transaction.objectStore('novels');
          // Add a new index or make changes as needed
          if (!novelsStore.indexNames.contains('nameLower')) {
            novelsStore.createIndex('nameLower', 'nameLower', { unique: false });
          }
        }
        // Future upgrades can be handled here
      };

      request.onsuccess = function() {
        db = request.result;
        resolve(db);
      };

      request.onerror = function(event) {
        console.error('Database open request error:', event.target.error);
        reject(event.target.error);
      };
    });
  };

  const countNovels = () => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readonly');
      const store = transaction.objectStore('novels');
      const countRequest = store.count();
      countRequest.onsuccess = function() {
        resolve(countRequest.result);
      };
      countRequest.onerror = function() {
        reject(countRequest.error);
      };
    });
  };

  const getAllNovels = () => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readonly');
      const store = transaction.objectStore('novels');
      const getAllRequest = store.getAll();
      getAllRequest.onsuccess = function() {
        resolve(getAllRequest.result);
      };
      getAllRequest.onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  const getNovels = (offset, limit) => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readonly');
      const store = transaction.objectStore('novels');
      const index = store.index('name');

      const result = [];
      let advanced = false;
      const cursorRequest = index.openCursor();

      cursorRequest.onsuccess = function(event) {
        const cursor = event.target.result;
        if (cursor) {
          if (!advanced && offset > 0) {
            advanced = true;
            cursor.advance(offset);
            return;
          }
          result.push(cursor.value);
          if (result.length < limit) {
            cursor.continue();
          } else {
            resolve(result);
          }
        } else {
          resolve(result);
        }
      };
      cursorRequest.onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  const getNovelsByNameIncludes = (searchTerm, offset, limit) => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readonly');
      const store = transaction.objectStore('novels');
      const index = store.index('nameLower');

      const lowerSearchTerm = searchTerm.toLowerCase();
      const result = [];
      index.openCursor().onsuccess = function(event) {
        const cursor = event.target.result;
        if (cursor) {
          if (cursor.value.nameLower.includes(lowerSearchTerm)) {
            if (offset > 0) {
              offset--;
            } else {
              result.push(cursor.value);
            }
          }
          if (result.length < limit) {
            cursor.continue();
          } else {
            resolve(result);
          }
        } else {
          resolve(result);
        }
      };
      index.openCursor().onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  const bulkAddNovels = (novels) => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readwrite');
      const store = transaction.objectStore('novels');

      const promises = novels.map((novel) => {
        return new Promise((res, rej) => {
          const request = store.put(novel);
          request.onsuccess = function() {
            res();
          };
          request.onerror = function(event) {
            console.error('Failed to add novel:', novel.name, event.target.error);
            res(); // Resolve even on error to continue
          };
        });
      });

      Promise.allSettled(promises).then(() => {
        resolve();
      }).catch((error) => {
        reject(error);
      });
    });
  };

  const clearNovels = () => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readwrite');
      const store = transaction.objectStore('novels');
      const clearRequest = store.clear();
      clearRequest.onsuccess = function() {
        resolve();
      };
      clearRequest.onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  const getSetting = (key) => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('settings', 'readonly');
      const store = transaction.objectStore('settings');
      const getRequest = store.get(key);
      getRequest.onsuccess = function() {
        resolve(getRequest.result ? getRequest.result.value : null);
      };
      getRequest.onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  const putSetting = (key, value) => {
    return new Promise((resolve, reject) => {
      const transaction = db.transaction('settings', 'readwrite');
      const store = transaction.objectStore('settings');
      const putRequest = store.put({ key: key, value: value });
      putRequest.onsuccess = function() {
        resolve();
      };
      putRequest.onerror = function(event) {
        reject(event.target.error);
      };
    });
  };

  /**
   * Constants and Variables
   */
  const PAGE_SIZE = 20;
  let currentPage = 0;
  let isLoading = false;
  let hasMore = true;
  let currentSearchTerm = '';

  const PLACEHOLDER_IMAGE = 'https://via.placeholder.com/500x700?text=No+Image';

  /**
   * Debounce Function to Limit Function Execution Rate
   * @param {Function} func
   * @param {number} delay
   * @returns {Function}
   */
  const debounce = (func, delay) => {
    let debounceTimer;
    return function(...args) {
      const context = this;
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => func.apply(context, args), delay);
    }
  };

  /**
   * Utility: Delay Function
   * @param {number} ms
   * @returns {Promise}
   */
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

  /**
   * Utility: Retry Operation with Exponential Backoff (3 Attempts)
   * @param {Function} operation
   * @param {number} attempts
   * @param {number} delayDuration
   * @returns {Promise}
   */
  const retryOperation = async (operation, attempts = 3, delayDuration = 1500) => {
    for (let i = 0; i < attempts; i++) {
      try {
        return await operation();
      } catch (error) {
        if (i === attempts - 1) throw error;
        await delay(delayDuration * Math.pow(2, i)); // Exponential backoff
      }
    }
  };

  /**
   * Initialize Intersection Observer for Infinite Scroll using Sentinel Element
   */
  const setupSentinelObserver = () => {
    const sentinel = document.getElementById('sentinel');
    if (!('IntersectionObserver' in window)) {
      // Fallback if IntersectionObserver is not supported
      window.addEventListener('scroll', onScrollLoadNextPage);
      return;
    }

    const sentinelObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting && hasMore && !isLoading) {
          loadNextPageWrapper();
        }
      });
    }, {
      rootMargin: '100px',
      threshold: 0
    });
    sentinelObserver.observe(sentinel);
  };

  // Fallback function for scroll event
  let scrollTimeout;
  const onScrollLoadNextPage = () => {
    if (scrollTimeout) {
      clearTimeout(scrollTimeout);
    }
    scrollTimeout = setTimeout(() => {
      if (window.scrollY + window.innerHeight >= document.body.scrollHeight - 100 && hasMore && !isLoading) {
        loadNextPageWrapper();
      }
    }, 200);
  };

  /**
   * Load Next Page Wrapper to prevent concurrent invocations
   */
  let loadNextPageQueue = Promise.resolve();
  const loadNextPageWrapper = () => {
    loadNextPageQueue = loadNextPageQueue.then(() => loadNextPage());
  };

  /**
   * Load Next Page of Novels from IndexedDB
   */
  const loadNextPage = async () => {
    if (isLoading) return; // Prevent multiple simultaneous loads
    isLoading = true;
    showLoader();

    try {
      let novels;

      if (currentSearchTerm === '') {
        novels = await getNovels(currentPage * PAGE_SIZE, PAGE_SIZE);
      } else {
        novels = await getNovelsByNameIncludes(currentSearchTerm, currentPage * PAGE_SIZE, PAGE_SIZE);
      }

      if (novels.length < PAGE_SIZE) {
        hasMore = false; // No more novels to load
      }

      if (novels.length > 0) {
        appendNovelsToDisplay(novels);
        currentPage++;
      } else {
        if (currentPage === 0) {
          showTemporaryError('No novels found for the given search term.', 5000);
        }
      }

      // Edge case: Load more if content doesn't fill viewport
      if (document.body.scrollHeight <= window.innerHeight && hasMore && !isLoading) {
        loadNextPageWrapper();
      }

    } catch (error) {
      console.error('Error loading novels:', error);
      showTemporaryError('An error occurred while loading novels.', 5000);
    } finally {
      hideLoader();
      isLoading = false;
    }
  };

  /**
   * Show Loader
   */
  const showLoader = () => {
    document.getElementById('loader').classList.add('visible');
  };

  /**
   * Hide Loader
   */
  const hideLoader = () => {
    document.getElementById('loader').classList.remove('visible');
  };

  /**
   * Display Error Message Temporarily
   * @param {string} message
   * @param {number} duration - Duration in milliseconds
   */
  const showTemporaryError = (message, duration = 5000) => {
    const content = document.getElementById('content');
    // Remove existing error messages
    const existingErrors = content.querySelectorAll('.error-message');
    existingErrors.forEach(error => error.remove());

    const errorDiv = document.createElement('div');
    errorDiv.className = 'error-message';
    errorDiv.setAttribute('role', 'alert');
    errorDiv.textContent = message;

    content.prepend(errorDiv);

    // Fade out after specified duration
    setTimeout(() => {
      errorDiv.classList.add('fade-out');
      // Remove from DOM after fade-out transition
      setTimeout(() => {
        errorDiv.remove();
      }, 500);
    }, duration);
  };

  /**
   * Append Novels to Display
   * @param {Array} novels
   */
  const appendNovelsToDisplay = novels => {
    const content = document.getElementById('content');
    const fragment = document.createDocumentFragment();

    novels.forEach(novel => {
      const card = createNovelCard(novel);
      fragment.appendChild(card);
    });

    content.appendChild(fragment);
    if (window.AOS) {
      AOS.refresh();
    }

    // Lazy load images
    lazyLoadImages();
  };

  /**
   * Create Novel Card Element
   * @param {Object} novel
   * @returns {HTMLElement}
   */
  const createNovelCard = (novel) => {
    const card = document.createElement('div');
    card.className = 'card';
    card.setAttribute('tabindex', '0');
    card.setAttribute('role', 'button');
    card.setAttribute('aria-labelledby', `novel-title-${encodeURIComponent(novel.name)}`);
    card.setAttribute('data-aos', 'fade-up');

    const imageContainer = document.createElement('div');
    imageContainer.className = 'card__image-container';

    const image = document.createElement('img');
    image.className = 'card__image';
    image.dataset.src = novel.coverUrl || PLACEHOLDER_IMAGE;
    image.alt = `Cover of ${novel.name}`;
    image.loading = 'lazy';
    image.onerror = function() {
      this.src = PLACEHOLDER_IMAGE;
    };

    imageContainer.appendChild(image);
    card.appendChild(imageContainer);

    const content = document.createElement('div');
    content.className = 'card__content';

    const title = document.createElement('h2');
    title.id = `novel-title-${encodeURIComponent(novel.name)}`;
    title.className = 'card__title';
    title.textContent = novel.name;

    content.appendChild(title);
    card.appendChild(content);

    // Event Listener to Open Modal
    const openModalEvent = (e) => {
      if (e.type === 'click' || (e.type === 'keypress' && (e.key === 'Enter' || e.key === ' '))) {
        e.preventDefault();
        showModal(novel);
      }
    };

    card.addEventListener('click', openModalEvent);
    card.addEventListener('keypress', openModalEvent);

    return card;
  };

  /**
   * Lazy Load Images using Intersection Observer
   */
  const lazyLoadImages = () => {
    const images = document.querySelectorAll('img[data-src]');
    const config = {
      rootMargin: '50px 0px',
      threshold: 0.01
    };

    let observer;
    if ('IntersectionObserver' in window) {
      observer = new IntersectionObserver((entries, self) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            preloadImage(entry.target);
            self.unobserve(entry.target);
          }
        });
      }, config);

      images.forEach(image => {
        observer.observe(image);
      });
    } else {
      // Fallback for browsers without IntersectionObserver support
      images.forEach(image => {
        preloadImage(image);
      });
    }
  };

  const preloadImage = (img) => {
    img.src = img.dataset.src;
    img.onload = () => {
      img.removeAttribute('data-src');
    };
  };

  /**
   * Utility: Escape HTML to Prevent XSS
   * @param {string} str
   * @returns {string}
   */
  const escapeHTML = (str) => {
    if (!str) return '';
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
  };

  /**
   * Modal Functionality
   */
  const modal = document.getElementById('modal');
  const modalTitle = document.getElementById('modal-title');
  const modalLink = document.getElementById('modal-link');
  const closeBtn = document.querySelector('.close');

  /**
   * Show Modal with Novel Details
   * @param {Object} novel
   */
  const showModal = (novel) => {
    modalTitle.textContent = novel.name;
    modalLink.setAttribute('href', novel.pdfUrl);
    modal.classList.add('show');
    modal.setAttribute('aria-hidden', 'false');
    document.body.style.overflow = 'hidden'; // Prevent background scrolling
    closeBtn.focus(); // Focus on close button for accessibility

    // Trap focus inside modal
    document.addEventListener('focus', trapFocus, true);
  };

  /**
   * Close Modal
   */
  const closeModal = () => {
    modal.classList.remove('show');
    modal.setAttribute('aria-hidden', 'true');
    document.body.style.overflow = 'auto';

    // Remove focus trap
    document.removeEventListener('focus', trapFocus, true);
  };

  const trapFocus = (event) => {
    if (!modal.contains(event.target)) {
      event.stopPropagation();
      closeBtn.focus();
    }
  };

  closeBtn.addEventListener('click', closeModal);

  window.addEventListener('click', (event) => {
    if (event.target === modal) {
      closeModal();
    }
  });

  window.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && modal.classList.contains('show')) {
      closeModal();
    }
  });

  /**
   * Search Functionality
   */
  const searchInput = document.getElementById('search-input');

  const handleSearch = debounce(async () => {
    currentSearchTerm = searchInput.value.trim();
    currentPage = 0;
    hasMore = true;

    const content = document.getElementById('content');
    content.innerHTML = ''; // Clear existing content

    // Reload the first page with the new search term
    await loadNextPage();
  }, 300); // 300ms debounce delay

  searchInput.addEventListener('input', handleSearch);

  /**
   * Refresh Data Functionality
   */
  const refreshButton = document.getElementById('refresh-button');
  refreshButton.addEventListener('click', async () => {
    showConfirmationModal('Are you sure you want to refresh the data? This may take a few moments.', async () => {
      isLoading = true;
      showLoader();
      hasMore = true;
      currentPage = 0;
      refreshButton.disabled = true;

      try {
        // Clear existing data
        await clearNovels();

        // Fetch and parse fresh data
        await fetchAndStoreNovels();

        // Reload the first page of novels
        document.getElementById('content').innerHTML = ''; // Clear existing content

        await loadNextPage();

        // Show success toast
        showToast('Data refreshed successfully!', 'success');
      } catch (error) {
        console.error('Error refreshing data:', error);
        showTemporaryError('Failed to refresh data. Please try again later.', 5000);
        // Show error toast
        showToast('Failed to refresh data.', 'error');
      } finally {
        hideLoader();
        isLoading = false;
        refreshButton.disabled = false;
      }
    });
  });

  /**
   * Show Confirmation Modal
   * @param {string} message
   * @param {Function} onConfirm
   */
  const showConfirmationModal = (message, onConfirm) => {
    const confirmationModal = document.createElement('div');
    confirmationModal.className = 'modal';
    confirmationModal.setAttribute('role', 'dialog');
    confirmationModal.setAttribute('aria-modal', 'true');

    const modalContent = document.createElement('div');
    modalContent.className = 'modal-content';

    const modalMessage = document.createElement('p');
    modalMessage.textContent = message;

    const modalButtons = document.createElement('div');
    modalButtons.className = 'modal-buttons';

    const confirmButton = document.createElement('button');
    confirmButton.textContent = 'Confirm';
    confirmButton.className = 'button confirm-button';

    const cancelButton = document.createElement('button');
    cancelButton.textContent = 'Cancel';
    cancelButton.className = 'button cancel-button';

    modalButtons.appendChild(confirmButton);
    modalButtons.appendChild(cancelButton);

    modalContent.appendChild(modalMessage);
    modalContent.appendChild(modalButtons);
    confirmationModal.appendChild(modalContent);
    document.body.appendChild(confirmationModal);
    document.body.style.overflow = 'hidden';

    confirmButton.focus();

    const closeConfirmationModal = () => {
      confirmationModal.remove();
      document.body.style.overflow = 'auto';
    };

    confirmButton.addEventListener('click', () => {
      closeConfirmationModal();
      onConfirm();
    });

    cancelButton.addEventListener('click', closeConfirmationModal);

    window.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        closeConfirmationModal();
      }
    });

    window.addEventListener('click', (event) => {
      if (event.target === confirmationModal) {
        closeConfirmationModal();
      }
    });
  };

  /**
   * Initialize Application
   */
  const initializeApp = async () => {
    try {
      // Open the database
      await openDatabase();

      // Check if any novels are already stored in IndexedDB
      const count = await countNovels();
      if (count === 0) {
        // Fetch and store data from the external website
        await fetchAndStoreNovels();
      } else {
        // Validate existing data
        const novels = await getAllNovels();
        if (!validateNovels(novels)) {
          await clearNovels();
          await fetchAndStoreNovels();
        }
      }

      // Load the first page of novels
      await loadNextPage();

      // Edge case: Load more if content doesn't fill viewport
      if (document.body.scrollHeight <= window.innerHeight && hasMore && !isLoading) {
        loadNextPageWrapper();
      }
    } catch (error) {
      console.error('Error initializing application:', error);
      showTemporaryError('Failed to load novels. Please try refreshing the page.', 5000);
      // Show error toast
      showToast('Failed to load novels.', 'error');
    }
  };

  /**
   * Fetch and Store Novels
   */
  const fetchAndStoreNovels = async () => {
    try {
      const html = await retryOperation(() => fetchHTML('https://jnovels.com/top-light-novels-to-read/'));
      let novels = parseHTML(html);

      if (novels.length === 0) {
        throw new Error('No novels found on the target page.');
      }

      showProgress();
      updateProgress(0);

      // Bulk upsert novels to IndexedDB
      const novelsToAdd = novels.map((novel, index) => {
        const escapedName = escapeHTML(novel.name);
        const escapedCoverUrl = escapeHTML(novel.coverUrl);
        const escapedPdfUrl = escapeHTML(novel.pdfUrl);
        return {
          id: index,
          name: escapedName,
          nameLower: escapedName.toLowerCase(),
          coverUrl: escapedCoverUrl,
          pdfUrl: escapedPdfUrl,
          timestamp: Date.now()
        };
      });

      await bulkAddNovelsWithProgress(novelsToAdd, (progress) => {
        updateProgress(progress);
      });

      console.log('Novels fetched and stored successfully:', novelsToAdd.length);

      // Show success toast
      showToast('Novels fetched and stored successfully.', 'success');

    } catch (error) {
      console.error('Error fetching or storing novels:', error);
      showTemporaryError('Failed to fetch and store novels. Please try again later.', 5000);
      // Show error toast
      showToast('Failed to fetch and store novels.', 'error');
    } finally {
      hideProgress();
    }
  };

  const bulkAddNovelsWithProgress = async (novels, progressCallback) => {
    const total = novels.length;
    let completed = 0;

    return new Promise((resolve, reject) => {
      const transaction = db.transaction('novels', 'readwrite');
      const store = transaction.objectStore('novels');

      transaction.oncomplete = function() {
        resolve();
      };

      transaction.onerror = function(event) {
        reject(event.target.error);
      };

      novels.forEach((novel) => {
        const request = store.put(novel);
        request.onsuccess = function() {
          completed++;
          const progress = (completed / total) * 100;
          progressCallback(progress);
        };
        request.onerror = function(event) {
          console.error('Failed to add novel:', novel.name, event.target.error);
          completed++;
          const progress = (completed / total) * 100;
          progressCallback(progress);
        };
      });
    });
  };

  /**
   * Fetch HTML Content from the URL
   * @param {string} url
   * @returns {Promise<string>}
   */
  const fetchHTML = async (url) => {
    try {
      const response = await fetch(url, { cache: 'no-cache' });

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }

      const htmlContent = await response.text();

      if (!htmlContent || typeof htmlContent !== 'string') {
        throw new Error('Fetched content is invalid or empty.');
      }

      return htmlContent;
    } catch (error) {
      console.error('Error fetching HTML:', error);
      throw error; // Re-throw to be handled by caller
    }
  };

  /**
   * Parse Fetched HTML to Extract Novel Data
   * @param {string} html
   * @returns {Array<Object>}
   */
  const parseHTML = (html) => {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const novels = [];
    const uniqueNovels = new Set();

    // Select all h3 tags as they contain the novel names and links
    const h3Elements = doc.querySelectorAll('h3');

    h3Elements.forEach((h3Element) => {
      try {
        const aTag = h3Element.querySelector('a');
        const spanTag = h3Element.querySelector('span');

        if (!aTag || !spanTag) return; // Skip if either tag is missing

        const name = spanTag.textContent.trim();
        const pdfLink = aTag.getAttribute('href').trim();

        // The cover image is inside the following p tag
        let pElement = h3Element.nextElementSibling;
        while (pElement && pElement.tagName.toLowerCase() !== 'p') {
          pElement = pElement.nextElementSibling;
        }
        if (!pElement) return; // Skip if p tag is missing

        const imgTag = pElement.querySelector('img');
        if (!imgTag) return; // Skip if img tag is missing

        let coverUrl = imgTag.getAttribute('src') || imgTag.getAttribute('data-src');
        if (!coverUrl) return; // Skip if cover URL is missing

        // Handle different URL formats
        if (coverUrl.startsWith('//')) {
          coverUrl = `https:${coverUrl}`;
        } else if (coverUrl.startsWith('/')) {
          coverUrl = `https://jnovels.com${coverUrl}`;
        } else if (!coverUrl.startsWith('http')) {
          // Handle relative URLs without leading '/'
          coverUrl = `https://jnovels.com/${coverUrl}`;
        }

        // Validate extracted data
        if (name && pdfLink && coverUrl) {
          const novelKey = `${name}-${coverUrl}-${pdfLink}`;
          if (!uniqueNovels.has(novelKey)) {
            uniqueNovels.add(novelKey);
            novels.push({
              name,
              coverUrl,
              pdfUrl: pdfLink
            });
          }
        } else {
          console.warn('Incomplete novel data found and skipped:', { name, pdfLink, coverUrl });
        }
      } catch (error) {
        // Skip any malformed entries
        console.error('Error parsing a novel entry:', error);
      }
    });

    return novels;
  };

  /**
   * Update Progress Bar
   * @param {number} percentage
   */
  const updateProgress = (percentage) => {
    document.getElementById('progress-bar').style.width = `${percentage}%`;
    document.getElementById('progress-text').textContent = `${Math.round(percentage)}%`;
  };

  /**
   * Show Progress Bar and Text
   */
  const showProgress = () => {
    document.getElementById('progress-container').style.display = 'block';
    document.getElementById('progress-text').style.display = 'block';
  };

  /**
   * Hide Progress Bar and Text
   */
  const hideProgress = () => {
    document.getElementById('progress-container').style.display = 'none';
    document.getElementById('progress-text').style.display = 'none';
  };

  /**
   * Validate Novels
   * @param {Array<Object>} novels
   * @returns {boolean}
   */
  const validateNovels = (novels) => {
    for (const novel of novels) {
      if (!novel.name || !novel.coverUrl || !novel.pdfUrl || !novel.timestamp) {
        return false;
      }
    }
    return true;
  };

  /**
   * Toast Notification Functionality
   */
  const showToast = (message, type = 'success') => {
    const toastContainer = document.getElementById('toast-container');
    const toast = document.createElement('div');
    toast.className = `toast ${type}`;

    let iconSVG = '';

    if (type === 'success') {
      iconSVG = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
          <path d="M20.285,6.708l-11.7,11.7l-5.566-5.566c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414l6.28,6.28
          c0.391,0.391,1.023,0.391,1.414,0l12.544-12.544C20.676,7.731,20.676,7.1,20.285,6.708z"/>
        </svg>
      `;
    } else if (type === 'error') {
      iconSVG = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
          <path d="M12,0C5.373,0,0,5.373,0,12s5-12,12-12S5.373,0,12,0z M16.242,16.242
          c0.391,0.391,0.391,1.023,0,1.414c-0.391,0.391-1,0.391-1.414,0L12,13.414l-2.828,2.828c-0.391,0.391-1,0.391-1.414,0
          c-0.391-0.391-0.391-1,0-1.414L10.586,12l-2.828-2.828c-0.391-0.391-0.391-1,0-1.414s1-0.391,1.414,0L12,10.586
          l2.828-2.828c0.391-0.391,1-0.391,1.414,0s0.391,1,0,1.414L13.414,12l2.828,2.828C16.633,15.219,16.633,15.851,16.242,16.242z"/>
        </svg>
      `;
    }

    toast.innerHTML = `${iconSVG} ${message}`;
    toast.classList.add('show');
    toastContainer.appendChild(toast);

    // Remove toast after 5 seconds
    setTimeout(() => {
      toast.classList.remove('show');
      setTimeout(() => {
        toast.remove();
      }, 500);
    }, 5000);
  };

  /**
   * Initialize Theme Toggle
   */
  const initializeTheme = async () => {
    const themeToggle = document.getElementById('theme-toggle');
    let currentTheme;
    try {
      currentTheme = await getSetting('theme') || 'dark';
    } catch (error) {
      console.error('Error getting theme setting:', error);
      currentTheme = 'dark';
    }

    if (currentTheme === 'light') {
      document.body.setAttribute('data-theme', 'light');
      themeToggle.checked = true;
    } else {
      document.body.setAttribute('data-theme', 'dark');
      themeToggle.checked = false;
    }

    themeToggle.addEventListener('change', async () => {
      const newTheme = themeToggle.checked ? 'light' : 'dark';
      document.body.setAttribute('data-theme', newTheme);
      try {
        await putSetting('theme', newTheme);
      } catch (error) {
        console.error('Error saving theme setting:', error);
      }
    });
  };

  /**
   * Initialize Menu Button and Menu
   */
  const initializeMenu = () => {
    const menuButton = document.getElementById('menu-button');
    const menu = document.getElementById('menu');
    const settingsPanel = document.getElementById('settings-panel');
    const settingsPanelClose = document.querySelector('.settings-panel__close');

    menuButton.addEventListener('click', () => {
      menu.classList.toggle('open');
      if (menu.classList.contains('open')) {
        document.body.style.overflow = 'hidden';
      } else {
        document.body.style.overflow = 'auto';
      }
    });

    const menuItems = menu.querySelectorAll('.menu__item');
    menuItems.forEach(menuItem => {
      menuItem.addEventListener('click', async (e) => {
        const target = e.target.getAttribute('data-target');
        if (target === 'home') {
          // Redirect to the first page where all novels are displayed
          currentSearchTerm = '';
          currentPage = 0;
          hasMore = true;
          document.getElementById('content').innerHTML = '';
          await loadNextPage();
        } else if (target === 'settings') {
          settingsPanel.classList.add('open');
          document.body.style.overflow = 'hidden';
        }
        menu.classList.remove('open');
        document.body.style.overflow = 'auto';
      });
    });

    settingsPanelClose.addEventListener('click', () => {
      settingsPanel.classList.remove('open');
      document.body.style.overflow = 'auto';
    });

    window.addEventListener('click', (event) => {
      if (event.target === settingsPanel) {
        settingsPanel.classList.remove('open');
        document.body.style.overflow = 'auto';
      }
    });

    window.addEventListener('keydown', (event) => {
      if (event.key === 'Escape' && settingsPanel.classList.contains('open')) {
        settingsPanel.classList.remove('open');
        document.body.style.overflow = 'auto';
      }
    });
  };

  /**
   * Initialize AOS and Start the Application once the DOM is fully loaded
   */
  document.addEventListener('DOMContentLoaded', () => {
    // Initialize AOS if available
    if (window.AOS) {
      AOS.init({
        duration: 800,
        easing: 'ease-in-out',
        once: true,
      });
    }

    setupSentinelObserver();
    initializeTheme();
    initializeMenu();
    initializeApp();
  });

})();