Source: js/pre-election-dashboard.js

/**
 * @module ElectionIntelligence/PreElectionMonitoring
 * @category Intelligence Analysis - Pre-Election Activity Monitoring & Behavior Anomaly Detection
 * 
 * @description
 * **Swedish Pre-Election Activity Monitoring & Electoral Behavior Intelligence Dashboard**
 * 
 * Advanced intelligence analysis platform implementing **critical pre-election period monitoring**
 * (12-24 months before elections) with real-time activity anomaly detection. Detects 
 * election-driven behavior changes through quarterly comparison and establishes early warning 
 * indicators for coalition formation, government dissolution, and electoral campaign intensity.
 * Uses comparative analytics to distinguish Q4 normal-year baseline from election-year surge patterns.
 * 
 * ## Intelligence Methodology
 * 
 * This module implements **pre-election period intelligence monitoring**:
 * - **Critical Period**: Q4 (October-December) in years preceding elections (2022, 2026, 2030)
 * - **Comparison Baseline**: Non-election years to establish normal-year Q4 patterns
 * - **Activity Metrics**: Ballots, documents, committee decisions, parliamentary attendance
 * - **Anomaly Detection**: Z-score and percentage-change thresholds for alerting
 * 
 * ## Pre-Election Intelligence Framework
 * 
 * **Three-Dimensional Analysis Taxonomy**:
 * 
 * 1. **Quarterly Activity Metrics** (Baseline Comparison)
 *    - Ballot volume (votes in parliamentary chambers)
 *    - Document production (government proposals, motions)
 *    - Committee decision output
 *    - Parliamentary attendance and participation rates
 *    - Speech frequency and debate intensity
 * 
 * 2. **Election-Year vs. Non-Election Comparison** (Anomaly Detection)
 *    - Q4 activity deltas (election years vs. baseline years)
 *    - Percentage change from historical average
 *    - Statistical significance testing (Z-scores)
 *    - Confidence intervals on normal-year patterns
 * 
 * 3. **Pre-Election Behavior Patterns** (Campaign Indicator)
 *    - Increased legislative activity (bills, amendments)
 *    - Coalition positioning behaviors
 *    - Government confidence votes and stability tests
 *    - Media attention surge and political discourse intensity
 *    - Campaign messaging through parliamentary statements
 * 
 * ## Data Sources (CIA Platform)
 * 
 * **Primary Intelligence Feeds**:
 * - `view_riksdagen_pre_election_quarterly_activity_sample.csv`
 *   * Fields: year, quarter, ballot_count, document_count, attendance_rate, decision_count
 *   * Scope: Quarterly data spanning 20+ years (2002-2025)
 *   * Use: Historical pattern baseline establishment, trend analysis
 * 
 * - `view_riksdagen_q4_election_year_comparison_sample.csv`
 *   * Fields: metric_name, q4_election_year, q4_baseline_avg, percent_delta, z_score, significance
 *   * Scope: Comparative analysis across election/non-election years
 *   * Use: Anomaly identification, early warning detection
 * 
 * ## OSINT Collection Strategy
 * 
 * **Pre-Election Intelligence Monitoring**:
 * 1. **Parliamentary Activity Tracking**: Real-time Riksdag API feeds
 * 2. **Government Statements**: Official announcements and press releases
 * 3. **Coalition Communications**: Party leader statements and negotiations
 * 4. **Media Monitoring**: Campaign coverage volume and intensity
 * 5. **Electoral Board**: Official election date announcements
 * 6. **Polling Data**: Pre-election polls with trend tracking
 * 7. **Social Media**: Campaign activity and engagement surge detection
 * 
 * ## Visualization Intelligence
 * 
 * **Chart.js Quarterly Activity Trends** (Primary):
 * - **20-Year Q4 Activity Timeline**: Baseline vs. election year comparison
 *   * Multi-line chart with election-year Q4s highlighted
 *   * Separate lines for: ballots, documents, attendance, decisions
 *   * Color coding: Normal years (blue) vs. Election years (red/orange)
 *   * Interactive: Hover reveals detailed metrics and year identification
 * 
 * **Chart.js Election-Year Comparison** (Anomaly):
 * - **Pre-Election Surge Indicators**: Percentage change from baseline
 *   * Bar chart showing positive/negative deltas for each metric
 *   * Color-coded by significance level (green/yellow/red)
 *   * Threshold lines showing warning (20%) and alert (50%) thresholds
 * 
 * **Chart.js Early Warning System** (Alert):
 * - **Statistical Anomaly Flags**: Z-score heat map
 *   * Metrics ordered by statistical significance
 *   * Color intensity represents deviation magnitude
 *   * Identifies which metrics show strongest election-year signals
 * 
 * **Chart.js Year-Over-Year Comparison** (Temporal):
 * - **Q4 by Year Heatmap**: Multi-year quarterly comparison
 *   * 20 years × 4 metrics = 80-cell matrix
 *   * Color intensity shows activity level
 *   * Diagonal highlights show election-year concentrations
 * 
 * ## Intelligence Analysis Frameworks Applied
 * 
 * @intelligence
 * - **Temporal Anomaly Detection**: Statistical deviation identification
 * - **Baseline Establishment**: Non-election year patterns as control
 * - **Comparative Analysis**: Election vs. non-election behavior patterns
 * - **Threshold-Based Alerting**: Pre-defined deviation triggers
 * - **Time-Series Decomposition**: Separating trend, seasonal, and anomaly components
 * 
 * @osint
 * - **Activity Intelligence**: Real-time parliamentary activity monitoring
 * - **Pattern Recognition**: Historical election-year signatures identification
 * - **Confidence Quantification**: Statistical bounds on anomaly significance
 * - **Multi-Source Correlation**: Linking government, parliamentary, and media signals
 * 
 * @risk
 * - **Government Dissolution Risk**: Q4 confidence vote surge indicators
 * - **Coalition Collapse Risk**: Increased legislative maneuvering signals
 * - **Election Timing Uncertainty**: Activity shifts suggest government instability
 * - **Campaign Intensity Risk**: Media/parliamentary surge indicates polarization
 * 
 * ## GDPR Compliance
 * 
 * @gdpr Pre-election monitoring uses only public parliamentary data (Article 9(2)(e)):
 * - Official voting records (public parliamentary records)
 * - Document counts (publicly filed legislative proposals)
 * - Attendance data (public parliamentary records)
 * - Published government announcements (public domain)
 * No personal behavioral tracking or individual-level prediction.
 * No voter data or campaign finance details processed.
 * Aggregate activity analysis only; no personal political affiliation data.
 * 
 * ## Security Architecture
 * 
 * @security Chart.js rendering with XSS-safe data binding
 * @security All CSV data validated with type checking and range enforcement
 * @security No real-time API tokens or credentials exposed
 * @security Historical data immutable; only new quarterly data added
 * @security Statistical thresholds disclosed transparently
 * @risk Medium - Early warning of government instability may be sensitive
 * 
 * ## Performance Characteristics
 * 
 * - **Data Volume**: 20 years × 4 quarters × 4-6 metrics = ~320-480 data points
 * - **Rendering**: Chart.js with 4 separate visualizations
 * - **Memory**: <1MB for complete pre-election monitoring dataset
 * - **Update Frequency**: Quarterly (at end of Q1, Q2, Q3, Q4)
 * - **Calculation**: Z-scores, percentile ranges, confidence intervals
 * 
 * ## Data Transformation Pipeline
 * 
 * **Load Strategy**:
 * 1. Attempt local cache load (`cia-data/pre-election/`)
 * 2. Parse CSV files into quarterly time-series structure
 * 3. Fallback to remote GitHub repository if local unavailable
 * 4. Identify election years (2022, 2026, 2030, etc.)
 * 5. Calculate baseline averages for non-election Q4s
 * 6. Compute delta percentages and Z-scores
 * 7. Cache results with 24-hour expiry
 * 8. Render visualizations with aggregated/transformed data
 * 
 * **Data Aggregation**:
 * - Baseline: Average non-election Q4 values by metric
 * - Election Delta: (Election_Q4 - Baseline) / Baseline × 100%
 * - Z-Score: (Election_Q4 - Baseline_Mean) / Baseline_StdDev
 * - Significance: Z-score > 2 = significant (p<0.05)
 * - Alert Trigger: Delta > threshold OR Z-score > 2.0
 * 
 * ## Alert Thresholds
 * 
 * **Warning Level** (20% deviation):
 * - Ballot volume: -30% or +20% from baseline
 * - Document count: +20% from baseline
 * - Committee decisions: +30% from baseline
 * - Attendance: -2% from baseline
 * 
 * **Critical Level** (50% deviation):
 * - Ballot volume: -50% or +50% from baseline
 * - Document count: +50% from baseline
 * - Committee decisions: +50% from baseline
 * - Attendance: -5% from baseline
 * 
 * @author Hack23 AB - Pre-Election Intelligence Team
 * @license Apache-2.0
 * @version 1.0.0
 * @since 2024
 * 
 * @see {@link https://github.com/Hack23/cia|CIA Platform Data Source}
 * @see {@link https://data.riksdagen.se|Riksdag Open Data API}
 * @see {@link ./THREAT_MODEL.md|Threat Model Documentation}
 * @see {@link ./SECURITY_ARCHITECTURE.md|Security Architecture}
 */

