Migaku Custom Stats

Custom stats for Migaku Memory.

// ==UserScript==
// @name         Migaku Custom Stats
// @namespace    http://tampermonkey.net/
// @version      0.1.22
// @description  Custom stats for Migaku Memory.
// @author       sguadalupe
// @license      GPL-3.0
// @match        https://study.migaku.com/
// @match        https://study.migaku.com/statistic
// @match        https://study.migaku.com/collection
// @run-at       document-idle
// @require      https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.min.js
// @grant        GM_addStyle
// ==/UserScript==

/* global pako, initSqlJs, Chart, Vue */

function debounce(func, wait) {
  let timeout;
  return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

(function () {
  "use strict";

  // =========================================================================
  // Constants and Configuration
  // =========================================================================

  // Environment and debug settings
  const SETTINGS = {
    ENVIRONMENT: "prod",
    DEFAULT_TIMEOUT: 15000,
    DEFAULT_DECK_ID: "all"
  };

  // Database configuration
  const DB_CONFIG = {
    DB_NAME: "srs",
    OBJECT_STORE: "data",
    SQL_CDN_PATH: "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/"
  };

  // Selectors for DOM elements
  const SELECTORS = {
    STATISTICS_ELEMENT: ".Statistic",
    TARGET_ELEMENT: ".UiPageLayout",
    MIGAKU_MAIN: ".MIGAKU-SRS[data-mgk-lang-selected]",
    VUE_CONTAINER_ID: "migaku-custom-stats-vue-container",
    ERROR_CONTAINER_ID: "migaku-custom-stats-error"
  };

  // Routes and navigation
  const ROUTES = {
    STATS_ROUTE: "/statistic"
  };

  // Attribute names
  const ATTRIBUTES = {
    LANG_SELECTED: "data-mgk-lang-selected",
    THEME: "data-mgk-theme" 
  };

  // Word status values
  const WORD_STATUS = {
    KNOWN: "KNOWN",
    LEARNING: "LEARNING",
    UNKNOWN: "UNKNOWN",
    IGNORED: "IGNORED"
  };

  // Chart configuration
  const CHART_CONFIG = {
    FORECAST_DAYS: 30,
    START_YEAR: 2020,
    START_MONTH: 0,
    START_DAY: 1,
    CHART_LABELS: {
      KNOWN: "Known",
      LEARNING: "Learning",
      UNKNOWN: "Unknown",
      IGNORED: "Ignored"
    },
    TOOLTIP_CONFIG: {
      CORNER_RADIUS: 20,
      PADDING: 12,
      CARET_SIZE: 0,
      BOX_PADDING: 4,
    },
    CHART_CDN_PATH: "https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js",
    ANIMATION_DELAY: 250
  };

  // SQL Queries
  const SQL_QUERIES = {
    DECKS_QUERY: `
      SELECT id, name 
      FROM deck 
      WHERE lang = ? AND del = 0
      ORDER BY name;
    `,
    WORD_QUERY: `
      SELECT
          SUM(CASE WHEN knownStatus = '${WORD_STATUS.KNOWN}' THEN 1 ELSE 0 END) as known_count,
          SUM(CASE WHEN knownStatus = '${WORD_STATUS.LEARNING}' THEN 1 ELSE 0 END) as learning_count,
          SUM(CASE WHEN knownStatus = '${WORD_STATUS.UNKNOWN}' THEN 1 ELSE 0 END) as unknown_count,
          SUM(CASE WHEN knownStatus = '${WORD_STATUS.IGNORED}' THEN 1 ELSE 0 END) as ignored_count
      FROM WordList
      WHERE language = ? AND del = 0`,
    WORD_QUERY_WITH_DECK: `
      SELECT
          SUM(CASE WHEN w.knownStatus = '${WORD_STATUS.KNOWN}' THEN 1 ELSE 0 END) as known_count,
          SUM(CASE WHEN w.knownStatus = '${WORD_STATUS.LEARNING}' THEN 1 ELSE 0 END) as learning_count,
          SUM(CASE WHEN w.knownStatus = '${WORD_STATUS.UNKNOWN}' THEN 1 ELSE 0 END) as unknown_count,
          SUM(CASE WHEN w.knownStatus = '${WORD_STATUS.IGNORED}' THEN 1 ELSE 0 END) as ignored_count
      FROM (
          SELECT DISTINCT w.dictForm, w.knownStatus
          FROM WordList w
          JOIN CardWordRelation cwr ON w.dictForm = cwr.dictForm
          JOIN card c ON cwr.cardId = c.id
          JOIN deck d ON c.deckId = d.id
          WHERE w.language = ? AND w.del = 0 AND d.id = ? AND c.del = 0
      ) as w`,
    DUE_QUERY: `
      SELECT
        due,
        COUNT(*) as count
      FROM card c
      JOIN card_type ct ON c.cardTypeId = ct.id
      WHERE ct.lang = ? AND c.due BETWEEN ? AND ? AND c.del = 0`,
    INTERVAL_QUERY: `
      SELECT
        interval as interval_group,
        COUNT(*) as count
      FROM card c
      JOIN card_type ct ON c.cardTypeId = ct.id
      WHERE ct.lang = ? AND c.del = 0 AND c.interval > 0
      GROUP BY interval_group
      ORDER BY interval_group`,
    REVIEW_HISTORY_QUERY: `
      SELECT 
        r.day,
        r.type,
        COUNT(DISTINCT r.cardId) as review_count
      FROM review r
      JOIN card c ON r.cardId = c.id
      JOIN card_type ct ON c.cardTypeId = ct.id
      JOIN reviewHistory rh ON r.day = rh.day
      WHERE ct.lang = ? AND r.day >= ? AND r.del = 0
      GROUP BY r.day, r.type
      ORDER BY r.day DESC, r.type`,
    STUDY_STATS_QUERY: `
      SELECT 
        COUNT(DISTINCT r.day) as days_studied,
        COUNT(*) as total_reviews,
        ROUND(COUNT(*) * 1.0 / COUNT(DISTINCT r.day), 1) as avg_reviews_per_day
      FROM review r
      JOIN card c ON r.cardId = c.id
      JOIN card_type ct ON c.cardTypeId = ct.id
      WHERE ct.lang = ? AND r.day BETWEEN ? AND ? AND r.del = 0`,
    CURRENT_DATE_QUERY: `
      SELECT entry 
      FROM keyValue
      WHERE key = 'study.activeDay.currentDate';`,
    PASS_RATE_QUERY: `
        SELECT 
          SUM(CASE WHEN r.type = 2 THEN 1 ELSE 0 END) as successful_reviews,
          SUM(CASE WHEN r.type = 1 THEN 1 ELSE 0 END) as failed_reviews
        FROM review r
        JOIN card c ON r.cardId = c.id
        JOIN card_type ct ON c.cardTypeId = ct.id
        WHERE ct.lang = ? AND r.day BETWEEN ? AND ? AND r.del = 0 AND r.type IN (1, 2);`
  };

  // UI/Display texts
  const UI_TEXTS = {
    ALL_DECKS: "All Decks",
    LOADING_MESSAGE: "Loading stats or no data available...",
    NO_DATA_MESSAGE: "Info: No word or due card data found for this language.",
    ERROR_TITLE: "Migaku Custom Stats - Error",
    INFO_TITLE: "Migaku Custom Stats - Info"
  };

  // =========================================================================
  // State Management
  // =========================================================================

  const appState = {
    isProcessing: false,
    selectedLanguage: null,
    languageChangeObserver: null,
    themeChangeObserver: null,
    selectedDeckId: SETTINGS.DEFAULT_DECK_ID,
    previousRoute: window.location.pathname
  };

  const dbState = {
    migakuDB: null,
    lastWordStats: null,
    lastDueStats: null,
    lastIntervalStats: null,
    lastReviewStats: null,
    lastStudyStats: null,
    availableDecks: []
  };

  const chartState = {
    isRenderingWord: false,
    isRenderingDue: false,
    isRenderingInterval: false,
    pendingRenderRequest: null
  };

  unsafeWindow.Vue = Vue;

  if (typeof Chart !== 'undefined' && Chart.defaults) {
    Chart.defaults.responsive = true;
    Chart.defaults.maintainAspectRatio = false;
  }

  const css = `
    .MCS__container {
        margin: 32px 0;
        display: flex;
        flex-direction: column;
        gap: 16px;
    }

    .MCS__wordcount {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 16px;
    }

    .MCS__wordcount__details {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .MCS__duechart {
        height: 300px;
        width: 100%;
        position: relative;
        padding: 8px;
        margin: 8px 0;
    }

    .MCS__intervalchart {
        height: 300px;
        width: 100%;
        position: relative;
        padding: 8px;
        margin: 8px 0;
    }
    
    .MCS__reviewchart {
        height: 300px;
        width: 100%;
        position: relative;
        padding: 8px;
        margin: 8px 0;
    }

    .MCS__wordcount__piechart {
        height: 200px;
        width: 200px;
        position: relative;
    }
    
    .MCS__study-stats {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 16px;
        margin: 16px 0;
    }
    
    .MCS__stat-box {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 16px;
        background: rgba(0, 199, 164, 0.05);
        border-radius: 8px;
    }
    
    .MCS__stat-value {
        font-size: 24px;
        font-weight: bold;
        margin-bottom: 8px;
    }
    
    .MCS__stat-label {
        font-size: 14px;
        text-align: center;
    }

    .MCS__deck-selector {
        margin: 16px 0;
    }
    
    /* Make sure canvas elements are visible */
    canvas {
        display: block;
        position: absolute;
        top: 0;
        left: 0;
    }

    .MCS__header-selector {
      margin-left: auto;
    }
    .Statistic__card__header {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }

    .MCS__radio-group {
      display: flex;
      align-items: center;
      padding: 8px 0;
      justify-content: space-around;
    }

    .MCS__radio-button {
      width: 24px;
      height: 24px;
      background-position: center;
      background-repeat: no-repeat;
      background-size: 16px 16px;
      border-radius: 50%;
      position: relative;
    }

    .MCS__radio-button.-toggled {
      background: none;
    }

    .MCS__radio-button.-toggled::after {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background-image: linear-gradient(180deg,var(--primary-gradient-1),var(--primary-gradient-2));
    }
    `;

  const themeConfigs = {
    dark: {
      backgroundElevation1: "#202047",
      backgroundElevation2: "#2b2b60",
      accent1: "rgba(178, 114, 255, 1)",
      accent2: "#fe4670",
      accent3: "#fba335",
      accent1Transparent: "rgba(178, 114, 255, 0.12)",
      textColor: "rgba(255, 255, 255, 1)",
      gridColor: "rgba(255, 255, 255, 0.1)",
      knownColor: "rgba(0, 199, 164, 1)",
      learningColor: "rgba(0, 199, 164, 0.4)",
      unknownColor: "rgba(255, 255, 255, 0.12)",
      ignoredColor: "rgba(255, 255, 255, 0.35)",
      barColor: "rgba(0, 199, 164, 1)",
    },
    light: {
      backgroundElevation1: "#fff",
      backgroundElevation2: "#fff",
      accent1: "#672fc3",
      accent2: "#fe4670",
      accent3: "#ff9345",
      accent1Transparent: "rgba(103, 47, 195, 0.12)",
      textColor: "rgba(0, 0, 90, 1)",
      gridColor: "rgba(0, 0, 0, 0.1)",
      knownColor: "rgba(0, 199, 164, 1)",
      learningColor: "rgba(0, 199, 164, 0.4)",
      unknownColor: "rgba(0, 0, 90, 0.07)",
      ignoredColor: "rgba(0, 0, 90, 0.15)",
      barColor: "rgba(0, 199, 164, 1)",
    },
  };

  function getCurrentTheme() {
    return document.documentElement.getAttribute(ATTRIBUTES.THEME) || "dark";
  }

  function getThemeColors() {
    const theme = getCurrentTheme();
    return themeConfigs[theme] || themeConfigs.dark;
  }

  // =========================================================================
  // Chart Manager
  // =========================================================================
  
  const ChartManager = {
    wordChartInstance: null,
    dueChartInstance: null,
    intervalChartInstance: null,
    reviewChartInstance: null,
    
    resetCharts() {
      this.destroyCharts();
    },
    
    /**
     * Updates or creates a word distribution pie chart
     * @param {HTMLCanvasElement} canvas - The canvas element to render on
     * @param {Object} wordStats - The word statistics data
     * @param {Function} logFn - Logging function
     * @returns {Object|null} - Chart instance or null if failed
     */
    createWordChart(canvas, wordStats, logFn) {
      if (!canvas || !wordStats) {
        logFn("Word chart creation aborted: missing canvas or data");
        return null;
      }
      
      const ctx = canvas.getContext('2d');
      if (!ctx) {
        logFn("Failed to get word chart canvas context");
        return null;
      }
      
      const themeColors = getThemeColors();
      const data = [
        wordStats.known_count || 0,
        wordStats.learning_count || 0,
        wordStats.unknown_count || 0,
        wordStats.ignored_count || 0,
      ];
      
      const chartConfig = {
        type: 'doughnut',
        data: {
          labels: [
            CHART_CONFIG.CHART_LABELS.KNOWN,
            CHART_CONFIG.CHART_LABELS.LEARNING, 
            CHART_CONFIG.CHART_LABELS.UNKNOWN, 
            CHART_CONFIG.CHART_LABELS.IGNORED
          ],
          datasets: [{
            label: 'Word Status Distribution',
            data: data,
            backgroundColor: [
              themeColors.knownColor,
              themeColors.learningColor,
              themeColors.unknownColor,
              themeColors.ignoredColor,
            ],
            borderWidth: 0,
          }],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          animation: {
            duration: 800,
            easing: 'easeOutQuart'
          },
          plugins: {
            legend: {
              position: 'right',
              labels: {
                color: themeColors.textColor,
                usePointStyle: true,
                pointStyle: 'circle'
              }
            },
            tooltip: {
              callbacks: {
                label: function (context) {
                  let label = context.label || "";
                  if (label) {
                    label += ": ";
                  }
                  if (context.parsed !== null) {
                    label += context.parsed.toLocaleString();
                  }
                  return label;
                },
              },
              backgroundColor: themeColors.backgroundElevation2,
              titleFontColor: themeColors.textColor,
              caretSize: CHART_CONFIG.TOOLTIP_CONFIG.CARET_SIZE,
              padding: CHART_CONFIG.TOOLTIP_CONFIG.PADDING,
              cornerRadius: CHART_CONFIG.TOOLTIP_CONFIG.CORNER_RADIUS,
              boxPadding: CHART_CONFIG.TOOLTIP_CONFIG.BOX_PADDING,
              multiKeyBackground: themeColors.backgroundElevation1,
              bodyColor: themeColors.textColor,
              titleColor: themeColors.textColor,
            }
          },
        }
      };
      
      try {
        if (this.wordChartInstance) {
          logFn("Updating existing word chart with new data");
          
          this.wordChartInstance.data.datasets[0].data = data;
          
          this.wordChartInstance.data.datasets[0].backgroundColor = [
            themeColors.knownColor,
            themeColors.learningColor,
            themeColors.unknownColor,
            themeColors.ignoredColor,
          ];
          
          this.wordChartInstance.options.plugins.legend.labels.color = themeColors.textColor;
          this.wordChartInstance.options.plugins.tooltip.backgroundColor = themeColors.backgroundElevation2;
          this.wordChartInstance.options.plugins.tooltip.bodyColor = themeColors.textColor;
          this.wordChartInstance.options.plugins.tooltip.titleColor = themeColors.textColor;
          
          this.wordChartInstance.update();
          return this.wordChartInstance;
        }
        
        this.wordChartInstance = new Chart(ctx, chartConfig);
        logFn("Word chart created successfully");
        return this.wordChartInstance;
      } catch (error) {
        logFn("Error in word chart creation/update:", error);
        
        try {
          if (this.wordChartInstance) {
            this.wordChartInstance.destroy();
          }
          this.wordChartInstance = new Chart(ctx, chartConfig);
          logFn("Word chart recreated after error");
          return this.wordChartInstance;
        } catch (recreateError) {
          logFn("Failed to recreate word chart:", recreateError);
          return null;
        }
      }
    },
    
    /**
     * Updates or creates a review history bar chart
     * @param {HTMLCanvasElement} canvas - The canvas element to render on
     * @param {Object} reviewStats - The review statistics data
     * @param {Function} logFn - Logging function
     * @returns {Object|null} - Chart instance or null if failed
     */
    createReviewHistoryChart(canvas, reviewStats, logFn) {
      if (!canvas) {
        logFn("Review history chart creation aborted: canvas is undefined");
        return null;
      }
      
      if (!reviewStats || !reviewStats.labels || !reviewStats.counts) {
        logFn("Review history chart creation aborted: reviewStats data is missing", reviewStats);
        return null;
      }
      
      const themeColors = getThemeColors();
      const ctx = canvas.getContext('2d');
      if (!ctx) {
        logFn("Failed to get review history chart canvas context");
        return null;
      }
      
      const chartConfig = {
        type: 'bar',
        data: {
          labels: reviewStats.labels,
          datasets: [
            {
              label: reviewStats.typeLabels[0],
              data: reviewStats.counts[0],
              backgroundColor: themeColors.accent1,
              borderWidth: 0,
              borderRadius: 4,
              order: 3
            },
            {
              label: reviewStats.typeLabels[1],
              data: reviewStats.counts[1],
              backgroundColor: themeColors.accent2,
              borderWidth: 0,
              borderRadius: 4,
              order: 2
            },
            {
              label: reviewStats.typeLabels[2],
              data: reviewStats.counts[2],
              backgroundColor: themeColors.accent3,
              borderWidth: 0,
              borderRadius: 4,
              order: 1
            }
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          animation: {
            duration: 800,
            easing: 'easeOutQuart'
          },
          scales: {
            y: {
              beginAtZero: true,
              stacked: true,
              title: {
                display: true,
                text: 'Number of Reviews',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                precision: 0,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
            x: {
              stacked: true,
              title: {
                display: true,
                text: 'Date',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                maxRotation: 45,
                minRotation: 45,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
          },
          plugins: {
            legend: {
              display: true,
              position: 'top',
              labels: {
                color: themeColors.textColor,
                usePointStyle: true,
                pointStyle: 'circle'
              }
            },
            tooltip: {
              mode: 'index',
              callbacks: {
                title: function(tooltipItems) {
                  return tooltipItems[0].label;
                },
                label: function(context) {
                  const value = context.parsed.y;
                  return `${context.dataset.label}: ${value}`;
                },
                footer: function(tooltipItems) {
                  const total = tooltipItems.reduce((sum, item) => sum + item.parsed.y, 0);
                  return `Total: ${total}`;
                }
              },
              backgroundColor: themeColors.backgroundElevation2,
              titleFontColor: themeColors.textColor,
              caretSize: CHART_CONFIG.TOOLTIP_CONFIG.CARET_SIZE,
              padding: CHART_CONFIG.TOOLTIP_CONFIG.PADDING,
              cornerRadius: CHART_CONFIG.TOOLTIP_CONFIG.CORNER_RADIUS,
              boxPadding: CHART_CONFIG.TOOLTIP_CONFIG.BOX_PADDING,
              multiKeyBackground: themeColors.backgroundElevation1,
              bodyColor: themeColors.textColor,
              footerColor: themeColors.textColor,
              titleColor: themeColors.textColor,
            }
          },
        }
      };
      
      try {
        if (this.reviewChartInstance) {
          logFn("Updating existing review history chart with new data");
          
          this.reviewChartInstance.data.labels = reviewStats.labels;
          this.reviewChartInstance.data.datasets[0].data = reviewStats.counts[0];
          this.reviewChartInstance.data.datasets[1].data = reviewStats.counts[1];
          this.reviewChartInstance.data.datasets[2].data = reviewStats.counts[2];
          this.reviewChartInstance.data.datasets[0].backgroundColor = themeColors.accent1;
          this.reviewChartInstance.data.datasets[1].backgroundColor = themeColors.accent2;
          this.reviewChartInstance.data.datasets[2].backgroundColor = themeColors.accent3;
          
          this.reviewChartInstance.options.scales.y.ticks.color = themeColors.textColor;
          this.reviewChartInstance.options.scales.y.title.color = themeColors.textColor;
          this.reviewChartInstance.options.scales.y.grid.color = themeColors.gridColor;
          this.reviewChartInstance.options.scales.x.ticks.color = themeColors.textColor;
          this.reviewChartInstance.options.scales.x.title.color = themeColors.textColor;
          this.reviewChartInstance.options.scales.x.grid.color = themeColors.gridColor;
          this.reviewChartInstance.options.plugins.tooltip.backgroundColor = themeColors.backgroundElevation2;
          this.reviewChartInstance.options.plugins.tooltip.bodyColor = themeColors.textColor;
          this.reviewChartInstance.options.plugins.tooltip.titleColor = themeColors.textColor;
          this.reviewChartInstance.options.plugins.tooltip.footerColor = themeColors.textColor;
          this.reviewChartInstance.options.plugins.legend.labels.color = themeColors.textColor;
          
          this.reviewChartInstance.update();
          return this.reviewChartInstance;
        }
        
        this.reviewChartInstance = new Chart(ctx, chartConfig);
        logFn("Review history chart created successfully");
        return this.reviewChartInstance;
      } catch (error) {
        logFn("Error in review history chart creation/update:", error);
        
        try {
          if (this.reviewChartInstance) {
            this.reviewChartInstance.destroy();
          }
          this.reviewChartInstance = new Chart(ctx, chartConfig);
          logFn("Review history chart recreated after error");
          return this.reviewChartInstance;
        } catch (recreateError) {
          logFn("Failed to recreate review history chart:", recreateError);
          return null;
        }
      }
    },
    
    /**
     * Updates or creates a due cards bar chart
     * @param {HTMLCanvasElement} canvas - The canvas element to render on
     * @param {Object} dueStats - The due statistics data
     * @param {Function} logFn - Logging function
     * @returns {Object|null} - Chart instance or null if failed
     */
    createDueChart(canvas, dueStats, logFn) {
      if (!canvas) {
        logFn("Chart creation aborted: canvas is undefined");
        return null;
      }
      
      if (!dueStats || !dueStats.labels || !dueStats.counts) {
        logFn("Chart creation aborted: dueStats data is missing", dueStats);
        return null;
      }
      
      const themeColors = getThemeColors();
      const ctx = canvas.getContext('2d');
      if (!ctx) {
        logFn("Failed to get canvas context");
        return null;
      }
      
      const cumulativeCounts = [];
      let runningSum = 0;
      for (let i = 0; i < dueStats.counts.length; i++) {
        runningSum += dueStats.counts[i];
        cumulativeCounts.push(runningSum);
      }
      
      const chartConfig = {
        type: 'bar',
        data: {
          labels: dueStats.labels,
          datasets: [
            {
              label: 'Cards Due',
              data: dueStats.counts,
              backgroundColor: themeColors.barColor,
              borderWidth: 0,
              borderRadius: 4,
              order: 2
            },
            {
              label: 'Cumulative Cards',
              data: cumulativeCounts,
              type: 'line',
              borderColor: themeColors.unknownColor,
              backgroundColor: themeColors.unknownColor,
              borderWidth: 2,
              pointStyle: false,
              tension: 0.4,
              fill: 'origin',
              yAxisID: 'y1',
              order: 1
            }
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          animation: {
            duration: 800,
            easing: 'easeOutQuart'
          },
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Cards Due',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                precision: 0,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
            y1: {
              position: 'right',
              beginAtZero: true,
              title: {
                display: true,
                text: 'Cumulative Cards',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                precision: 0,
              },
              grid: {
                drawOnChartArea: false,
              },
            },
            x: {
              title: {
                display: true,
                text: 'Date',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                maxRotation: 45,
                minRotation: 45,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
          },
          plugins: {
            legend: {
              display: true,
              position: 'top',
              labels: {
                color: themeColors.textColor,
                usePointStyle: true,
                pointStyle: 'circle'
              }
            },
            tooltip: {
              mode: 'index',
              intersect: false,
              callbacks: {
                title: function(tooltipItems) {
                  return tooltipItems[0].label;
                },
                label: function(context) {
                  const datasetLabel = context.dataset.label || '';
                  const value = context.parsed.y;
                  const total = cumulativeCounts[cumulativeCounts.length - 1];
                  
                  if (datasetLabel === 'Cards Due' && value > 0) {
                    const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
                    return `${datasetLabel}: ${value} (${percentage}%)`;
                  }
                  
                  if (datasetLabel === 'Cumulative Cards') {
                    const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
                    return `${datasetLabel}: ${value} (${percentage}%)`;
                  }
                  
                  return `${datasetLabel}: ${value}`;
                }
              },
              backgroundColor: themeColors.backgroundElevation2,
              titleFontColor: themeColors.textColor,
              caretSize: CHART_CONFIG.TOOLTIP_CONFIG.CARET_SIZE,
              padding: CHART_CONFIG.TOOLTIP_CONFIG.PADDING,
              cornerRadius: CHART_CONFIG.TOOLTIP_CONFIG.CORNER_RADIUS,
              boxPadding: CHART_CONFIG.TOOLTIP_CONFIG.BOX_PADDING,
              multiKeyBackground: themeColors.backgroundElevation1,
              bodyColor: themeColors.textColor,
              titleColor: themeColors.textColor,
            }
          },
        }
      };
      
      try {
        if (this.dueChartInstance) {
          logFn("Updating existing due chart with new data");
          
          const newCumulativeCounts = [];
          let newRunningSum = 0;
          for (let i = 0; i < dueStats.counts.length; i++) {
            newRunningSum += dueStats.counts[i];
            newCumulativeCounts.push(newRunningSum);
          }
          
          this.dueChartInstance.data.labels = dueStats.labels;
          this.dueChartInstance.data.datasets[0].data = dueStats.counts;
          
          if (this.dueChartInstance.data.datasets.length > 1) {
            this.dueChartInstance.data.datasets[1].data = newCumulativeCounts;
            this.dueChartInstance.data.datasets[1].borderColor = themeColors.unknownColor;
            this.dueChartInstance.data.datasets[1].backgroundColor = themeColors.unknownColor;
          } else {
            this.dueChartInstance.data.datasets.push({
              label: 'Cumulative Cards',
              data: newCumulativeCounts,
              type: 'line',
              borderColor: themeColors.unknownColor,
              backgroundColor: themeColors.unknownColor,
              borderWidth: 2,
              pointStyle: false,
              tension: 0.4,
              yAxisID: 'y1',
              order: 1
            });
            
            if (!this.dueChartInstance.options.scales.y1) {
              this.dueChartInstance.options.scales.y1 = {
                position: 'right',
                beginAtZero: true,
                title: {
                  display: true,
                  text: 'Cumulative Cards',
                  color: themeColors.textColor
                },
                ticks: {
                  color: themeColors.textColor,
                  precision: 0,
                },
                grid: {
                  drawOnChartArea: false,
                },
              };
            }
          }
          
          const finalTotal = newCumulativeCounts[newCumulativeCounts.length - 1];
          this.dueChartInstance.options.plugins.tooltip.callbacks.label = function(context) {
            const datasetLabel = context.dataset.label || '';
            const value = context.parsed.y;
            
            if (datasetLabel === 'Cards Due' && value > 0) {
              const percentage = finalTotal > 0 ? ((value / finalTotal) * 100).toFixed(1) : '0.0';
              return `${datasetLabel}: ${value} (${percentage}%)`;
            }
            
            if (datasetLabel === 'Cumulative Cards') {
              const percentage = finalTotal > 0 ? ((value / finalTotal) * 100).toFixed(1) : '0.0';
              return `${datasetLabel}: ${value} (${percentage}%)`;
            }
            
            return `${datasetLabel}: ${value}`;
          };
          
          this.dueChartInstance.data.datasets[0].backgroundColor = themeColors.barColor;
          
          this.dueChartInstance.options.scales.y.ticks.color = themeColors.textColor;
          this.dueChartInstance.options.scales.y.title.color = themeColors.textColor;
          this.dueChartInstance.options.scales.y.grid.color = themeColors.gridColor;
          this.dueChartInstance.options.scales.y1.ticks.color = themeColors.textColor;
          this.dueChartInstance.options.scales.y1.title.color = themeColors.textColor;
          this.dueChartInstance.options.scales.x.ticks.color = themeColors.textColor;
          this.dueChartInstance.options.scales.x.title.color = themeColors.textColor;
          this.dueChartInstance.options.scales.x.grid.color = themeColors.gridColor;
          this.dueChartInstance.options.plugins.legend.labels.color = themeColors.textColor;
          this.dueChartInstance.options.plugins.tooltip.backgroundColor = themeColors.backgroundElevation2;
          this.dueChartInstance.options.plugins.tooltip.bodyColor = themeColors.textColor;
          this.dueChartInstance.options.plugins.tooltip.titleColor = themeColors.textColor;
          
          this.dueChartInstance.options.plugins.legend.display = true;
          
          this.dueChartInstance.update();
          return this.dueChartInstance;
        }
        
        this.dueChartInstance = new Chart(ctx, chartConfig);
        logFn("Due chart created successfully");
        return this.dueChartInstance;
      } catch (error) {
        logFn("Error in due chart creation/update:", error);
        
        try {
          if (this.dueChartInstance) {
            this.dueChartInstance.destroy();
          }
          this.dueChartInstance = new Chart(ctx, chartConfig);
          logFn("Due chart recreated after error");
          return this.dueChartInstance;
        } catch (recreateError) {
          logFn("Failed to recreate due chart:", recreateError);
          return null;
        }
      }
    },
    
    /**
     * Updates or creates a review intervals bar chart
     * @param {HTMLCanvasElement} canvas - The canvas element to render on
     * @param {Object} intervalStats - The interval statistics data
     * @param {Function} logFn - Logging function
     * @returns {Object|null} - Chart instance or null if failed
     */
    createIntervalChart(canvas, intervalStats, logFn) {
      if (!canvas) {
        logFn("Interval chart creation aborted: canvas is undefined");
        return null;
      }
      
      if (!intervalStats || !intervalStats.labels || !intervalStats.counts) {
        logFn("Interval chart creation aborted: intervalStats data is missing", intervalStats);
        return null;
      }
      
      const themeColors = getThemeColors();
      const ctx = canvas.getContext('2d');
      if (!ctx) {
        logFn("Failed to get interval chart canvas context");
        return null;
      }
      
      const cumulativeCounts = [];
      let runningSum = 0;
      for (let i = 0; i < intervalStats.counts.length; i++) {
        runningSum += intervalStats.counts[i];
        cumulativeCounts.push(runningSum);
      }
      
      const maxCount = Math.max(...intervalStats.counts);
      const maxCumulative = cumulativeCounts[cumulativeCounts.length - 1];
      
      const chartConfig = {
        type: 'bar',
        data: {
          labels: intervalStats.labels,
          datasets: [
            {
              label: 'Cards per Interval',
              data: intervalStats.counts,
              backgroundColor: themeColors.accent1,
              borderWidth: 0,
              borderRadius: 4,
              order: 2
            },
            {
              label: 'Cumulative Cards',
              data: cumulativeCounts,
              type: 'line',
              borderColor: themeColors.accent1Transparent,
              backgroundColor: themeColors.accent1Transparent,
              borderWidth: 2,
              pointStyle: false,
              tension: 0.4,
              fill: 'origin',
              yAxisID: 'y1',
              order: 1
            }
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Number of Cards',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                precision: 0,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
            y1: {
              position: 'right',
              beginAtZero: true,
              title: {
                display: true,
                text: 'Cumulative Cards',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                precision: 0,
              },
              grid: {
                drawOnChartArea: false,
              },
            },
            x: {
              title: {
                display: true,
                text: 'Review Interval (Days)',
                color: themeColors.textColor
              },
              ticks: {
                color: themeColors.textColor,
                maxRotation: 45,
                minRotation: 45,
              },
              grid: {
                color: themeColors.gridColor,
              },
            },
          },
          plugins: {
            legend: {
              display: true,
              position: 'top',
              labels: {
                color: themeColors.textColor,
                usePointStyle: true,
                pointStyle: 'circle'
              }
            },
            tooltip: {
              mode: 'index',
              intersect: false,
              callbacks: {
                title: function(tooltipItems) {
                  return tooltipItems[0].label;
                },
                label: function(context) {
                  const datasetLabel = context.dataset.label || '';
                  const value = context.parsed.y;
                  const total = cumulativeCounts[cumulativeCounts.length - 1];
                  
                  if (datasetLabel === 'Cards per Interval' && value > 0) {
                    const percentage = ((value / total) * 100).toFixed(1);
                    return `${datasetLabel}: ${value} (${percentage}%)`;
                  }
                  
                  if (datasetLabel === 'Cumulative Cards') {
                    const percentage = ((value / total) * 100).toFixed(1);
                    return `${datasetLabel}: ${value} (${percentage}%)`;
                  }
                  
                  return `${datasetLabel}: ${value}`;
                }
              },
              backgroundColor: themeColors.backgroundElevation2,
              titleFontColor: themeColors.textColor,
              caretSize: CHART_CONFIG.TOOLTIP_CONFIG.CARET_SIZE,
              padding: CHART_CONFIG.TOOLTIP_CONFIG.PADDING,
              cornerRadius: CHART_CONFIG.TOOLTIP_CONFIG.CORNER_RADIUS,
              boxPadding: CHART_CONFIG.TOOLTIP_CONFIG.BOX_PADDING,
              multiKeyBackground: themeColors.backgroundElevation1,
              bodyColor: themeColors.textColor,
              titleColor: themeColors.textColor,
            }
          },
        }
      };
      
      try {
        if (this.intervalChartInstance) {
          logFn("Updating existing interval chart with new data");
          
          this.intervalChartInstance.data.labels = intervalStats.labels;
          this.intervalChartInstance.data.datasets[0].data = intervalStats.counts;
          this.intervalChartInstance.data.datasets[1].data = cumulativeCounts;
          
          this.intervalChartInstance.options.plugins.tooltip.callbacks = {
            title: function(tooltipItems) {
              return tooltipItems[0].label;
            },
            label: function(context) {
              const datasetLabel = context.dataset.label || '';
              const value = context.parsed.y;
              const total = cumulativeCounts[cumulativeCounts.length - 1];
              
              if (datasetLabel === 'Cards per Interval' && value > 0) {
                const percentage = ((value / total) * 100).toFixed(1);
                return `${datasetLabel}: ${value} (${percentage}%)`;
              }
              
              if (datasetLabel === 'Cumulative Cards') {
                const percentage = ((value / total) * 100).toFixed(1);
                return `${datasetLabel}: ${value} (${percentage}%)`;
              }
              
              return `${datasetLabel}: ${value}`;
            }
          };
          
          this.intervalChartInstance.data.datasets[0].backgroundColor = themeColors.accent1;
          this.intervalChartInstance.data.datasets[1].borderColor = themeColors.accent1Transparent;
          this.intervalChartInstance.data.datasets[1].backgroundColor = themeColors.accent1Transparent;
          
          this.intervalChartInstance.options.scales.y.ticks.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.y.title.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.y.grid.color = themeColors.gridColor;
          this.intervalChartInstance.options.scales.y1.ticks.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.y1.title.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.x.ticks.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.x.title.color = themeColors.textColor;
          this.intervalChartInstance.options.scales.x.grid.color = themeColors.gridColor;
          this.intervalChartInstance.options.plugins.legend.labels.color = themeColors.textColor;
          this.intervalChartInstance.options.plugins.tooltip.backgroundColor = themeColors.backgroundElevation2;
          this.intervalChartInstance.options.plugins.tooltip.bodyColor = themeColors.textColor;
          this.intervalChartInstance.options.plugins.tooltip.titleColor = themeColors.textColor;
          
          this.intervalChartInstance.update();
          return this.intervalChartInstance;
        }
        
        this.intervalChartInstance = new Chart(ctx, chartConfig);
        logFn("Interval chart created successfully");
        return this.intervalChartInstance;
      } catch (error) {
        logFn("Error in interval chart creation/update:", error);
        
        try {
          if (this.intervalChartInstance) {
            this.intervalChartInstance.destroy();
          }
          this.intervalChartInstance = new Chart(ctx, chartConfig);
          logFn("Interval chart recreated after error");
          return this.intervalChartInstance;
        } catch (recreateError) {
          logFn("Failed to recreate interval chart:", recreateError);
          return null;
        }
      }
    },
    
    /**
     * Updates charts with new data and theme
     * @param {Object} options - Configuration options
     * @param {HTMLCanvasElement} options.wordCanvas - Word chart canvas
     * @param {HTMLCanvasElement} options.dueCanvas - Due chart canvas
     * @param {HTMLCanvasElement} options.intervalCanvas - Interval chart canvas
     * @param {HTMLCanvasElement} options.reviewCanvas - Review history chart canvas
     * @param {Object} options.wordStats - Word statistics data
     * @param {Object} options.dueStats - Due statistics data
     * @param {Object} options.intervalStats - Interval statistics data
     * @param {Object} options.reviewStats - Review history statistics data
     * @param {Function} options.onComplete - Callback after charts are rendered
     * @param {Function} options.logFn - Logging function
     */
    updateCharts(options) {
      const { wordCanvas, dueCanvas, intervalCanvas, reviewCanvas, wordStats, dueStats, intervalStats, reviewStats, onComplete, logFn } = options;
      
      logFn("Chart update triggered");
      
      let wordChartSuccess = false;
      let dueChartSuccess = false;
      let intervalChartSuccess = false;
      let reviewChartSuccess = false;
      
      if (wordCanvas && wordStats) {
        wordChartSuccess = !!this.createWordChart(wordCanvas, wordStats, logFn);
      }
      
      if (dueCanvas && dueStats && dueStats.labels && dueStats.counts) {
        dueChartSuccess = !!this.createDueChart(dueCanvas, dueStats, logFn);
      }
      
      if (intervalCanvas && intervalStats && intervalStats.labels && intervalStats.counts) {
        intervalChartSuccess = !!this.createIntervalChart(intervalCanvas, intervalStats, logFn);
      }
      
      if (reviewCanvas && reviewStats && reviewStats.labels && reviewStats.counts) {
        reviewChartSuccess = !!this.createReviewHistoryChart(reviewCanvas, reviewStats, logFn);
      }
      
      if (onComplete) {
        onComplete({
          wordChartSuccess,
          dueChartSuccess,
          intervalChartSuccess,
          reviewChartSuccess
        });
      }
    },
    
    destroyCharts() {
      if (this.wordChartInstance) {
        try {
          this.wordChartInstance.destroy();
          this.wordChartInstance = null;
        } catch (e) {
          console.error("Error destroying word chart instance:", e);
        }
      }
      
      if (this.dueChartInstance) {
        try {
          this.dueChartInstance.destroy();
          this.dueChartInstance = null;
        } catch (e) {
          console.error("Error destroying due chart instance:", e);
        }
      }
      
      if (this.intervalChartInstance) {
        try {
          this.intervalChartInstance.destroy();
          this.intervalChartInstance = null;
        } catch (e) {
          console.error("Error destroying interval chart instance:", e);
        }
      }
      
      if (this.reviewChartInstance) {
        try {
          this.reviewChartInstance.destroy();
          this.reviewChartInstance = null;
        } catch (e) {
          console.error("Error destroying review chart instance:", e);
        }
      }
    }
  };

  GM_addStyle(css);

  function createExtensionLogger() {
    return function () {
      const args = Array.from(arguments);
      if (SETTINGS.ENVIRONMENT === "dev") {
        console.log("Migaku Custom Stats:", ...args);
      }
    };
  }
  const extensionLog = createExtensionLogger();

  const dbName = DB_CONFIG.DB_NAME;
  const objectStoreName = DB_CONFIG.OBJECT_STORE;
  const statisticsElementSelector = SELECTORS.STATISTICS_ELEMENT;
  const targetElementSelector = SELECTORS.TARGET_ELEMENT;
  const statsRoute = ROUTES.STATS_ROUTE;

  function waitForElement(selector, timeout = SETTINGS.DEFAULT_TIMEOUT) {
    return new Promise((resolve, reject) => {
      const observer = new MutationObserver((_, obs) => {
        const element = document.querySelector(selector);
        if (element) {
          extensionLog(`Element '${selector}' detected.`);
          obs.disconnect();
          resolve(element);
        }
      });

      const element = document.querySelector(selector);
      if (element) {
        extensionLog(`Element '${selector}' found immediately.`);
        resolve(element);
        return;
      }

      observer.observe(document.body, { childList: true, subtree: true });

      setTimeout(() => {
        if (!document.querySelector(selector)) {
          observer.disconnect();
          extensionLog(
            `Element '${selector}' not found via MutationObserver after ${timeout}ms.`
          );
          resolve(null);
        }
      }, timeout);
    });
  }

  function initDB() {
    return new Promise((resolve, reject) => {
      if (dbState.migakuDB) {
        extensionLog("Using existing DB connection.");
        resolve(dbState.migakuDB);
        return;
      }

      extensionLog("Attempting to open IndexedDB...");
      const request = indexedDB.open(dbName);

      request.onerror = (event) => {
        extensionLog(`IndexedDB error: ${event.target.errorCode}`);
        reject(new Error(`IndexedDB error: ${event.target.errorCode}`));
      };

      request.onsuccess = (event) => {
        extensionLog("Successfully connected to Migaku DB:", dbName);
        dbState.migakuDB = event.target.result;
        resolve(dbState.migakuDB);
      };

      request.onupgradeneeded = () => {
        extensionLog(
          "Database upgrade needed (or first time setup). This script doesn't handle upgrades."
        );
      };
    });
  }

  async function runStatsLogic() {
    if (window.location.pathname !== statsRoute) {
      extensionLog("Not on stats route, skipping logic.");
      if (appState.languageChangeObserver) {
        extensionLog("Disconnecting language change observer.");
        appState.languageChangeObserver.disconnect();
        appState.languageChangeObserver = null;
      }
      return;
    }

    const mainElement = await waitForElement(SELECTORS.MIGAKU_MAIN);
    if (!mainElement) {
      extensionLog("Migaku main element not found, skipping logic.");
      if (appState.languageChangeObserver) {
        extensionLog("Migaku element lost, disconnecting language change observer.");
        appState.languageChangeObserver.disconnect();
        appState.languageChangeObserver = null;
      }
      return;
    }

    if (!appState.languageChangeObserver) {
      extensionLog("Setting up language change observer.");
      appState.languageChangeObserver = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
          if (
            mutation.type === "attributes" &&
            mutation.attributeName === ATTRIBUTES.LANG_SELECTED
          ) {
            const newLanguage = mainElement.getAttribute(ATTRIBUTES.LANG_SELECTED);
            extensionLog(`Language attribute changed to: ${newLanguage}`);
            
            appState.selectedDeckId = SETTINGS.DEFAULT_DECK_ID;
            extensionLog(`Reset deck selection to '${UI_TEXTS.ALL_DECKS}' due to language change`);
            
            runStatsLogic();
            break;
          }
        }
      });
      appState.languageChangeObserver.observe(mainElement, {
        attributes: true,
        attributeFilter: [ATTRIBUTES.LANG_SELECTED],
      });
      extensionLog("Language change observer attached.");
    }

    let currentLanguage;
    try {
      currentLanguage = mainElement.attributes.getNamedItem(ATTRIBUTES.LANG_SELECTED).value;
      if (currentLanguage !== appState.selectedLanguage) {
        extensionLog("Processing for language:", currentLanguage);
      } else {
        extensionLog("Language unchanged, re-validating stats...");
      }
    } catch (error) {
      extensionLog("Could not read selected language attribute.", error);
      dbState.lastWordStats = null;
      dbState.lastDueStats = null;
      dbState.lastIntervalStats = null;
      dbState.lastReviewStats = null;
      dbState.lastStudyStats = null;
      await displayCustomStats("Error: Could not determine selected language. Please reload the page.")
        .catch((e) => extensionLog("Failed to display language error", e));
      appState.isProcessing = false;
      return;
    }

    if (appState.isProcessing && appState.selectedLanguage === currentLanguage) {
      extensionLog("Stats logic already running for the current language, skipping.");
      return;
    }

    if (currentLanguage === appState.selectedLanguage && appState.isProcessing) {
      extensionLog(`Skipping run: Language (${currentLanguage}) hasn't changed and processing is ongoing.`);
      return;
    }
    
    if (currentLanguage === appState.selectedLanguage && !appState.isProcessing) {
      extensionLog(`Language (${currentLanguage}) hasn't changed since last successful run. Checking if stats need refresh.`);
    }

    if (currentLanguage !== appState.selectedLanguage) {
      appState.selectedDeckId = SETTINGS.DEFAULT_DECK_ID;
      extensionLog(`Language changed from "${appState.selectedLanguage}" to "${currentLanguage}". Reset deck to "${UI_TEXTS.ALL_DECKS}".`);
      ChartManager.resetCharts();
      dbState.lastWordStats = null;
      dbState.lastDueStats = null;
      dbState.lastIntervalStats = null;
      dbState.lastReviewStats = null;
      dbState.lastStudyStats = null;
    }

    appState.selectedLanguage = currentLanguage;
    appState.isProcessing = true;
    extensionLog("Running stats logic for language:", appState.selectedLanguage);

    try {
      const db = await initDB();
      if (!db) {
        throw new Error("Failed to initialize database connection.");
      }
      await accessMigakuData(db);
    } catch (error) {
      extensionLog("Error in runStatsLogic:", error);
      dbState.lastWordStats = null;
      dbState.lastDueStats = null;
      dbState.lastIntervalStats = null;
      dbState.lastReviewStats = null;
      dbState.lastStudyStats = null;
      await displayCustomStats(`Error loading statistics: ${error.message}. Please try again later.`)
        .catch((e) => extensionLog("Failed to display run error", e));
    } finally {
      appState.isProcessing = false;
      extensionLog("Stats logic processing finished for language:", appState.selectedLanguage);
    }
  }

  // =========================================================================
  // Database Helpers
  // =========================================================================
  
  /**
   * Decompresses a gzipped Uint8Array
   * @param {Uint8Array} compressedData - The compressed data blob
   * @param {Function} logFn - Logging function
   * @returns {Uint8Array|null} - Decompressed data or null if failed
   */
  function decompressData(compressedData, logFn) {
    try {
      logFn("Attempting Gzip decompression...");
      const decompressedData = pako.inflate(compressedData);
      logFn("Decompression successful.");
      return decompressedData;
    } catch (err) {
      logFn("Gzip Decompression failed:", err);
      return null;
    }
  }
  
  /**
   * Initializes the SQL.js engine
   * @param {Function} logFn - Logging function
   * @returns {Object|null} - SQL.js instance or null if failed
   */
  async function initializeSqlEngine(logFn) {
    try {
      logFn("Initializing sql.js...");
      const SQL = await initSqlJs({
        locateFile: (file) => `${DB_CONFIG.SQL_CDN_PATH}${file}`,
      });
      logFn("sql.js initialized successfully.");
      return SQL;
    } catch (err) {
      logFn("Failed to initialize sql.js:", err);
      return null;
    }
  }
  
  /**
   * Fetches available decks for the selected language
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {Function} logFn - Logging function
   * @returns {Array} - Array of deck objects with id and name
   */
  function fetchDecks(dbInstance, language, logFn) {
    const decks = [{ id: SETTINGS.DEFAULT_DECK_ID, name: UI_TEXTS.ALL_DECKS }];
    
    try {
      const decksResults = dbInstance.exec(SQL_QUERIES.DECKS_QUERY, [language]);
      
      if (decksResults.length > 0 && decksResults[0].values.length > 0) {
        logFn("Decks query results:", decksResults[0]);
        
        decksResults[0].values.forEach((row) => {
          const id = String(row[0]);
          const name = row[1];
          decks.push({ id, name });
        });
        
        logFn("Available decks:", decks);
      } else {
        logFn("No decks found for language:", language);
      }
    } catch (decksError) {
      logFn("Error fetching decks:", decksError);
    }
    
    return decks;
  }
  
  /**
   * Fetches word statistics for the selected language and deck
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {string} deckId - Selected deck ID
   * @param {Function} logFn - Logging function
   * @returns {Object|null} - Word statistics or null if failed
   */
  function fetchWordStats(dbInstance, language, deckId, logFn) {
    try {
      let wordQuery = SQL_QUERIES.WORD_QUERY;
      let wordQueryParams = [language];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        wordQuery = SQL_QUERIES.WORD_QUERY_WITH_DECK;
        wordQueryParams = [language, deckId];
      }
      
      const wordResults = dbInstance.exec(wordQuery, wordQueryParams);
      
      if (wordResults.length > 0 && wordResults[0].values.length > 0) {
        logFn("Word query results:", wordResults);
        const numberOfResults = wordResults[0].values[0].length;
        const wordValues = {};
        
        for (let i = 0; i < numberOfResults; i++) {
          wordValues[wordResults[0].columns[i]] = wordResults[0].values[0][i];
        }
        
        return wordValues;
      } else {
        logFn("Word query returned no results.");
        return null;
      }
    } catch (error) {
      logFn("Error fetching word stats:", error);
      return null;
    }
  }
  
  /**
   * Fetches due cards statistics for the selected language and deck
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {string} deckId - Selected deck ID
   * @param {Function} logFn - Logging function
   * @param {Date} currentDate - The current date fetched from the database
   * @param {number} currentDayNumber - The current day number relative to the start date
   * @param {string} dueStatsPeriod - Period ID (default: "dueStats1")
   * @returns {Object|null} - Due cards statistics or null if failed
   */
  function fetchDueStats(dbInstance, language, deckId, logFn, currentDate, currentDayNumber, dueStatsPeriod = "dueStats1") {
    try {
      let forecastDays;
      const period = dueStatsPeriod.replace("dueStats", "");
      
      if (period === "All") {
        forecastDays = 3650;
      } else {
        const periodMonths = parseInt(period, 10) || 1;
        const today = new Date();
        today.setHours(0, 0, 0, 0);
        const forecastEndDate = new Date(today);
        
        forecastEndDate.setMonth(today.getMonth() + periodMonths);
        
        forecastDays = Math.round((forecastEndDate - today) / (1000 * 60 * 60 * 24));
      }
      
      let endDayNumber;
      if (dueStatsPeriod === "dueStatsAll") {
        let maxDueQuery = `SELECT MAX(due) as maxDue FROM card c
                          JOIN card_type ct ON c.cardTypeId = ct.id
                          WHERE ct.lang = ? AND c.due >= ? AND c.del = 0`;
        let maxDueParams = [language, currentDayNumber];
        
        if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
          maxDueQuery += " AND c.deckId = ?";
          maxDueParams.push(deckId);
        }
        
        const maxDueResults = dbInstance.exec(maxDueQuery, maxDueParams);
        
        if (maxDueResults.length > 0 && maxDueResults[0].values.length > 0 && maxDueResults[0].values[0][0] !== null) {
          const maxDueDay = maxDueResults[0].values[0][0];
          endDayNumber = Math.min(currentDayNumber + 365, maxDueDay);
          logFn(`Found max due day: ${maxDueDay}, using end day: ${endDayNumber}`);
        } else {
          endDayNumber = currentDayNumber + forecastDays - 1;
          logFn(`No max due day found, using default: ${endDayNumber}`);
        }
      } else {
        endDayNumber = currentDayNumber + (forecastDays - 1);
      }
      
      logFn(`Calculating due cards between day ${currentDayNumber} and ${endDayNumber}`);
      
      const actualForecastDays = endDayNumber - currentDayNumber + 1;
      
      let dueQuery = SQL_QUERIES.DUE_QUERY;
      let dueQueryParams = [language, currentDayNumber, endDayNumber];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        dueQuery += " AND c.deckId = ?";
        dueQueryParams.push(deckId);
      }
      
      dueQuery += " GROUP BY due ORDER BY due";
      
      const dueResults = dbInstance.exec(dueQuery, dueQueryParams);
      
      const dateLabels = [];
      const dateCounts = [];
      const tempDate = new Date(currentDate);
      
      for (let i = 0; i < actualForecastDays; i++) {
        let label = tempDate.toLocaleDateString(undefined, {
          month: "short",
          day: "numeric",
          year: "numeric"
        });
        if (SETTINGS.ENVIRONMENT === "dev") {
          label += ` (${currentDayNumber + i})`;
        }
        dateLabels.push(label);
        dateCounts.push(0);
        tempDate.setDate(tempDate.getDate() + 1);
      }
      
      if (dueResults.length > 0 && dueResults[0].values.length > 0) {
        logFn("Due cards query results:", dueResults[0]);
        const dueCountsByDay = {};
        
        dueResults[0].values.forEach((row) => {
          const resultRow = {};
          dueResults[0].columns.forEach((col, index) => {
            resultRow[col] = row[index];
          });
          dueCountsByDay[resultRow.due] = resultRow.count;
        });
        
        for (let i = 0; i < actualForecastDays; i++) {
          const dayNum = currentDayNumber + i;
          if (dueCountsByDay[dayNum]) {
            dateCounts[i] = dueCountsByDay[dayNum];
          }
        }
        
        if (dueStatsPeriod === "dueStatsAll") {
          let lastNonZeroIndex = dateCounts.length - 1;
          while (lastNonZeroIndex >= 0 && dateCounts[lastNonZeroIndex] === 0) {
            lastNonZeroIndex--;
          }
          
          const extraDays = 5;
          lastNonZeroIndex = Math.min(lastNonZeroIndex + extraDays, dateCounts.length - 1);
          
          if (lastNonZeroIndex >= 0) {
            dateLabels.splice(lastNonZeroIndex + 1);
            dateCounts.splice(lastNonZeroIndex + 1);
            logFn(`Trimmed data to ${lastNonZeroIndex + 1} days`);
          }
        }
      } else {
        logFn("Due cards query returned no results.");
      }
      
      return { labels: dateLabels, counts: dateCounts };
    } catch (error) {
      logFn("Error fetching due stats:", error);
      return null;
    }
  }

  /**
   * Fetches review interval statistics for the selected language and deck
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {string} deckId - Selected deck ID
   * @param {Function} logFn - Logging function
   * @param {string} percentileId - Percentile ID (default: "intervalPercentile75")
   * @returns {Object|null} - Interval statistics or null if failed
   */
  function fetchIntervalStats(dbInstance, language, deckId, logFn, percentileId = "intervalPercentile75") {
    try {
      const percentile = percentileId.replace("intervalPercentile", "");
      let intervalQuery = SQL_QUERIES.INTERVAL_QUERY;
      let intervalQueryParams = [language];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        intervalQuery = intervalQuery.replace("WHERE ct.lang = ? AND c.del = 0 AND c.interval > 0", 
                                             "WHERE ct.lang = ? AND c.del = 0 AND c.interval > 0 AND c.deckId = ?");
        intervalQueryParams.push(deckId);
      }
      
      const intervalResults = dbInstance.exec(intervalQuery, intervalQueryParams);
      
      if (intervalResults.length > 0 && intervalResults[0].values.length > 0) {
        logFn("Interval query results:", intervalResults[0]);
        
        const intervalMap = new Map();
        let maxInterval = 0;
        let totalCards = 0;
        
        intervalResults[0].values.forEach((row) => {
          const interval = Math.round(row[0]);
          const count = row[1];
          intervalMap.set(interval, count);
          maxInterval = Math.max(maxInterval, interval);
          totalCards += count;
        });
        
        const cutoffPercentile = percentile / 100;
        let cumulativeCount = 0;
        let cutoffInterval = maxInterval;
        
        const sortedIntervals = Array.from(intervalMap.keys()).sort((a, b) => a - b);
        
        for (const interval of sortedIntervals) {
          cumulativeCount += intervalMap.get(interval);
          const percentileValue = cumulativeCount / totalCards;
          
          if (percentileValue >= cutoffPercentile) {
            cutoffInterval = interval;
            break;
          }
        }
        
        logFn(`Excluding intervals beyond ${cutoffInterval} days (${percentile}th percentile)`);
        
        const intervalLabels = [];
        const intervalCounts = [];
        
        for (let i = 1; i <= cutoffInterval; i++) {
          let label = i === 1 ? "1 day" : `${i} days`;
          
          intervalLabels.push(label);
          intervalCounts.push(intervalMap.has(i) ? intervalMap.get(i) : 0);
        }
        
        return { labels: intervalLabels, counts: intervalCounts };
      } else {
        logFn("Interval query returned no results.");
        return null;
      }
    } catch (error) {
      logFn("Error fetching interval stats:", error);
      return null;
    }
  }

  /**
   * Fetches review history statistics for the selected language and deck
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {string} deckId - Selected deck ID
   * @param {Function} logFn - Logging function
   * @param {Date} currentDate - The current date fetched from the database
   * @param {number} currentDayNumber - The current day number relative to the start date
   * @param {string} periodId - Period ID (default: "reviewHistory1")
   * @param {string} grouping - Time grouping ('Days', 'Weeks', 'Months', default: 'Days')
   * @returns {Object|null} - Review history statistics or null if failed
   */
  function fetchReviewHistory(dbInstance, language, deckId, logFn, currentDate, currentDayNumber, periodId = "reviewHistory1", grouping = "Days") {
    try {
      const period = periodId.replace("reviewHistory", "");
      const startDate = new Date(CHART_CONFIG.START_YEAR, CHART_CONFIG.START_MONTH, CHART_CONFIG.START_DAY);
      
      let periodDays;
      if (period === "All") {
        periodDays = currentDayNumber;
      } else {
        const periodMonths = parseInt(period, 10) || 1;
        const periodStartDate = new Date(currentDate);
        periodStartDate.setMonth(currentDate.getMonth() - periodMonths);
        periodDays = Math.round((currentDate - periodStartDate) / (1000 * 60 * 60 * 24)) + 1;
      }
      
      const periodDaysAgoDayNumber = currentDayNumber - periodDays;
      
      logFn(`Fetching review history since day ${periodDaysAgoDayNumber} (${periodDays} days ago), grouped by ${grouping}`);
      
      let reviewQuery = SQL_QUERIES.REVIEW_HISTORY_QUERY;
      let reviewQueryParams = [language, periodDaysAgoDayNumber];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        reviewQuery = reviewQuery.replace(
          "WHERE ct.lang = ? AND r.day >= ? AND r.del = 0", 
          "WHERE ct.lang = ? AND r.day >= ? AND r.del = 0 AND c.deckId = ?"
        );
        reviewQueryParams.push(deckId);
      }
      
      const reviewResults = dbInstance.exec(reviewQuery, reviewQueryParams);
      
      const dateLabels = [];
      const type0Counts = [];
      const type1Counts = [];
      const type2Counts = [];
      const dayMap = new Map();
      const aggregateMap = new Map();
      
      let actualPeriodDays = periodDays;
      if (period === "All" && reviewResults.length > 0 && reviewResults[0].values.length > 0) {
        let earliestDayWithReviews = currentDayNumber;
        reviewResults[0].values.forEach(row => {
          const dayNumber = row[0];
          earliestDayWithReviews = Math.min(earliestDayWithReviews, dayNumber);
        });
        
        const daysWithData = currentDayNumber - earliestDayWithReviews + 1;
        actualPeriodDays = Math.min(periodDays, daysWithData);
        logFn(`Found earliest day with reviews: ${earliestDayWithReviews}, using period: ${actualPeriodDays} days`);
      }
      
      let currentGroupKey = null;
      let groupIndex = -1;
      for (let i = 0; i < actualPeriodDays; i++) {
        const dayNumber = currentDayNumber - (actualPeriodDays - 1 - i);
        const date = new Date(startDate);
        date.setDate(date.getDate() + dayNumber);
        date.setHours(0, 0, 0, 0);
        
        let displayDate;
        let groupKey;
        
        if (grouping === 'Weeks') {
          const dayOfWeek = (date.getDay() + 6) % 7;
          const weekStartDate = new Date(date);
          weekStartDate.setDate(date.getDate() - dayOfWeek);
          groupKey = weekStartDate.toISOString().split('T')[0];
          
          if (groupKey !== currentGroupKey) {
            displayDate = weekStartDate.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
            dateLabels.push(`Week of ${displayDate}`);
            type0Counts.push(0);
            type1Counts.push(0);
            type2Counts.push(0);
            currentGroupKey = groupKey;
            groupIndex++;
            aggregateMap.set(groupKey, { index: groupIndex, data: [0, 0, 0] });
          }
        } else if (grouping === 'Months') {
          groupKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
          
          if (groupKey !== currentGroupKey) {
            displayDate = date.toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
            dateLabels.push(displayDate);
            type0Counts.push(0);
            type1Counts.push(0);
            type2Counts.push(0);
            currentGroupKey = groupKey;
            groupIndex++;
            aggregateMap.set(groupKey, { index: groupIndex, data: [0, 0, 0] });
          }
        } else {
          groupKey = dayNumber;
          displayDate = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
          if (SETTINGS.ENVIRONMENT === "dev") {
            displayDate += ` (${dayNumber})`;
          }
          dateLabels.push(displayDate);
          type0Counts.push(0);
          type1Counts.push(0);
          type2Counts.push(0);
          dayMap.set(dayNumber, { index: i });
        }
      }
      
      if (reviewResults.length > 0 && reviewResults[0].values.length > 0) {
        logFn("Review history query results:", reviewResults[0]);
        
        reviewResults[0].values.forEach(row => {
          const dayNumber = row[0];
          const reviewType = row[1];
          const count = row[2];
          const date = new Date(startDate);
          date.setDate(date.getDate() + dayNumber);
          date.setHours(0, 0, 0, 0);
          
          let targetIndex = -1;
          let targetMapEntry = null;
          
          if (grouping === 'Weeks') {
            const dayOfWeek = (date.getDay() + 6) % 7;
            const weekStartDate = new Date(date);
            weekStartDate.setDate(date.getDate() - dayOfWeek);
            const groupKey = weekStartDate.toISOString().split('T')[0];
            if (aggregateMap.has(groupKey)) {
               targetMapEntry = aggregateMap.get(groupKey);
               targetIndex = targetMapEntry.index;
            }
          } else if (grouping === 'Months') {
            const groupKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
            if (aggregateMap.has(groupKey)) {
              targetMapEntry = aggregateMap.get(groupKey);
              targetIndex = targetMapEntry.index;
            }
          } else {
            if (dayMap.has(dayNumber)) {
              targetIndex = dayMap.get(dayNumber).index;
            }
          }
          
          if (targetIndex !== -1) {
            if (reviewType === 0) {
               if (grouping === 'Days') type0Counts[targetIndex] += count; else targetMapEntry.data[0] += count;
            } else if (reviewType === 1) {
               if (grouping === 'Days') type1Counts[targetIndex] += count; else targetMapEntry.data[1] += count;
            } else if (reviewType === 2) {
               if (grouping === 'Days') type2Counts[targetIndex] += count; else targetMapEntry.data[2] += count;
            }
          }
        });
        
        if (grouping === 'Weeks' || grouping === 'Months') {
            aggregateMap.forEach(entry => {
                type0Counts[entry.index] = entry.data[0];
                type1Counts[entry.index] = entry.data[1];
                type2Counts[entry.index] = entry.data[2];
            });
        }
        
      } else {
        logFn("Review history query returned no results");
      }
      
      return { 
        labels: dateLabels, 
        counts: [type0Counts, type1Counts, type2Counts],
        typeLabels: ['New cards', 'Failed reviews', 'Successful reviews']
      };
    } catch (error) {
      logFn("Error fetching review history:", error);
      return null;
    }
  }
  
  /**
   * Fetches study statistics for the selected language and deck
   * @param {Object} dbInstance - SQL.js database instance
   * @param {string} language - Selected language
   * @param {string} deckId - Selected deck ID
   * @param {Function} logFn - Logging function
   * @param {number} currentDayNumber - The current day number relative to the start date
   * @param {string} periodId - Period ID (default: "studyStats1")
   * @returns {Object|null} - Study statistics or null if failed
   */
  function fetchStudyStats(dbInstance, language, deckId, logFn, currentDayNumber, periodId = "studyStats1") {
    try {
      const period = periodId.replace("studyStats", "");
      const startDate = new Date(CHART_CONFIG.START_YEAR, CHART_CONFIG.START_MONTH, CHART_CONFIG.START_DAY);
      
      let periodDays;
      let startDayNumber;
      let earliestReviewDayForAllTime = null;

      if (period === "All") {
        let earliestReviewQuery = `SELECT MIN(r.day) as minDay 
                                   FROM review r 
                                   JOIN card c ON r.cardId = c.id 
                                   JOIN card_type ct ON c.cardTypeId = ct.id 
                                   WHERE ct.lang = ? AND r.del = 0`;
        
        let earliestReviewParams = [language];
        
        if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
          earliestReviewQuery += " AND c.deckId = ?";
          earliestReviewParams.push(deckId);
        }
        
        const earliestReviewResults = dbInstance.exec(earliestReviewQuery, earliestReviewParams);
        
        if (earliestReviewResults.length > 0 && 
            earliestReviewResults[0].values.length > 0 && 
            earliestReviewResults[0].values[0][0] !== null) {
          earliestReviewDayForAllTime = earliestReviewResults[0].values[0][0];
          periodDays = currentDayNumber - earliestReviewDayForAllTime + 1;
          startDayNumber = earliestReviewDayForAllTime;
          logFn(`Found earliest review day (All time): ${earliestReviewDayForAllTime}, setting period to ${periodDays} days`);
        } else {
          periodDays = currentDayNumber + 1;
          startDayNumber = 0;
          logFn(`No earliest review day found (All time), using full period: ${periodDays} days`);
        }
      } else {
        const periodMonths = parseInt(period, 10) || 1;
        const today = new Date(startDate);
        today.setDate(today.getDate() + currentDayNumber);
        const periodStartDate = new Date(today);
        periodStartDate.setMonth(today.getMonth() - periodMonths);
        periodDays = Math.round((today - periodStartDate) / (1000 * 60 * 60 * 24)) + 1;
        startDayNumber = currentDayNumber - periodDays + 1;
        logFn(`Using fixed period: ${periodMonths} months (${periodDays} days), starting from day ${startDayNumber}`);
      }
      
      logFn(`Fetching study stats from day ${startDayNumber} to ${currentDayNumber}`);
      
      let studyQuery = SQL_QUERIES.STUDY_STATS_QUERY;
      let studyQueryParams = [language, startDayNumber, currentDayNumber];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        studyQuery = studyQuery.replace(
          "AND r.del = 0", 
          "AND c.deckId = ? AND r.del = 0" 
        );
        studyQueryParams.push(deckId);
      }
      
      let passRateQuery = SQL_QUERIES.PASS_RATE_QUERY;
      let passRateQueryParams = [language, startDayNumber, currentDayNumber];
      
      if (deckId !== SETTINGS.DEFAULT_DECK_ID) {
        passRateQuery = passRateQuery.replace(
          "AND r.del = 0", 
          "AND c.deckId = ? AND r.del = 0" 
        );
        passRateQueryParams.push(deckId);
      }
      
      const studyResults = dbInstance.exec(studyQuery, studyQueryParams);
      const passRateResults = dbInstance.exec(passRateQuery, passRateQueryParams);
      
      if (studyResults.length > 0 && studyResults[0].values.length > 0) {
        logFn("Study stats query results:", studyResults[0]);
        
        const days_studied = studyResults[0].values[0][0] || 0;
        const total_reviews = studyResults[0].values[0][1] || 0;
        const avg_reviews_per_day = studyResults[0].values[0][2] || 0;

        let denominator;
        if (period === "All" && earliestReviewDayForAllTime !== null && days_studied > 0) {
          denominator = currentDayNumber - earliestReviewDayForAllTime + 1;
        } else {
           denominator = Math.max(1, periodDays);
        }

        logFn(`Days studied: ${days_studied}, Total reviews: ${total_reviews}, Avg reviews per day: ${avg_reviews_per_day}, Period days (calculated): ${periodDays}, Denominator for %: ${denominator}`);
        
        const daysStudiedPercent = Math.round((days_studied / denominator) * 100);
        
        let pass_rate = 0;
        if (passRateResults.length > 0 && passRateResults[0].values.length > 0) {
          const successful_reviews = passRateResults[0].values[0][0] || 0;
          const failed_reviews = passRateResults[0].values[0][1] || 0;
          const total_answered_reviews = successful_reviews + failed_reviews;
          
          if (total_answered_reviews > 0) {
            pass_rate = Math.round((successful_reviews / total_answered_reviews) * 100);
          }
          
          logFn(`Pass rate calculation: ${successful_reviews} successful of ${total_answered_reviews} total = ${pass_rate}%`);
        }
        
        return {
          days_studied,
          days_studied_percent: daysStudiedPercent,
          total_reviews,
          avg_reviews_per_day,
          period_days: periodDays,
          pass_rate
        };
      } else {
        logFn("Study stats query returned no results");
        return {
          days_studied: 0,
          days_studied_percent: 0,
          total_reviews: 0,
          avg_reviews_per_day: 0,
          period_days: periodDays,
          pass_rate: 0
        };
      }
    } catch (error) {
      logFn("Error fetching study stats:", error);
      return null;
    }
  }

  async function accessMigakuData(db, vueInstance = null) {
    if (!db.objectStoreNames.contains(objectStoreName)) {
      extensionLog(
        `Object store "${objectStoreName}" not found in database "${dbName}".`
      );
      extensionLog(
        `Available stores: ${Array.from(db.objectStoreNames).join(", ")}`
      );
      
      const errorMessage = `Object store '${objectStoreName}' not found.`;
      
      if (vueInstance) {
        vueInstance.updateData({
          wordStats: null,
          dueStats: null,
          intervalStats: null,
          reviewStats: null,
          studyStats: null,
          message: errorMessage,
          isError: true
        });
        return;
      }
      
      await displayCustomStats(
        "Error",
        errorMessage
      ).catch((e) => extensionLog("Failed to display object store error", e));
      return;
    }

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([objectStoreName], "readonly");
      const objectStore = transaction.objectStore(objectStoreName);
      const getAllRequest = objectStore.getAll();

      transaction.oncomplete = () => {
        extensionLog("Read transaction completed.");
      };

      transaction.onerror = (event) => {
        extensionLog("Read transaction error:", event.target.error);
        
        const errorMessage = `Error reading DB: ${event.target.error}`;
        
        if (vueInstance) {
          vueInstance.updateData({
            wordStats: null,
            dueStats: null,
            intervalStats: null,
            reviewStats: null,
            studyStats: null,
            message: errorMessage,
            isError: true
          });
          reject(event.target.error);
          return;
        }
        
        displayCustomStats(errorMessage).catch(
          (e) => extensionLog("Failed to display DB read error", e)
        );
        reject(event.target.error);
      };

      getAllRequest.onerror = (event) => {
        extensionLog("Error getting record:", event.target.error);
      };

      getAllRequest.onsuccess = async (event) => {
        try {
          const allRecords = event.target.result;
          extensionLog(
            `Retrieved ${allRecords.length} raw records from ${objectStoreName}.`
          );

          if (
            !allRecords ||
            allRecords.length === 0 ||
            !allRecords[0].data ||
            !(allRecords[0].data instanceof Uint8Array)
          ) {
            extensionLog(
              `Expected record structure not found in ${objectStoreName}. Check the data structure.`
            );
            
            const errorMessage = "Could not find SQLite data blob.";
            
            if (vueInstance) {
              vueInstance.updateData({
                wordStats: null,
                dueStats: null,
                intervalStats: null,
                reviewStats: null,
                studyStats: null,
                message: errorMessage,
                isError: true
              });
              resolve();
              return;
            }
            
            await displayCustomStats(
              "Error",
              errorMessage
            ).catch((e) =>
              extensionLog("Failed to display structure error", e)
            );
            resolve();
            return;
          }

          let dbFileBlob = allRecords[0].data;
          
          dbFileBlob = decompressData(dbFileBlob, extensionLog);
          if (!dbFileBlob) {
            const errorMessage = "Gzip decompression failed.";
            
            if (vueInstance) {
              vueInstance.updateData({
                wordStats: null,
                dueStats: null,
                intervalStats: null,
                reviewStats: null,
                studyStats: null,
                message: errorMessage,
                isError: true
              });
              resolve();
              return;
            }
            
            await displayCustomStats(
              "Error",
              errorMessage
            ).catch((e) =>
              extensionLog("Failed to display decompression error", e)
            );
            resolve();
            return;
          }
          
          const SQL = await initializeSqlEngine(extensionLog);
          if (!SQL) {
            const errorMessage = "Failed to load sql.js engine.";
            
            if (vueInstance) {
              vueInstance.updateData({
                wordStats: null,
                dueStats: null,
                intervalStats: null,
                reviewStats: null,
                studyStats: null,
                message: errorMessage,
                isError: true
              });
              resolve();
              return;
            }
            
            await displayCustomStats(
              "Error",
              errorMessage
            ).catch((e) =>
              extensionLog("Failed to display sql.js init error", e)
            );
            resolve();
            return;
          }

          let dbInstance = null;
          
          try {
            extensionLog("Loading database into sql.js...");
            dbInstance = new SQL.Database(dbFileBlob);
            extensionLog("Database loaded successfully.");
            extensionLog("Executing SQL queries...");
            
            let currentDateString = null;
            let currentDate = new Date();
            currentDate.setHours(12, 0, 0, 0);
            let currentDayNumber = 0;
            const startDate = new Date(CHART_CONFIG.START_YEAR, CHART_CONFIG.START_MONTH, CHART_CONFIG.START_DAY, 12, 0, 0, 0);
            
            try {
              const dateResult = dbInstance.exec(SQL_QUERIES.CURRENT_DATE_QUERY);
              if (dateResult.length > 0 && dateResult[0].values.length > 0 && dateResult[0].values[0][0]) {
                currentDateString = dateResult[0].values[0][0];
                currentDate = new Date(currentDateString + 'T00:00:00');
                extensionLog("Fetched current date from DB:", currentDateString);
              } else {
                 extensionLog("Could not fetch current date from DB, falling back to system time.");
              }
            } catch (dateError) {
              extensionLog("Error fetching current date from DB, falling back to system time:", dateError);
            }
            currentDayNumber = Math.floor((currentDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
            
            const decks = fetchDecks(dbInstance, appState.selectedLanguage, extensionLog);
            dbState.availableDecks = decks;
            
            const wordValues = fetchWordStats(
              dbInstance, 
              appState.selectedLanguage, 
              appState.selectedDeckId, 
              extensionLog
            );
            
            const dueData = fetchDueStats(
              dbInstance, 
              appState.selectedLanguage, 
              appState.selectedDeckId, 
              extensionLog,
              currentDate,
              currentDayNumber,
              vueInstance ? vueInstance.selectedDuePeriod : "dueStats1"
            );
            
            const intervalData = fetchIntervalStats(
              dbInstance, 
              appState.selectedLanguage, 
              appState.selectedDeckId, 
              extensionLog,
              vueInstance ? vueInstance.selectedPercentile : "intervalPercentile75"
            );
            
            const reviewHistoryData = fetchReviewHistory(
              dbInstance,
              appState.selectedLanguage,
              appState.selectedDeckId,
              extensionLog,
              currentDate,
              currentDayNumber,
              vueInstance ? vueInstance.selectedPeriodReviewHistory : "reviewHistory1",
              vueInstance ? vueInstance.selectedReviewGrouping : "Days"
            );
            
            const studyStatsData = fetchStudyStats(
              dbInstance,
              appState.selectedLanguage,
              appState.selectedDeckId,
              extensionLog,
              currentDayNumber,
              vueInstance ? vueInstance.selectedPeriodStudyStats : "studyStats1"
            );

            dbState.lastWordStats = wordValues;
            dbState.lastDueStats = dueData;
            dbState.lastIntervalStats = intervalData;
            dbState.lastReviewStats = reviewHistoryData;
            dbState.lastStudyStats = studyStatsData;

            if (vueInstance) {
              vueInstance.updateData({
                wordStats: wordValues,
                dueStats: dueData,
                intervalStats: intervalData,
                reviewStats: reviewHistoryData,
                studyStats: studyStatsData,
                availableDecks: dbState.availableDecks,
                isError: false,
                message: ""
              });
              resolve();
              return;
            }

            if (wordValues || (dueData && dueData.labels.length > 0) || (intervalData && intervalData.labels.length > 0)) {
              await displayCustomStats({
                wordStats: wordValues,
                dueStats: dueData,
                intervalStats: intervalData,
                reviewStats: reviewHistoryData,
                studyStats: studyStatsData
              });
            } else {
              extensionLog(
                "All queries returned no usable data. Displaying message."
              );
              await displayCustomStats(
                UI_TEXTS.NO_DATA_MESSAGE
              );
            }
          } catch (error) {
            extensionLog("Error during SQL execution/processing:", error);
            dbState.lastWordStats = null;
            dbState.lastDueStats = null;
            dbState.lastIntervalStats = null;
            dbState.lastReviewStats = null;
            dbState.lastStudyStats = null;
            
            if (vueInstance) {
              vueInstance.updateData({
                wordStats: null,
                dueStats: null,
                intervalStats: null,
                reviewStats: null,
                studyStats: null,
                message: `Processing error: ${error.message}`,
                isError: true
              });
              resolve();
              return;
            }
            
            await displayCustomStats(`Processing error: ${error.message}`);
          } finally {
            if (dbInstance) {
              extensionLog("Closing SQL.js database instance.");
              dbInstance.close();
            }
            resolve();
          }
        } catch (outerError) {
          extensionLog("Error in getAllRequest.onsuccess:", outerError);
          dbState.lastWordStats = null;
          dbState.lastDueStats = null;
          dbState.lastIntervalStats = null;
          dbState.lastReviewStats = null;
          dbState.lastStudyStats = null;
          
          if (vueInstance) {
            vueInstance.updateData({
              wordStats: null,
              dueStats: null,
              intervalStats: null,
              reviewStats: null,
              studyStats: null,
              message: `Internal script error: ${outerError.message}`,
              isError: true
            });
            resolve();
            return;
          }
          
          await displayCustomStats(
            `Internal script error: ${outerError.message}`
          );
          resolve();
        }
      };
    });
  }

  // =========================================================================
  // Vue Components
  // =========================================================================

  const DropdownMenu = {
    props: {
      items: {
        type: Array,
        required: true
      },
      modelValue: {
        type: [String, Number, Object],
        default: null
      },
      itemKey: {
        type: String,
        default: 'id'
      },
      itemLabel: {
        type: [String, Function],
        default: 'name'
      },
      placeholder: {
        type: String,
        default: 'Select an option'
      },
      width: {
        type: Number,
        default: 250
      },
      componentHash: String
    },
    emits: ['update:modelValue'],
    data() {
      return {
        isDropdownOpen: false
      };
    },
    computed: {
      selectedItemLabel() {
        const selectedItem = this.items.find(item => this.getItemKey(item) === this.modelValue);
        return selectedItem ? this.getItemLabel(selectedItem) : this.placeholder;
      }
    },
    methods: {
      toggleDropdown(event) {
        event.stopPropagation();
        this.isDropdownOpen = !this.isDropdownOpen;
      },
      selectItem(item, event) {
        event.stopPropagation();
        const itemKey = this.getItemKey(item);
        if (this.modelValue !== itemKey) {
          this.$emit('update:modelValue', itemKey);
        }
        this.isDropdownOpen = false;
      },
      closeDropdown() {
        this.isDropdownOpen = false;
      },
      getItemKey(item) {
        return item[this.itemKey];
      },
      getItemLabel(item) {
        if (typeof this.itemLabel === 'function') {
          return this.itemLabel(item);
        }
        return item[this.itemLabel];
      }
    },
    mounted() {
      document.addEventListener('click', this.closeDropdown);
    },
    beforeUnmount() {
      document.removeEventListener('click', this.closeDropdown);
    },
    template: `
    <div 
      v-bind:[componentHash]="true" 
      tabindex="0" 
      class="multiselect multiselect--right" 
      :class="{ '-has-value': modelValue !== null, 'multiselect--active': isDropdownOpen }" 
      role="combobox" 
      :style="{ width: width + 'px' }" 
      @click="toggleDropdown"
    >
      <div class="UiIcon multiselect__caret" style="width: 24px;">
        <div class="UiIcon__inner">
          <div class="UiSvg UiIcon__svg" name="ChevronDownSmall" gradient="false" spin="false">
            <div class="UiSvg__inner">
              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" role="img">
                <path fill="currentColor" fill-rule="evenodd" d="M7.116 10.116a1.25 1.25 0 0 1 1.768 0L12 13.232l3.116-3.116a1.25 1.25 0 0 1 1.768 1.768l-4 4a1.25 1.25 0 0 1-1.768 0l-4-4a1.25 1.25 0 0 1 0-1.768" clip-rule="evenodd"></path>
              </svg>
            </div>
          </div>
        </div>
      </div>
      <div class="multiselect__tags">
        <slot name="trigger" :selectedLabel="selectedItemLabel">
          <span class="multiselect__single">
            <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">{{ selectedItemLabel }}</span>
          </span>
        </slot>
      </div>
      <div 
        class="multiselect__content-wrapper" 
        tabindex="-1" 
        style="max-height: 300px;" 
        :style="{ display: isDropdownOpen ? 'block' : 'none' }"
      >
        <ul class="multiselect__content" role="listbox" style="display: inline-block;">
          <li class="multiselect__element" role="option" v-for="item in items" :key="getItemKey(item)">
            <span 
              class="multiselect__option" 
              :class="{ 'multiselect__option--highlight multiselect__option--selected': getItemKey(item) === modelValue }" 
              @click="selectItem(item, $event)"
            >
              <slot name="item" :item="item" :isSelected="getItemKey(item) === modelValue">
                <div class="multiselect__optionWrapper" :style="{ width: width - 40 + 'px' }" >
                  <span 
                    class="UiTypo UiTypo__caption" 
                    :class="{ '-emphasis': getItemKey(item) === modelValue }" 
                    style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
                  >
                    {{ getItemLabel(item) }}
                  </span>
                  <div class="UiIcon multiselect__checkIcon" style="width: 24px;">
                    <div v-if="getItemKey(item) === modelValue" class="UiIcon__inner">
                      <div class="UiSvg UiIcon__svg" name="Check" gradient="true" spin="false">
                         <div class="UiSvg__inner UiIcon__gradient" :style="'clip-path: url(#checkmark-dd-' + getItemKey(item) + ');'">
                           <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" role="img">
                            <defs>
                              <clipPath :id="'checkmark-dd-' + getItemKey(item)" data-dont-prefix-id="" transform="scale(1)">
                                <path fill="currentColor" fill-rule="evenodd" d="M19.83 7.066a1.25 1.25 0 0 1 .104 1.764l-8 9a1.25 1.25 0 0 1-1.818.054l-5-5a1.25 1.25 0 0 1 1.768-1.768l4.063 4.063 7.119-8.01a1.25 1.25 0 0 1 1.765-.103" clip-rule="evenodd">
                                </path>
                              </clipPath>
                            </defs>
                          </svg>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </slot>
            </span>
          </li>
        </ul>
      </div>
    </div>
    `
  };

  const RadioButtonGroup = {
    props: {
      options: {
        type: Array,
        required: true,
      },
      modelValue: {
        type: String,
        required: true,
      },
      componentHash: String,
      name: {
        type: String,
        default: 'radio-group'
      }
    },
    emits: ['update:modelValue'],
    methods: {
      handleChange(event) {
        this.$emit('update:modelValue', event.target.value);
      }
    },
    template: `
    <div v-bind:[componentHash]="true" class="MCS__radio-group" role="radiogroup">
      <div 
        v-for="option in options" 
        :key="option.id" 
        class="UiCheckbox -radio" 
        :class="{ '-toggled': modelValue === option.id }"
        v-bind:[componentHash]="true"
      >
        <div class="UiCheckbox__input">
          <input 
            type="radio" 
            :name="name" 
            :id="name + '-' + option.id" 
            :value="option.id" 
            :checked="modelValue === option.id" 
            @change="handleChange" 
            class="UiCheckbox__input__element"
            v-bind:[componentHash]="true"
          >
          <div class="UiCheckbox__icon__container">
            <div
              class="UiCheckbox__icon MCS__radio-button"
              :class="{ '-toggled': modelValue === option.id }"
            >
              <svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" />
              </svg>
            </div>
          </div>
        </div>
        <label :for="name + '-' + option.id" class="UiCheckbox__label UiTypo UiTypo__caption">
          {{ option.name }}
        </label>
      </div>
    </div>
    `
  };

  const DeckSelector = {
    components: { DropdownMenu },
    props: {
      availableDecks: Array,
      selectedDeckId: String,
      componentHash: String
    },
    emits: ['deck-selected'],
    methods: {
      handleDeckUpdate(newDeckId) {
        if (this.selectedDeckId !== newDeckId) {
          this.$emit('deck-selected', newDeckId);
        }
      }
    },
    template: `
    <div v-bind:[componentHash]="true" class="MCS__deck-selector UiFormField SettingsGeneral__optionLeft">
      <div class="UiFormField__labelContainer">
        <label v-bind:[componentHash]="true" class="UiTypo UiTypo__body UiFormField__labelContainer__typo">Deck</label>
      </div>
      <div class="UiFormField__controlContainer">
        <dropdown-menu
          :items="availableDecks"
          :modelValue="selectedDeckId"
          @update:modelValue="handleDeckUpdate"
          item-key="id"
          item-label="name"
          placeholder="Select Deck"
          :width="250"
          :component-hash="componentHash"
        >
          <template #trigger="{ selectedLabel }">
            <span class="multiselect__single">
              <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">{{ selectedLabel }}</span>
            </span>
          </template>
        </dropdown-menu>
      </div>
    </div>
    `
  };

  const WordStatsCard = {
    props: {
      wordStats: Object,
      componentHash: String,
      chartRef: String
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    updated() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    beforeUnmount() {
      this.$emit('canvas-unmounted');
    },
    template: `
      <div v-if="wordStats" v-bind:[componentHash]="true" class="MCS__word-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Word Status</h3>
        </div>
        <div v-bind:[componentHash]="true" class="MCS__wordcount">
          <div v-bind:[componentHash]="true" class="MCS__wordcount__details">
            <div v-bind:[componentHash]="true" class="MCS__wordcount__details__column">
              <div v-bind:[componentHash]="true">
                <span class="UiTypo UiTypo__caption">Known:</span> 
                <span class="UiTypo UiTypo__heading4 -heading -inline">{{ wordStats.known_count ?? 'N/A' }}</span>
              </div>
              <div v-bind:[componentHash]="true">
                <span class="UiTypo UiTypo__caption">Learning:</span> 
                <span class="UiTypo UiTypo__heading4 -heading -inline">{{ wordStats.learning_count ?? 'N/A' }}</span>
              </div>
              <div v-bind:[componentHash]="true">
                <span class="UiTypo UiTypo__caption">Unknown:</span> 
                <span class="UiTypo UiTypo__heading4 -heading -inline">{{ wordStats.unknown_count ?? 'N/A' }}</span>
              </div>
              <div v-bind:[componentHash]="true">
                <span class="UiTypo UiTypo__caption">Ignored:</span> 
                <span class="UiTypo UiTypo__heading4 -heading -inline">{{ wordStats.ignored_count ?? 'N/A' }}</span>
              </div>
            </div>
          </div>
          <div v-bind:[componentHash]="true" class="MCS__wordcount__piechart">
            <canvas ref="canvas"></canvas>
          </div>
        </div>
      </div>
      <div v-else v-bind:[componentHash]="true" class="MCS__word-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Word Status</h3>
        </div>
        <p v-bind:[componentHash]="true" class="UiTypo UiTypo__body2">Could not load word status data.</p>
      </div>
    `
  };

  const DueStatsCard = {
    props: {
      dueStats: Object,
      componentHash: String,
      chartRef: String,
      selectedPeriod: {
        type: String,
        default: "dueStats1"
      }
    },
    components: {
      DropdownMenu
    },
    data() {
      return {
        periodOptions: [
          { id: "dueStats1", name: '1 Month' },
          { id: "dueStats2", name: '2 Months' },
          { id: "dueStats3", name: '3 Months' },
          { id: "dueStats6", name: '6 Months' },
          { id: "dueStats12", name: '12 Months' },
          { id: "dueStatsAll", name: 'All time' }
        ]
      };
    },
    computed: {
      currentPeriod: {
        get() {
          return this.selectedPeriod;
        },
        set(value) {
          this.$emit('period-change', value);
        }
      },
      forecastDays() {
        const selectedOption = this.periodOptions.find(p => p.id === this.currentPeriod);
        return selectedOption ? selectedOption.days : 30;
      }
    },
    methods: {
      handlePeriodChange(period) {
        this.currentPeriod = period;
      }
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    updated() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    beforeUnmount() {
      this.$emit('canvas-unmounted');
    },
    template: `
      <div v-if="dueStats && dueStats.labels && dueStats.counts" v-bind:[componentHash]="true" class="MCS__due-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Cards Due</h3>
          <div class="MCS__header-selector">
            <dropdown-menu
              :items="periodOptions"
              :modelValue="currentPeriod"
              @update:modelValue="handlePeriodChange"
              item-key="id"
              item-label="name"
              placeholder="Select Period"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span
                    v-if="selectedLabel !== 'All time'"
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    Next {{ selectedLabel }}
                  </span>
                  <span
                    v-else
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    All time
                  </span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <div v-bind:[componentHash]="true" class="MCS__duechart">
          <canvas ref="canvas"></canvas>
        </div>
      </div>
      <div v-else v-bind:[componentHash]="true" class="MCS__due-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Cards Due</h3>
           <div class="MCS__header-selector">
            <dropdown-menu
              :items="periodOptions"
              :modelValue="currentPeriod"
              @update:modelValue="handlePeriodChange"
              item-key="id"
              item-label="name"
              placeholder="Select Period"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">Next {{ selectedLabel }}</span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <p v-bind:[componentHash]="true" class="UiTypo UiTypo__body2">Could not load due card data.</p>
      </div>
    `
  };

  const MessageCard = {
    props: {
      isError: Boolean,
      message: String,
      componentHash: String
    },
    template: `
      <div v-bind:[componentHash]="true" class="Statistic__card__header">
        <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">
          {{ isError ? '${UI_TEXTS.ERROR_TITLE}' : '${UI_TEXTS.INFO_TITLE}' }}
        </h3>
      </div>
      <div v-bind:[componentHash]="true" class="MCS__message">
        <span v-bind:[componentHash]="true" class="UiTypo UiTypo__body1">{{ message }}</span>
      </div>
    `
  };

  const IntervalStatsCard = {
    props: {
      intervalStats: Object,
      componentHash: String,
      chartRef: String
    },
    components: {
      DropdownMenu
    },
    data() {
      return {
        selectedPercentile: "intervalPercentile75",
        percentileOptions: [
          { id: "intervalPercentile50", name: '50th' },
          { id: "intervalPercentile75", name: '75th' },
          { id: "intervalPercentile95", name: '95th' },
          { id: "intervalPercentile100", name: '100th' }
        ]
      };
    },
    methods: {
      handlePercentileChange(value) {
        this.selectedPercentile = value;
        this.$emit('percentile-changed', value);
      }
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    updated() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    beforeUnmount() {
      this.$emit('canvas-unmounted');
    },
    template: `
      <div v-if="intervalStats && intervalStats.labels && intervalStats.counts" v-bind:[componentHash]="true" class="MCS__interval-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Review Intervals</h3>
          <div class="MCS__header-selector">
            <dropdown-menu
              :items="percentileOptions"
              :modelValue="selectedPercentile"
              @update:modelValue="handlePercentileChange"
              item-key="id"
              item-label="name"
              placeholder="Select Percentile"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">{{ selectedLabel }} Percentile</span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <div v-bind:[componentHash]="true" class="MCS__intervalchart">
          <canvas ref="canvas"></canvas>
        </div>
      </div>
      <div v-else v-bind:[componentHash]="true" class="MCS__interval-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Review Intervals</h3>
          <div class="MCS__header-selector">
            <dropdown-menu
              :items="percentileOptions"
              :modelValue="selectedPercentile"
              @update:modelValue="handlePercentileChange"
              item-key="id"
              item-label="name"
              placeholder="Select Percentile"
              :width="120"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">{{ selectedLabel }} Percentile</span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <p v-bind:[componentHash]="true" class="UiTypo UiTypo__body2">Could not load review interval data.</p>
      </div>
    `
  };
  
  const StudyStatsCard = {
    components: {
      DropdownMenu
    },
    props: {
      studyStats: Object,
      componentHash: String
    },
    data() {
      return {
        selectedPeriod: "studyStats1",
        periodOptions: [
          { id: "studyStats1", name: '1 Month' },
          { id: "studyStats2", name: '2 Months' },
          { id: "studyStats3", name: '3 Months' },
          { id: "studyStats6", name: '6 Months' },
          { id: "studyStats12", name: '12 Months' },
          { id: "studyStatsAll", name: 'All time' }
        ]
      };
    },
    methods: {
      handlePeriodChange(period) {
        this.selectedPeriod = period;
        this.$emit('period-change', period);
      }
    },
    template: `
      <div v-if="studyStats" v-bind:[componentHash]="true" class="MCS__study-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Study Statistics</h3>
          <div class="MCS__header-selector">
            <dropdown-menu
              :items="periodOptions"
              :modelValue="selectedPeriod"
              @update:modelValue="handlePeriodChange"
              item-key="id"
              item-label="name"
              placeholder="Select Period"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span
                    v-if="selectedLabel !== 'All time'"
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    Last {{ selectedLabel }}
                  </span>
                  <span
                    v-else
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    All time
                  </span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <div v-bind:[componentHash]="true" class="MCS__study-stats">
          <div v-bind:[componentHash]="true" class="MCS__stat-box">
            <div v-bind:[componentHash]="true" class="MCS__stat-value">{{ studyStats.days_studied_percent }}%</div>
            <div v-bind:[componentHash]="true" class="MCS__stat-label">of days studied</div>
          </div>
          <div v-bind:[componentHash]="true" class="MCS__stat-box">
            <div v-bind:[componentHash]="true" class="MCS__stat-value">{{ studyStats.pass_rate }}%</div>
            <div v-bind:[componentHash]="true" class="MCS__stat-label">Pass rate</div>
          </div>
          <div v-bind:[componentHash]="true" class="MCS__stat-box">
            <div v-bind:[componentHash]="true" class="MCS__stat-value">{{ studyStats.total_reviews.toLocaleString() }}</div>
            <div v-bind:[componentHash]="true" class="MCS__stat-label">Total reviews</div>
          </div>
          <div v-bind:[componentHash]="true" class="MCS__stat-box">
            <div v-bind:[componentHash]="true" class="MCS__stat-value">{{ studyStats.avg_reviews_per_day }}</div>
            <div v-bind:[componentHash]="true" class="MCS__stat-label">Avg. reviews per study day</div>
          </div>
        </div>
      </div>
      <div v-else v-bind:[componentHash]="true" class="MCS__study-stats-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Study Statistics</h3>
        </div>
        <p v-bind:[componentHash]="true" class="UiTypo UiTypo__body2">Could not load study statistics data.</p>
      </div>
    `
  };
  
  const ReviewHistoryCard = {
    components: {
      DropdownMenu,
      RadioButtonGroup
    },
    props: {
      reviewStats: Object,
      componentHash: String,
      chartRef: String,
      selectedPeriod: {
        type: String,
        default: "reviewHistory1"
      },
      selectedGrouping: {
        type: String,
        default: "Days"
      }
    },
    data() {
      return {
        periodOptions: [
          { id: "reviewHistory1", name: '1 Month' },
          { id: "reviewHistory2", name: '2 Months' },
          { id: "reviewHistory3", name: '3 Months' },
          { id: "reviewHistory6", name: '6 Months' },
          { id: "reviewHistory12", name: '12 Months' },
          { id: "reviewHistoryAll", name: 'All time' }
        ],
        groupingOptions: [
          { id: "Days", name: 'Days' },
          { id: "Weeks", name: 'Weeks' },
          { id: "Months", name: 'Months' }
        ]
      };
    },
    computed: {
      currentPeriod: {
        get() {
          return this.selectedPeriod;
        },
        set(value) {
          this.$emit('period-change', value);
        }
      },
      currentGrouping: {
        get() {
          return this.selectedGrouping;
        },
        set(value) {
          this.$emit('grouping-change', value);
        }
      }
    },
    methods: {
      handlePeriodChange(period) {
        this.currentPeriod = period;
      },
      handleGroupingChange(grouping) {
        this.currentGrouping = grouping;
      }
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    updated() {
      this.$nextTick(() => {
        if (this.$refs.canvas) {
          this.$emit('canvas-mounted', this.$refs.canvas);
        }
      });
    },
    beforeUnmount() {
      this.$emit('canvas-unmounted');
    },
    template: `
      <div v-if="reviewStats && reviewStats.labels && reviewStats.counts" v-bind:[componentHash]="true" class="MCS__review-history-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Review History</h3>
          <div class="MCS__header-selector">
            <dropdown-menu
              :items="periodOptions"
              :modelValue="currentPeriod"
              @update:modelValue="handlePeriodChange"
              item-key="id"
              item-label="name"
              placeholder="Select Period"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span
                    v-if="selectedLabel !== 'All time'"
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    Last {{ selectedLabel }}
                  </span>
                  <span
                    v-else
                    class="UiTypo UiTypo__caption -no-wrap multiselect__single__text"
                  >
                    All time
                  </span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <radio-button-group
          style="margin-right: 8px;"
          :options="groupingOptions"
          :modelValue="currentGrouping"
          @update:modelValue="handleGroupingChange"
          name="review-grouping"
          :component-hash="componentHash"
        />
        <div v-bind:[componentHash]="true" class="MCS__reviewchart">
          <canvas ref="canvas"></canvas>
        </div>
      </div>
      <div v-else v-bind:[componentHash]="true" class="MCS__review-history-card">
        <div v-bind:[componentHash]="true" class="Statistic__card__header">
          <h3 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading3 -heading">Review History</h3>
          <div class="MCS__header-selector">
            <radio-button-group
              style="margin-right: 8px;"
              :options="groupingOptions"
              :modelValue="currentGrouping"
              @update:modelValue="handleGroupingChange"
              name="review-grouping-fallback"
              :component-hash="componentHash"
            />
            <dropdown-menu
              :items="periodOptions"
              :modelValue="currentPeriod"
              @update:modelValue="handlePeriodChange"
              item-key="id"
              item-label="name"
              placeholder="Select Period"
              :width="180"
              :component-hash="componentHash"
            >
              <template #trigger="{ selectedLabel }">
                <span class="multiselect__single">
                  <span class="UiTypo UiTypo__caption -no-wrap multiselect__single__text">Last {{ selectedLabel }}</span>
                </span>
              </template>
            </dropdown-menu>
          </div>
        </div>
        <p v-bind:[componentHash]="true" class="UiTypo UiTypo__body2">Could not load review history data.</p>
      </div>
    `
  };

  /**
   * Prepares and displays the Vue component with stats data
   * 
   * @param {Object|string} data - Stats data or error message
   * @returns {Object|null} - Vue app instance or null
   */
  async function displayCustomStats(data) {
    extensionLog("Preparing to display Vue application...");
    
    const statisticsDiv = await waitForElement(statisticsElementSelector);
    if (!statisticsDiv) {
      extensionLog("Statistics element not found, cannot display stats.");
      return null;
    }

    const componentHash = statisticsDiv.attributes[0].nodeName;

    const statsContainer = await waitForElement(targetElementSelector);
    if (!statsContainer) {
      extensionLog("Target container not found, cannot display stats.");
      return null;
    }
    extensionLog("Target element confirmed for display:", targetElementSelector);

    const oldVueContainer = statsContainer.querySelector(`#${SELECTORS.VUE_CONTAINER_ID}`);
    if (oldVueContainer) oldVueContainer.remove();

    const isError = typeof data === "string" || data instanceof Error;
    let wordStats = null;
    let dueStats = null;
    let intervalStats = null;
    let message = "";

    if (isError) {
      message = data instanceof Error ? `Error: ${data.message}` : data;
      extensionLog(`Displaying message: ${message}`);
      if (!(data && data.hasOwnProperty("wordStats"))) {
        dbState.lastWordStats = null;
        dbState.lastDueStats = null;
        dbState.lastIntervalStats = null;
        dbState.lastReviewStats = null;
        dbState.lastStudyStats = null;
      }
    } else if (data && (data.wordStats || data.dueStats || data.intervalStats)) {
      wordStats = data.wordStats;
      dueStats = data.dueStats;
      intervalStats = data.intervalStats;
      if (data !== dbState.lastWordStats) {
        dbState.lastWordStats = wordStats;
        dbState.lastDueStats = dueStats;
        dbState.lastIntervalStats = intervalStats;
      }
      extensionLog("Displaying data:", { wordStats, dueStats, intervalStats });
    } else {
      message = UI_TEXTS.LOADING_MESSAGE;
      extensionLog(message);
      dbState.lastWordStats = null;
      dbState.lastDueStats = null;
      dbState.lastIntervalStats = null;
      dbState.lastReviewStats = null;
      dbState.lastStudyStats = null;
    }

    const vueContainer = document.createElement('div');
    vueContainer.id = SELECTORS.VUE_CONTAINER_ID;
    statsContainer.appendChild(vueContainer);

    const app = Vue.createApp({
      components: {
        DropdownMenu,
        DeckSelector,
        WordStatsCard,
        DueStatsCard,
        MessageCard,
        IntervalStatsCard,
        ReviewHistoryCard,
        StudyStatsCard,
        RadioButtonGroup
      },
      data() {
        return {
          wordStats,
          dueStats,
          intervalStats,
          reviewStats: data && data.reviewStats || null,
          studyStats: data && data.studyStats || null,
          isError,
          message,
          availableDecks: dbState.availableDecks,
          selectedDeckId: appState.selectedDeckId,
          selectedLanguage: appState.selectedLanguage,
          selectedPercentile: "intervalPercentile75",
          selectedPeriodStudyStats: "studyStats1",
          selectedPeriodReviewHistory: "reviewHistory1",
          selectedDuePeriod: "dueStats1",
          selectedReviewGrouping: "Days",
          componentHash,
          currentTheme: getCurrentTheme(),
          wordChartRendered: false,
          dueChartRendered: false,
          intervalChartRendered: false,
          reviewChartRendered: false,
          wordCanvas: null,
          dueCanvas: null,
          intervalCanvas: null,
          reviewCanvas: null
        };
      },
      methods: {
        handleDeckSelected(deckId) {
          if (this.selectedDeckId === deckId) {
            return;
          }
          
          this.selectedDeckId = deckId; 
          appState.selectedDeckId = deckId;
          
          runFilteredStatsQuery(this);
        },
        handleWordCanvasMounted(canvas) {
          this.wordCanvas = canvas;
          this.updateCharts();
        },
        handleDueCanvasMounted(canvas) {
          this.dueCanvas = canvas;
          this.updateCharts();
        },
        handleIntervalCanvasMounted(canvas) {
          this.intervalCanvas = canvas;
          this.updateCharts();
        },
        handleReviewCanvasMounted(canvas) {
          this.reviewCanvas = canvas;
          this.updateCharts();
        },
        handleWordCanvasUnmounted() {
          extensionLog("Word canvas unmounted, cleaning up");
          this.wordCanvas = null;
          this.wordChartRendered = false;
        },
        handleDueCanvasUnmounted() {
          extensionLog("Due canvas unmounted, cleaning up");
          this.dueCanvas = null;
          this.dueChartRendered = false;
        },
        handleIntervalCanvasUnmounted() {
          extensionLog("Interval canvas unmounted, cleaning up");
          this.intervalCanvas = null;
          this.intervalChartRendered = false;
        },
        handleReviewCanvasUnmounted() {
          extensionLog("Review canvas unmounted, cleaning up");
          this.reviewCanvas = null;
          this.reviewChartRendered = false;
        },
        updateCharts() {
          this.debouncedUpdateCharts();
        },
        renderCharts() {
          this.renderWordChart();
          this.renderDueChart();
          this.renderIntervalChart();
          this.renderReviewChart();
        },
        renderWordChart() {
          if (this.wordChartRendered || !this.wordCanvas || !this.wordStats) {
            return;
          }
          
          this.wordChartRendered = true;
          
          ChartManager.createWordChart(
            this.wordCanvas,
            this.wordStats,
            extensionLog
          );
        },
        renderDueChart() {
          if (this.dueChartRendered || !this.dueCanvas || !this.dueStats) {
            return;
          }
          
          this.dueChartRendered = true;
          
          ChartManager.createDueChart(
            this.dueCanvas,
            this.dueStats,
            extensionLog
          );
        },
        renderIntervalChart() {
          if (this.intervalChartRendered || !this.intervalCanvas || !this.intervalStats) {
            return;
          }
          
          this.intervalChartRendered = true;
          
          ChartManager.createIntervalChart(
            this.intervalCanvas,
            this.intervalStats,
            extensionLog
          );
        },
        renderReviewChart() {
          if (this.reviewChartRendered || !this.reviewCanvas || !this.reviewStats) {
            return;
          }
          
          this.reviewChartRendered = true;
          
          ChartManager.createReviewHistoryChart(
            this.reviewCanvas,
            this.reviewStats,
            extensionLog
          );
        },
        debouncedUpdateCharts: debounce(function() {
          extensionLog("Debounced chart update triggered");
          
          this.wordChartRendered = false;
          this.dueChartRendered = false;
          this.intervalChartRendered = false;
          this.reviewChartRendered = false;
          
          this.$nextTick(() => {
            ChartManager.updateCharts({
              wordCanvas: this.wordCanvas,
              dueCanvas: this.dueCanvas,
              intervalCanvas: this.intervalCanvas,
              reviewCanvas: this.reviewCanvas,
              wordStats: this.wordStats,
              dueStats: this.dueStats,
              intervalStats: this.intervalStats,
              reviewStats: this.reviewStats,
              logFn: extensionLog,
              onComplete: (results) => {
                this.wordChartRendered = results.wordChartSuccess;
                this.dueChartRendered = results.dueChartSuccess;
                this.intervalChartRendered = results.intervalChartSuccess;
                this.reviewChartRendered = results.reviewChartSuccess;
              }
            });
          });
        }, 300),
        updateChartsWithTheme() {
          this.currentTheme = getCurrentTheme();
          this.debouncedUpdateCharts();
        },
        updateData(newData) {
          extensionLog("Updating Vue component data", newData);
          
          if (newData.wordStats !== undefined) {
            this.wordStats = newData.wordStats;
          }
          if (newData.dueStats !== undefined) {
            this.dueStats = newData.dueStats;
          }
          if (newData.intervalStats !== undefined) {
            this.intervalStats = newData.intervalStats;
          }
          if (newData.reviewStats !== undefined) {
            this.reviewStats = newData.reviewStats;
          }
          if (newData.studyStats !== undefined) {
            this.studyStats = newData.studyStats;
          }
          if (newData.availableDecks !== undefined) {
            this.availableDecks = newData.availableDecks;
          }
          if (newData.selectedDeckId !== undefined) {
            this.selectedDeckId = newData.selectedDeckId;
          }
          if (newData.selectedLanguage !== undefined) {
            this.selectedLanguage = newData.selectedLanguage;
          }
          if (newData.message !== undefined) {
            this.message = newData.message;
          }
          if (newData.isError !== undefined) {
            this.isError = newData.isError;
          }
        },
        handleIntervalPercentileChanged(percentile) {
          if (this.selectedPercentile === percentile) {
            return;
          }
          
          this.selectedPercentile = percentile;
          runFilteredStatsQuery(this);
        },
        handleStudyStatsPeriodChange(period) {
          if (this.selectedPeriodStudyStats === period) {
            return;
          }
          
          this.selectedPeriodStudyStats = period;
          runFilteredStatsQuery(this);
        },
        handleReviewHistoryPeriodChange(period) {
          if (this.selectedPeriodReviewHistory === period) {
            return;
          }
          
          this.selectedPeriodReviewHistory = period;
          runFilteredStatsQuery(this);
        },
        handleReviewHistoryGroupingChange(grouping) {
          if (this.selectedReviewGrouping === grouping) {
             return;
          }
          this.selectedReviewGrouping = grouping;
          runFilteredStatsQuery(this);
        },
        handleDuePeriodChange(period) {
          if (this.selectedDuePeriod === period) {
            return;
          }
          
          this.selectedDuePeriod = period;
          runFilteredStatsQuery(this);
        }
      },
      watch: {
        wordStats: {
          handler() {
            extensionLog("wordStats changed, scheduling chart update");
            this.wordChartRendered = false;
            this.$nextTick(this.debouncedUpdateCharts);
          },
          deep: true
        },
        dueStats: {
          handler() {
            extensionLog("dueStats changed, scheduling chart update");
            this.dueChartRendered = false;
            this.$nextTick(this.debouncedUpdateCharts);
          },
          deep: true
        },
        intervalStats: {
          handler() {
            extensionLog("intervalStats changed, scheduling chart update");
            this.intervalChartRendered = false;
            this.$nextTick(this.debouncedUpdateCharts);
          },
          deep: true
        },
        reviewStats: {
          handler() {
            extensionLog("reviewStats changed, scheduling chart update");
            this.reviewChartRendered = false;
            this.$nextTick(this.debouncedUpdateCharts);
          },
          deep: true
        },
        currentTheme() {
          extensionLog("Theme changed, scheduling chart update");
          this.wordChartRendered = false;
          this.dueChartRendered = false;
          this.intervalChartRendered = false;
          this.reviewChartRendered = false;
          this.$nextTick(this.debouncedUpdateCharts);
        },
        selectedLanguage(newLang, oldLang) {
          if (newLang !== oldLang) {
            extensionLog(`Language changed in component from ${oldLang} to ${newLang}, resetting charts`);
            ChartManager.resetCharts();
            this.wordCanvas = null;
            this.dueCanvas = null;
            this.intervalCanvas = null;
            this.reviewCanvas = null;
            this.wordChartRendered = false;
            this.dueChartRendered = false;
            this.intervalChartRendered = false;
            this.reviewChartRendered = false;
          }
        }
      },
      mounted() {
        extensionLog("Vue component mounted");
        
        extensionLog("Scheduling initial chart creation");
        this.$nextTick(() => {
          this.debouncedUpdateCharts();
        });
      },
      beforeUnmount() {
        ChartManager.destroyCharts();
        this.wordCanvas = null;
        this.dueCanvas = null;
        this.intervalCanvas = null;
        this.reviewCanvas = null;
      },
      template: `
        <div class="MCS__container" :class="{'UiCard -lesson Statistic__card': isError || message}">
          <template v-if="isError || message">
            <message-card 
              :is-error="isError" 
              :message="message" 
              :component-hash="componentHash" 
            />
          </template>
          <template v-else>
            <h2 v-bind:[componentHash]="true" class="UiTypo UiTypo__heading2 -heading Statistic__title">Migaku Custom Stats</h2>
            <div v-bind:[componentHash]="true" class="UiCard -lesson Statistic__card">
              <!-- Deck Selector -->
              <deck-selector 
                :available-decks="availableDecks" 
                :selected-deck-id="selectedDeckId" 
                :component-hash="componentHash"
                @deck-selected="handleDeckSelected" 
              />
              
              <!-- Word Stats -->
              <word-stats-card 
                :word-stats="wordStats" 
                :component-hash="componentHash" 
                chart-ref="wordChart"
                @canvas-mounted="handleWordCanvasMounted"
                @canvas-unmounted="handleWordCanvasUnmounted"
              />
              
              <!-- Due Stats -->
              <due-stats-card 
                :due-stats="dueStats" 
                :component-hash="componentHash" 
                chart-ref="dueChart"
                @canvas-mounted="handleDueCanvasMounted"
                @canvas-unmounted="handleDueCanvasUnmounted"
                :selected-period="selectedDuePeriod"
                @period-change="handleDuePeriodChange"
              />
              
              <!-- Interval Stats -->
              <interval-stats-card 
                :interval-stats="intervalStats" 
                :component-hash="componentHash" 
                chart-ref="intervalChart"
                @canvas-mounted="handleIntervalCanvasMounted"
                @canvas-unmounted="handleIntervalCanvasUnmounted"
                @percentile-changed="handleIntervalPercentileChanged"
              />
              
              <!-- Study Stats -->
              <study-stats-card 
                :study-stats="studyStats" 
                :component-hash="componentHash"
                @period-change="handleStudyStatsPeriodChange"
              />
              
              <!-- Review History -->
              <review-history-card 
                :review-stats="reviewStats"
                :selectedPeriod="selectedPeriodReviewHistory"
                :selectedGrouping="selectedReviewGrouping"
                :component-hash="componentHash"
                chart-ref="reviewChart"
                @canvas-mounted="handleReviewCanvasMounted"
                @canvas-unmounted="handleReviewCanvasUnmounted"
                @period-change="handleReviewHistoryPeriodChange"
                @grouping-change="handleReviewHistoryGroupingChange"
              />
            </div>
          </template>
        </div>
      `
    });

    try {
      extensionLog("Mounting Vue app");
      const vueApp = app.mount('#migaku-custom-stats-vue-container');
      extensionLog("Vue app mounted successfully.");

      if (!isError && !message) {
        extensionLog("Initializing charts");
        vueApp.debouncedUpdateCharts();
      }
      
      appState.themeChangeObserver = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
          if (
            mutation.type === "attributes" &&
            mutation.attributeName === ATTRIBUTES.THEME
          ) {
            extensionLog("Theme changed, updating charts with new theme colors");
            
            if (vueApp) {
              try {
                vueApp.updateChartsWithTheme();
              } catch (themeUpdateError) {
                extensionLog("Error updating charts after theme change:", themeUpdateError);
              }
            }
            
            break;
          }
        }
      });
      
      appState.themeChangeObserver.observe(document.documentElement, {
        attributes: true,
        attributeFilter: [ATTRIBUTES.THEME],
      });
      
      return vueApp;
    } catch (vueError) {
      extensionLog("Error mounting Vue app:", vueError);
      const errorDiv = document.createElement('div');
      errorDiv.id = SELECTORS.ERROR_CONTAINER_ID;
      errorDiv.innerHTML = `<p>Error mounting Vue application: ${vueError.message}</p>`;
      statsContainer.appendChild(errorDiv);
      return null;
    }
  }

  function runFilteredStatsQuery(vueInstance) {
    const selectedDeck = dbState.availableDecks.find((d) => d.id === appState.selectedDeckId);
    const deckName = selectedDeck ? selectedDeck.name : UI_TEXTS.ALL_DECKS;
    extensionLog(`Refreshing data for deck: ${deckName} (ID: ${appState.selectedDeckId})`);

    if (appState.isProcessing) {
      extensionLog("Stats refresh already in progress, skipping.");
      return;
    }

    appState.isProcessing = true;

    initDB()
      .then((db) => accessMigakuData(db, vueInstance))
      .catch((err) => {
        extensionLog("Error refreshing data:", err);
        
        const errorMessage = `Error refreshing statistics: ${err.message}. Please try again later.`;
        
        if (vueInstance) {
          vueInstance.updateData({
            wordStats: null,
            dueStats: null,
            intervalStats: null,
            reviewStats: null,
            studyStats: null,
            message: errorMessage,
            isError: true
          });
        } else {
          displayCustomStats(errorMessage);
        }
      })
      .finally(() => {
        appState.isProcessing = false;
      });
  }

  const originalPushState = history.pushState;
  const originalReplaceState = history.replaceState;

  history.pushState = function () {
    const previousPath = appState.previousRoute;
    const result = originalPushState.apply(this, arguments);
    const currentPath = window.location.pathname;
    appState.previousRoute = currentPath;
    
    extensionLog(`Route changed from ${previousPath} to ${currentPath}`);
    
    if (currentPath === statsRoute && previousPath !== statsRoute) {
      extensionLog("Entering statistics page, resetting charts");
      ChartManager.resetCharts();
    }
    
    setTimeout(runStatsLogic, 0);
    return result;
  };

  history.replaceState = function () {
    const previousPath = appState.previousRoute;
    const result = originalReplaceState.apply(this, arguments);
    const currentPath = window.location.pathname;
    appState.previousRoute = currentPath;
    
    extensionLog(`Route replaced from ${previousPath} to ${currentPath}`);
    
    if (currentPath === statsRoute && previousPath !== statsRoute) {
      extensionLog("Entering statistics page, resetting charts");
      ChartManager.resetCharts();
    }
    
    setTimeout(runStatsLogic, 0);
    return result;
  };

  window.addEventListener("popstate", () => {
    const previousPath = appState.previousRoute;
    const currentPath = window.location.pathname;
    appState.previousRoute = currentPath;
    
    extensionLog(`Route popped from ${previousPath} to ${currentPath}`);
    
    if (currentPath === statsRoute && previousPath !== statsRoute) {
      extensionLog("Entering statistics page, resetting charts");
      ChartManager.resetCharts();
    }
    
    runStatsLogic();
  });

  extensionLog("Migaku Custom Stats Script Initializing...");
  runStatsLogic();
})();