(function() {
  'use strict';

  // Configuration
  const CONFIG = {
    dataUrls: {
      preElection: [
        'cia-data/pre-election/view_riksdagen_pre_election_quarterly_activity_sample.csv',
        'https://raw.githubusercontent.com/Hack23/cia/master/service.data.impl/sample-data/view_riksdagen_pre_election_quarterly_activity_sample.csv'
      ],
      electionComparison: [
        'cia-data/pre-election/view_riksdagen_q4_election_year_comparison_sample.csv',
        'https://raw.githubusercontent.com/Hack23/cia/master/service.data.impl/sample-data/view_riksdagen_q4_election_year_comparison_sample.csv'
      ]
    },
    cachePrefix: 'riksdag_pre_election_',
    cacheDuration: 24 * 60 * 60 * 1000, // 24 hours
    chartColors: {
      ballots: '#00d9ff',
      documents: '#ff006e',
      attendance: '#ffbe0b',
      baseline: '#666666',
      normal: '#388e3c',
      warning: '#f57c00',
      alert: '#d32f2f',
      election: '#ff006e',
      nonElection: '#00d9ff'
    },
    thresholds: {
      ballotWarning: -30,
      ballotAlert: -50,
      documentWarning: 20,
      attendanceWarning: -2,
      yoyAlert: 50
    }
  };

  // Translations for 2 languages (EN, SV)
  // NOTE: Only English and Swedish translations implemented. Other languages use English fallback.
  const TRANSLATIONS = {
    en: {
      title: 'Pre-Election Monitoring Dashboard',
      currentYear: '2025 Q4',
      baseline: 'Baseline',
      deviation: 'Deviation',
      ballotActivity: 'Ballot Activity',
      documentProduction: 'Document Production',
      attendanceRate: 'Attendance Rate',
      partyWinRate: 'Party Win Rate',
      vsBaseline: 'vs baseline',
      yoy: 'YoY',
      normal: 'NORMAL',
      reduced: 'REDUCED',
      elevated: 'ELEVATED',
      improving: 'IMPROVING',
      declining: 'DECLINING',
      stable: 'STABLE',
      metrics: {
        ballots: 'Ballots',
        documents: 'Documents',
        attendance: 'Attendance',
        yoyChange: 'YoY Change',
        winRate: 'Win Rate',
        absenceRate: 'Absence Rate',
        proposals: 'Proposals',
        assignments: 'Assignments'
      },
      status: {
        ok: 'OK',
        warning: 'Warning',
        alert: 'Alert'
      },
      chartLabels: {
        ballots: 'Ballots',
        documents: 'Documents',
        attendance: 'Attendance',
        electionYear: 'Election Year',
        nonElectionYear: 'Non-Election Year',
        baseline: 'Baseline'
      }
    },
    sv: {
      title: 'Övervakning före val',
      currentYear: '2025 Q4',
      baseline: 'Baslinje',
      deviation: 'Avvikelse',
      ballotActivity: 'Omröstningsaktivitet',
      documentProduction: 'Dokumentproduktion',
      attendanceRate: 'Närvarofrekvens',
      partyWinRate: 'Partiets vinstfrekvens',
      vsBaseline: 'vs baslinje',
      yoy: 'ÅfÅ',
      normal: 'NORMAL',
      reduced: 'MINSKAD',
      elevated: 'FÖRHÖJD',
      improving: 'FÖRBÄTTRAS',
      declining: 'FÖRSÄMRAS',
      stable: 'STABIL',
      metrics: {
        ballots: 'Omröstningar',
        documents: 'Dokument',
        attendance: 'Närvaro',
        yoyChange: 'ÅfÅ-förändring',
        winRate: 'Vinstfrekvens',
        absenceRate: 'Frånvarofrekvens',
        proposals: 'Förslag',
        assignments: 'Uppdrag'
      },
      status: {
        ok: 'OK',
        warning: 'Varning',
        alert: 'Alert'
      },
      chartLabels: {
        ballots: 'Omröstningar',
        documents: 'Dokument',
        attendance: 'Närvaro',
        electionYear: 'Valår',
        nonElectionYear: 'Icke-valår',
        baseline: 'Baslinje'
      }
    }
  };

  // Detect current language from URL
  function getCurrentLanguage() {
    const url = window.location.pathname;
    if (url.includes('_sv.html')) return 'sv';
    if (url.includes('_da.html')) return 'da';
    if (url.includes('_no.html')) return 'no';
    if (url.includes('_fi.html')) return 'fi';
    if (url.includes('_de.html')) return 'de';
    if (url.includes('_fr.html')) return 'fr';
    if (url.includes('_es.html')) return 'es';
    if (url.includes('_nl.html')) return 'nl';
    if (url.includes('_ar.html')) return 'ar';
    if (url.includes('_he.html')) return 'he';
    if (url.includes('_ja.html')) return 'ja';
    if (url.includes('_ko.html')) return 'ko';
    if (url.includes('_zh.html')) return 'zh';
    return 'en';
  }

  const currentLang = getCurrentLanguage();
  const t = TRANSLATIONS[currentLang] || TRANSLATIONS.en;

  // Data Manager
  class PreElectionDataManager {
    constructor() {
      this.preElectionData = null;
      this.electionComparisonData = null;
    }

    async fetchData() {
      try {
        // Try to load from cache first
        const cachedPreElection = this.loadFromCache('preElection');
        const cachedElectionComparison = this.loadFromCache('electionComparison');

        if (cachedPreElection && cachedElectionComparison) {
          this.preElectionData = cachedPreElection;
          this.electionComparisonData = cachedElectionComparison;
          console.log('✓ Loaded pre-election data from cache');
          return true;
        }

        // Fetch fresh data with local-first strategy
        const [preElectionCsv, electionComparisonCsv] = await Promise.all([
          this.fetchWithFallback(CONFIG.dataUrls.preElection),
          this.fetchWithFallback(CONFIG.dataUrls.electionComparison)
        ]);

        if (!preElectionCsv || !electionComparisonCsv) {
          throw new Error('Failed to fetch CIA data');
        }

        // Parse CSV data
        this.preElectionData = this.parseCSV(preElectionCsv);
        this.electionComparisonData = this.parseCSV(electionComparisonCsv);

        // Cache the data
        this.saveToCache('preElection', this.preElectionData);
        this.saveToCache('electionComparison', this.electionComparisonData);

        console.log('✓ Loaded pre-election data from source');
        return true;
      } catch (error) {
        console.error('Error fetching pre-election data:', error);
        return false;
      }
    }

    async fetchWithFallback(urls) {
      // urls can be a string or array of URLs (local first, then remote)
      const urlArray = Array.isArray(urls) ? urls : [urls];

      for (let i = 0; i < urlArray.length; i++) {
        const url = urlArray[i];
        const isLocal = !url.startsWith('http');

        try {
          console.log(`Trying to fetch: ${url}`);
          const response = await fetch(url);

          if (response.ok) {
            const text = await response.text();
            // Verify we got actual CSV data (not empty or error page)
            if (text.trim().length > 0 && text.includes(',')) {
              console.log(`✓ Successfully loaded from ${isLocal ? 'local' : 'remote'}: ${url}`);
              return text;
            }
          }
        } catch (error) {
          console.warn(`Failed to fetch from ${url}:`, error.message);
          // Continue to next URL in fallback chain
        }
      }

      console.error('All fetch attempts failed for:', urlArray);
      return null;
    }

    parseCSV(csvText) {
      const lines = csvText.trim().split('\n');
      const headers = lines[0].split(',').map(h => h.trim());
      const data = [];

      for (let i = 1; i < lines.length; i++) {
        const values = lines[i].split(',');
        const row = {};
        headers.forEach((header, index) => {
          const value = values[index]?.trim() || '';
          // Convert numeric values
          if (!isNaN(value) && value !== '') {
            row[header] = parseFloat(value);
          } else {
            row[header] = value;
          }
        });
        data.push(row);
      }

      return data;
    }

    loadFromCache(key) {
      try {
        const cached = localStorage.getItem(CONFIG.cachePrefix + key);
        if (!cached) return null;

        const { data, timestamp } = JSON.parse(cached);
        const age = Date.now() - timestamp;

        if (age > CONFIG.cacheDuration) {
          localStorage.removeItem(CONFIG.cachePrefix + key);
          return null;
        }

        return data;
      } catch (error) {
        console.error('Cache load error:', error);
        return null;
      }
    }

    saveToCache(key, data) {
      try {
        const cacheData = {
          data: data,
          timestamp: Date.now()
        };
        localStorage.setItem(CONFIG.cachePrefix + key, JSON.stringify(cacheData));
      } catch (error) {
        console.error('Cache save error:', error);
      }
    }

    getLatestYear() {
      if (!this.preElectionData || this.preElectionData.length === 0) return null;
      return Math.max(...this.preElectionData.map(d => d.year));
    }

    getCurrentYearData(year) {
      if (!this.preElectionData) return null;
      
      // If no year specified, use the latest year in the dataset
      const targetYear = year !== undefined ? year : this.getLatestYear();
      if (!targetYear) return null;
      
      return this.preElectionData.find(d => d.year === targetYear);
    }

    calculateDeviations(currentYear) {
      const data = this.getCurrentYearData(currentYear);
      if (!data) return null;

      return {
        ballots: data.ballot_percent_change_from_baseline || 0,
        documents: data.document_percent_change_from_baseline || 0,
        assignments: ((data.total_new_assignments - data.baseline_assignments) / data.baseline_assignments * 100) || 0,
        attendance: ((data.avg_attendance_rate - data.baseline_attendance) / data.baseline_attendance * 100) || 0
      };
    }

    classifyActivityLevel(deviation) {
      if (deviation < -50) return 'SEVERELY_REDUCED';
      if (deviation < -30) return 'REDUCED_ACTIVITY';
      if (deviation > 50) return 'UNUSUALLY_HIGH_ACTIVITY';
      if (deviation > 20) return 'ELEVATED_ACTIVITY';
      return 'NORMAL_ACTIVITY';
    }

    generateEarlyWarnings() {
      const data = this.getCurrentYearData();
      if (!data) return [];

      const warnings = [];
      const deviations = this.calculateDeviations();

      // Ballot warning
      if (deviations.ballots < CONFIG.thresholds.ballotAlert) {
        warnings.push({ metric: 'ballots', status: 'alert', deviation: deviations.ballots });
      } else if (deviations.ballots < CONFIG.thresholds.ballotWarning) {
        warnings.push({ metric: 'ballots', status: 'warning', deviation: deviations.ballots });
      } else {
        warnings.push({ metric: 'ballots', status: 'ok', deviation: deviations.ballots });
      }

      // Document warning
      if (Math.abs(deviations.documents) > CONFIG.thresholds.documentWarning) {
        warnings.push({ metric: 'documents', status: 'warning', deviation: deviations.documents });
      } else {
        warnings.push({ metric: 'documents', status: 'ok', deviation: deviations.documents });
      }

      // Attendance warning
      if (deviations.attendance < CONFIG.thresholds.attendanceWarning) {
        warnings.push({ metric: 'attendance', status: 'warning', deviation: deviations.attendance });
      } else {
        warnings.push({ metric: 'attendance', status: 'ok', deviation: deviations.attendance });
      }

      // YoY change warning
      const yoyDeviation = Number(data.yoy_ballot_change_pct) || 0;
      if (Math.abs(yoyDeviation) > CONFIG.thresholds.yoyAlert) {
        warnings.push({ metric: 'yoyChange', status: 'alert', deviation: yoyDeviation });
      } else {
        warnings.push({ metric: 'yoyChange', status: 'ok', deviation: yoyDeviation });
      }

      return warnings;
    }
  }

  // Chart Renderer
  class PreElectionCharts {
    constructor(dataManager) {
      this.dataManager = dataManager;
    }

    renderQ4Timeline() {
      const ctx = document.getElementById('q4-timeline-chart');
      if (!ctx) return;

      const data = this.dataManager.preElectionData;
      if (!data || data.length === 0) return;

      // Get translations
      const lang = getCurrentLanguage();
      const t = TRANSLATIONS[lang] || TRANSLATIONS.en;

      // Sort by year
      data.sort((a, b) => a.year - b.year);

      new Chart(ctx, {
        type: 'line',
        data: {
          labels: data.map(d => d.year),
          datasets: [
            {
              label: t.metrics.ballots,
              data: data.map(d => d.total_ballots),
              borderColor: CONFIG.chartColors.ballots,
              backgroundColor: CONFIG.chartColors.ballots + '33',
              yAxisID: 'y1',
              tension: 0.3
            },
            {
              label: t.metrics.documents,
              data: data.map(d => d.total_documents),
              borderColor: CONFIG.chartColors.documents,
              backgroundColor: CONFIG.chartColors.documents + '33',
              yAxisID: 'y2',
              tension: 0.3
            },
            {
              label: t.baseline + ' (Ballots)',
              data: data.map(d => d.baseline_ballots),
              borderColor: CONFIG.chartColors.baseline,
              borderDash: [5, 5],
              pointRadius: 0,
              yAxisID: 'y1',
              fill: false
            },
            {
              label: t.baseline + ' (Documents)',
              data: data.map(d => d.baseline_documents),
              borderColor: CONFIG.chartColors.baseline,
              borderDash: [5, 5],
              pointRadius: 0,
              yAxisID: 'y2',
              fill: false
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          interaction: {
            mode: 'index',
            intersect: false
          },
          plugins: {
            legend: {
              position: 'top',
              labels: { color: '#e0e0e0' }
            },
            tooltip: {
              callbacks: {
                label: function(context) {
                  let label = context.dataset.label || '';
                  if (label) {
                    label += ': ';
                  }
                  label += context.parsed.y.toLocaleString();
                  return label;
                }
              }
            }
          },
          scales: {
            y1: {
              type: 'linear',
              position: 'left',
              title: {
                display: true,
                text: t.chartLabels.ballots,
                color: CONFIG.chartColors.ballots
              },
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            },
            y2: {
              type: 'linear',
              position: 'right',
              title: {
                display: true,
                text: t.chartLabels.documents,
                color: CONFIG.chartColors.documents
              },
              ticks: { color: '#e0e0e0' },
              grid: { drawOnChartArea: false }
            },
            x: {
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            }
          }
        }
      });
    }

    renderElectionComparison() {
      const ctx = document.getElementById('election-comparison-chart');
      if (!ctx) return;

      const data = this.dataManager.electionComparisonData;
      if (!data || data.length === 0) return;

      // Get translations
      const lang = getCurrentLanguage();
      const t = TRANSLATIONS[lang] || TRANSLATIONS.en;

      // Sort by year
      data.sort((a, b) => a.year - b.year);

      new Chart(ctx, {
        type: 'bar',
        data: {
          labels: data.map(d => d.year),
          datasets: [
            {
              label: t.chartLabels.electionYear,
              data: data.map(d => (d.is_election_year === 't' || d.is_election_year === true) ? d.total_ballots : null),
              backgroundColor: CONFIG.chartColors.election + '99',
              borderColor: CONFIG.chartColors.election,
              borderWidth: 1
            },
            {
              label: t.chartLabels.nonElectionYear,
              data: data.map(d => (d.is_election_year === 'f' || d.is_election_year === false) ? d.total_ballots : null),
              backgroundColor: CONFIG.chartColors.nonElection + '99',
              borderColor: CONFIG.chartColors.nonElection,
              borderWidth: 1
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            legend: {
              position: 'top',
              labels: { color: '#e0e0e0' }
            },
            tooltip: {
              callbacks: {
                label: function(context) {
                  return context.dataset.label + ': ' + (context.parsed.y || 0).toLocaleString() + ' ' + t.chartLabels.ballots.toLowerCase();
                }
              }
            }
          },
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Total Ballots',
                color: '#e0e0e0'
              },
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            },
            x: {
              ticks: { 
                color: '#e0e0e0',
                maxRotation: 45,
                minRotation: 45
              },
              grid: { color: '#ffffff22' }
            }
          }
        }
      });
    }

    renderDeviationRadar() {
      const ctx = document.getElementById('deviation-radar-chart');
      if (!ctx) return;

      const latestYear = this.dataManager.getLatestYear();
      const data = this.dataManager.getCurrentYearData(latestYear);
      if (!data) return;

      // Get translations
      const lang = getCurrentLanguage();
      const t = TRANSLATIONS[lang] || TRANSLATIONS.en;

      new Chart(ctx, {
        type: 'radar',
        data: {
          labels: [t.metrics.ballots, t.metrics.documents, t.metrics.assignments, t.metrics.attendance, t.metrics.winRate, t.metrics.absenceRate],
          datasets: [
            {
              label: `${latestYear} Q4`,
              data: [
                data.total_ballots / 100,
                data.total_documents / 100,
                data.total_new_assignments,
                data.avg_attendance_rate,
                data.avg_party_win_rate,
                data.avg_party_absence_rate
              ],
              borderColor: CONFIG.chartColors.ballots,
              backgroundColor: CONFIG.chartColors.ballots + '33',
              pointBackgroundColor: CONFIG.chartColors.ballots
            },
            {
              label: t.baseline,
              data: [
                data.baseline_ballots / 100,
                data.baseline_documents / 100,
                data.baseline_assignments,
                data.baseline_attendance || 85,
                data.baseline_party_win_rate || 56,
                data.baseline_party_absence_rate || 15
              ],
              borderColor: CONFIG.chartColors.baseline,
              backgroundColor: CONFIG.chartColors.baseline + '22',
              borderDash: [5, 5],
              pointBackgroundColor: CONFIG.chartColors.baseline
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            legend: {
              position: 'top',
              labels: { color: '#e0e0e0' }
            }
          },
          scales: {
            r: {
              angleLines: { color: '#ffffff22' },
              grid: { color: '#ffffff22' },
              pointLabels: { color: '#e0e0e0' },
              ticks: { 
                color: '#e0e0e0',
                backdropColor: 'transparent'
              }
            }
          }
        }
      });
    }

    renderPartyTrends() {
      const ctx = document.getElementById('party-trends-chart');
      if (!ctx) return;

      const data = this.dataManager.preElectionData;
      if (!data || data.length === 0) return;

      data.sort((a, b) => a.year - b.year);

      new Chart(ctx, {
        type: 'line',
        data: {
          labels: data.map(d => d.year),
          datasets: [
            {
              label: 'Party Win Rate (%)',
              data: data.map(d => d.avg_party_win_rate),
              borderColor: CONFIG.chartColors.normal,
              backgroundColor: CONFIG.chartColors.normal + '33',
              tension: 0.3,
              yAxisID: 'y'
            },
            {
              label: 'Party Absence Rate (%)',
              data: data.map(d => d.avg_party_absence_rate),
              borderColor: CONFIG.chartColors.alert,
              backgroundColor: CONFIG.chartColors.alert + '33',
              tension: 0.3,
              yAxisID: 'y'
            },
            {
              label: 'Party Documents (÷100)',
              data: data.map(d => (d.party_documents_total || 0) / 100),
              borderColor: CONFIG.chartColors.documents,
              backgroundColor: CONFIG.chartColors.documents + '33',
              tension: 0.3,
              yAxisID: 'y2'
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          interaction: {
            mode: 'index',
            intersect: false
          },
          plugins: {
            legend: {
              position: 'top',
              labels: { color: '#e0e0e0' }
            }
          },
          scales: {
            y: {
              type: 'linear',
              position: 'left',
              title: {
                display: true,
                text: 'Percentage (%)',
                color: '#e0e0e0'
              },
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            },
            y2: {
              type: 'linear',
              position: 'right',
              title: {
                display: true,
                text: 'Documents (÷100)',
                color: '#e0e0e0'
              },
              ticks: { color: '#e0e0e0' },
              grid: { drawOnChartArea: false }
            },
            x: {
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            }
          }
        }
      });
    }

    renderYoYWaterfall() {
      const ctx = document.getElementById('yoy-waterfall-chart');
      if (!ctx) return;

      const data = this.dataManager.preElectionData;
      if (!data || data.length === 0) return;

      data.sort((a, b) => a.year - b.year);

      const years = data.map(d => d.year);
      const values = data.map(d => d.total_ballots);
      
      // Generate labels and changes dynamically
      const labels = [];
      const changes = [];
      
      for (let i = 0; i < values.length; i++) {
        if (i === 0) {
          // First year: absolute value
          labels.push(String(years[0]));
          changes.push(values[0]);
        } else {
          // Subsequent years: year-over-year change
          labels.push(String(years[i]) + ' Change');
          changes.push(values[i] - values[i - 1]);
        }
      }

      new Chart(ctx, {
        type: 'bar',
        data: {
          labels: labels,
          datasets: [{
            label: 'Ballot Activity',
            data: changes,
            backgroundColor: changes.map((v, i) => 
              i === 0 ? CONFIG.chartColors.ballots :
              v > 0 ? CONFIG.chartColors.normal : CONFIG.chartColors.alert
            ),
            borderColor: changes.map((v, i) => 
              i === 0 ? CONFIG.chartColors.ballots :
              v > 0 ? CONFIG.chartColors.normal : CONFIG.chartColors.alert
            ),
            borderWidth: 2
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            legend: {
              display: false
            },
            tooltip: {
              callbacks: {
                label: function(context) {
                  const value = context.parsed.y;
                  const label = context.label;
                  if (label.includes('Change')) {
                    return (value > 0 ? '+' : '') + value.toLocaleString() + ' ballots';
                  }
                  return value.toLocaleString() + ' ballots';
                }
              }
            }
          },
          scales: {
            y: {
              title: {
                display: true,
                text: 'Ballots',
                color: '#e0e0e0'
              },
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            },
            x: {
              ticks: { color: '#e0e0e0' },
              grid: { color: '#ffffff22' }
            }
          }
        }
      });
    }

    renderWarningMatrix() {
      const container = document.getElementById('warning-matrix');
      if (!container) return;

      const warnings = this.dataManager.generateEarlyWarnings();
      const lang = getCurrentLanguage();
      const t = TRANSLATIONS[lang] || TRANSLATIONS.en;

      // Clear existing content safely
      container.textContent = '';

      warnings.forEach(w => {
        const statusIcon = w.status === 'ok' ? '🟢' : w.status === 'warning' ? '🟡' : '🔴';
        const statusClass = w.status === 'ok' ? 'normal' : w.status === 'warning' ? 'warning' : 'alert';
        const statusLabel = t.status[w.status] || w.status.toUpperCase();
        const metricLabel = t.metrics[w.metric] || w.metric;
        const deviationText = (w.deviation > 0 ? '+' : '') + w.deviation.toFixed(1) + '%';

        const cell = document.createElement('div');
        cell.classList.add('warning-cell', statusClass);

        const statusIconEl = document.createElement('div');
        statusIconEl.classList.add('status-icon');
        statusIconEl.setAttribute('role', 'img');
        statusIconEl.setAttribute('aria-label', statusLabel);
        statusIconEl.textContent = statusIcon;

        const metricNameEl = document.createElement('div');
        metricNameEl.classList.add('metric-name');
        metricNameEl.textContent = metricLabel;

        const deviationValueEl = document.createElement('div');
        deviationValueEl.classList.add('deviation-value');
        deviationValueEl.textContent = deviationText;

        cell.appendChild(statusIconEl);
        cell.appendChild(metricNameEl);
        cell.appendChild(deviationValueEl);

        container.appendChild(cell);
      });
    }

    renderAllCharts() {
      this.renderQ4Timeline();
      this.renderElectionComparison();
      this.renderDeviationRadar();
      this.renderPartyTrends();
      this.renderYoYWaterfall();
      this.renderWarningMatrix();
    }
  }

  // Status Card Updater
  function updateStatusCards(dataManager) {
    const latestYear = dataManager.getLatestYear();
    const data = dataManager.getCurrentYearData(latestYear);
    if (!data) return;

    const deviations = dataManager.calculateDeviations(latestYear);

    // Update ballot activity
    const ballotCard = document.querySelector('.status-card[data-metric="ballots"]');
    if (ballotCard) {
      ballotCard.querySelector('.current-value').textContent = data.total_ballots.toLocaleString();
      ballotCard.querySelector('.baseline-comparison').textContent = 
        (deviations.ballots > 0 ? '+' : '') + deviations.ballots.toFixed(2) + '% ' + t.vsBaseline;
      
      const badge = ballotCard.querySelector('.status-badge');
      if (deviations.ballots < -30) {
        badge.textContent = t.reduced;
        badge.className = 'status-badge alert';
      } else if (deviations.ballots > 20) {
        badge.textContent = t.elevated;
        badge.className = 'status-badge improving';
      } else {
        badge.textContent = t.normal;
        badge.className = 'status-badge normal';
      }
    }

    // Update document production
    const docCard = document.querySelector('.status-card[data-metric="documents"]');
    if (docCard) {
      docCard.querySelector('.current-value').textContent = data.total_documents.toLocaleString();
      docCard.querySelector('.baseline-comparison').textContent = 
        (deviations.documents > 0 ? '+' : '') + deviations.documents.toFixed(2) + '% ' + t.vsBaseline;
      
      const badge = docCard.querySelector('.status-badge');
      badge.textContent = t.normal;
      badge.className = 'status-badge normal';
    }

    // Update attendance rate
    const attendanceCard = document.querySelector('.status-card[data-metric="attendance"]');
    if (attendanceCard) {
      attendanceCard.querySelector('.current-value').textContent = 
        data.avg_attendance_rate.toFixed(2) + '%';
      attendanceCard.querySelector('.baseline-comparison').textContent = 
        (deviations.attendance > 0 ? '+' : '') + deviations.attendance.toFixed(2) + '% ' + t.vsBaseline;
      
      const badge = attendanceCard.querySelector('.status-badge');
      badge.textContent = t.stable;
      badge.className = 'status-badge normal';
    }

    // Update party performance
    const partyCard = document.querySelector('.status-card[data-metric="party-performance"]');
    if (partyCard) {
      partyCard.querySelector('.current-value').textContent = 
        data.avg_party_win_rate.toFixed(2) + '%';
      
      // Derive previous year from available data rather than using hard-coded 2024
      const availableYears = Array.from(
        new Set(dataManager.preElectionData.map(d => d.year))
      ).sort((a, b) => a - b);
      const latestYearIndex = availableYears.indexOf(latestYear);
      const prevYearValue = latestYearIndex > 0 ? availableYears[latestYearIndex - 1] : null;
      const prevYear = prevYearValue !== null
        ? dataManager.preElectionData.find(d => d.year === prevYearValue)
        : null;
      
      const yoyChange = (prevYear && prevYear.avg_party_win_rate)
        ? ((data.avg_party_win_rate - prevYear.avg_party_win_rate) / prevYear.avg_party_win_rate * 100)
        : 0;
      
      partyCard.querySelector('.baseline-comparison').textContent = 
        (yoyChange > 0 ? '+' : '') + yoyChange.toFixed(2) + '% ' + t.yoy;
      
      const badge = partyCard.querySelector('.status-badge');
      if (yoyChange > 0) {
        badge.textContent = t.improving;
        badge.className = 'status-badge improving';
      } else {
        badge.textContent = t.declining;
        badge.className = 'status-badge warning';
      }
    }
  }

  // Initialize dashboard
  async function initDashboard() {
    // Check if dashboard exists on page
    if (!document.getElementById('pre-election-dashboard')) {
      return;
    }

    console.log('Initializing Pre-Election Monitoring Dashboard...');

    // Show loading state
    const dashboard = document.getElementById('pre-election-dashboard');
    dashboard.classList.add('loading');

    // Initialize data manager
    const dataManager = new PreElectionDataManager();
    const success = await dataManager.fetchData();

    if (!success) {
      console.error('Failed to load pre-election data');
      dashboard.innerHTML = '<p class="error">Failed to load dashboard data. Please try again later.</p>';
      return;
    }

    // Update status cards
    updateStatusCards(dataManager);

    // Render charts
    const chartRenderer = new PreElectionCharts(dataManager);
    chartRenderer.renderAllCharts();

    // Remove loading state
    dashboard.classList.remove('loading');

    console.log('Pre-Election Monitoring Dashboard initialized successfully');
  }

  // Wait for DOM and Chart.js to be ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
      // Wait for Chart.js to load (max 10 seconds)
      let attempts = 0;
      const maxAttempts = 100; // 10 seconds at 100ms intervals
      const checkChartJS = setInterval(() => {
        attempts++;
        if (typeof Chart !== 'undefined') {
          clearInterval(checkChartJS);
          initDashboard();
        } else if (attempts >= maxAttempts) {
          clearInterval(checkChartJS);
          console.error('Chart.js failed to load after 10 seconds');
          const dashboard = document.getElementById('pre-election-dashboard');
          if (dashboard) {
            dashboard.innerHTML = '<div class="error">Failed to load Chart.js library. Please refresh the page.</div>';
          }
        }
      }, 100);
    });
  } else {
    // Wait for Chart.js to load (max 10 seconds)
    let attempts = 0;
    const maxAttempts = 100; // 10 seconds at 100ms intervals
    const checkChartJS = setInterval(() => {
      attempts++;
      if (typeof Chart !== 'undefined') {
        clearInterval(checkChartJS);
        initDashboard();
      } else if (attempts >= maxAttempts) {
        clearInterval(checkChartJS);
        console.error('Chart.js failed to load after 10 seconds');
        const dashboard = document.getElementById('pre-election-dashboard');
        if (dashboard) {
          dashboard.innerHTML = '<div class="error">Failed to load Chart.js library. Please refresh the page.</div>';
        }
      }
    }, 100);
  }

})();