Source: scripts/generate-news-indexes.js

#!/usr/bin/env node

/**
 * @module Intelligence/NewsGeneration
 * @category Intelligence Operations / Supporting Infrastructure
 * @name Dynamic News Index Generation - Multi-Language Article Aggregation
 * 
 * @description
 * Automated news index generation system that dynamically scans published news articles
 * across all 14 supported languages and generates corresponding index pages with proper
 * metadata, filtering capabilities, and SEO optimization for parliamentary intelligence.
 * 
 * Operational Context:
 * This script solves the critical maintenance problem of hardcoded article arrays in
 * static index HTML files. Instead of manually updating article lists for 14 language
 * variants, the system autonomously discovers published articles and generates index
 * pages with consistent structure, metadata, and search optimization.
 * 
 * Multi-Language Support (14 languages):
 * - English (en), Swedish (sv), Danish (da), Norwegian (no), Finnish (fi)
 * - German (de), French (fr), Spanish (es), Dutch (nl)
 * - Arabic (ar), Hebrew (he), Japanese (ja), Korean (ko), Chinese (zh)
 * - Each language includes localized titles, keywords, breadcrumbs, filtering UI
 * 
 * Core Functionality:
 * - Scans news/ directory recursively for published HTML article files
 * - Extracts article metadata: title, date, description, language, category tags
 * - Aggregates articles by language code for proper index organization
 * - Generates dynamic filter controls: article type, topic category, sort order
 * - Creates SEO-optimized index pages with proper JSON-LD schema markup
 * - Implements responsive UI with accessibility features (WCAG 2.1 AA)
 * 
 * Intelligence Integration:
 * - Enables real-time tracking of parliamentary activity coverage
 * - Identifies news gaps and coverage imbalances across political topics
 * - Supports rapid content discovery for international audience segments
 * - Maintains consistent intelligence narrative across language variants
 * 
 * Article Discovery & Categorization:
 * - Prospective news: Upcoming parliamentary events (week-ahead, committee agendas)
 * - Retrospective news: Completed parliamentary activities (votes, decisions)
 * - Analysis pieces: Strategic interpretation of political developments
 * - Breaking news: Urgent parliamentary developments and emergency situations
 * 
 * Topic Categories:
 * - Parliament (Riksdag structure, committee reports, legislative process)
 * - Government (cabinet decisions, ministry statements, regulatory actions)
 * - Defense (national security, military policy, NATO/EU coordination)
 * - Environment (climate policy, emissions trading, sustainability)
 * - Committees (specific committee activities and cross-committee coordination)
 * - Legislation (bill tracking, proposal analysis, amendments)
 * 
 * SEO & Accessibility:
 * - Implements Open Graph meta tags for social media sharing
 * - Generates JSON-LD structured data for search engine indexing
 * - Provides hreflang tags for multi-language version discovery
 * - Includes alt text for all images and proper heading hierarchy
 * - Mobile-responsive design with proper viewport configuration
 * 
 * Localization Features:
 * - Translated UI elements: filter labels, breadcrumbs, no-results messages
 * - Localized date formats and sort options
 * - Language-specific keyword optimization for search engines
 * - Proper locale configuration (en_US, sv_SE, etc.)
 * 
 * Integration Points:
 * - Invoked by CI/CD pipeline after news generation scripts
 * - Feeds article discovery service for dashboard widgets
 * - Consumed by search functionality and site navigation
 * - Referenced by analytics tracking for page visit metrics
 * 
 * Data Integrity:
 * - Validates article file existence before inclusion
 * - Handles missing or malformed metadata gracefully
 * - Provides diagnostic output for troubleshooting
 * - Complies with ISO 27001:2022 A.12.6.1 (change management)
 * 
 * Usage:
 *   node scripts/generate-news-indexes.js
 *   # Generates: news/index.html, news/index_sv.html, ... news/index_zh.html
 * 
 * @intelligence Core infrastructure for maintaining searchable intelligence archive
 * @osint Aggregates published political intelligence across global audience
 * @risk Incomplete article discovery may result in search visibility gaps
 * @gdpr No personal data processing (aggregation of published articles only)
 * @security HTML generation uses html-utils.js escaping to prevent XSS
 * 
 * @author Hack23 AB (Content Infrastructure Team)
 * @license Apache-2.0
 * @version 3.0.0
 * @see NEWS_WORKFLOW_EXECUTIVE_SUMMARY.md for context
 * @see generate-news-enhanced.js (produces articles consumed by this indexer)
 * @see html-utils.js (provides HTML entity escaping)
 * @see WCAG 2.1 AA accessibility standards
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { escapeHtml } from './html-utils.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Configuration
const NEWS_DIR = path.join(__dirname, '..', 'news');

const LANGUAGES = {
  en: {
    name: 'English', code: 'en', locale: 'en_US',
    title: 'News',
    subtitle: 'Latest news and analysis from Sweden\'s Riksdag. The Economist-style political journalism covering parliament, government, and agencies with systematic transparency.',
    keywords: 'riksdag news, swedish parliament, government bills, committee reports, propositions, motions, parliamentary votes, political analysis, Sweden Democrats, Social Democrats, Moderaterna, coalition politics, transparency, democracy',
    breadcrumbs: { home: 'Home', news: 'News' },
    backLink: 'Back to Main',
    filters: {
      type: 'Type:', allTypes: 'All types', prospective: 'Prospective', retrospective: 'Retrospective', analysis: 'Analysis', breaking: 'Breaking news',
      topic: 'Topic:', allTopics: 'All Topics', parliament: 'Parliament', government: 'Government', defense: 'Defense', environment: 'Environment', committees: 'Committees', legislation: 'Legislation',
      sort: 'Sort:', newest: 'Newest First', oldest: 'Oldest First', titleSort: 'Title'
    },
    noResults: 'No articles matched the filters',
    i18n: { noArticles: 'No articles available', loading: 'Loading articles...', articleCount: '(n) => n === 1 ? \'1 article\' : \'\' + n + \' articles\'' },
    schemaDescription: 'Swedish Parliament Intelligence Platform - Monitor political activity with systematic transparency'
  },
  sv: {
    name: 'Svenska', code: 'sv', locale: 'sv_SE',
    title: 'Nyheter',
    subtitle: 'Senaste nyheterna och analyser från Sveriges Riksdag. Politisk journalistik i The Economist-stil som täcker riksdag, regering och myndigheter med systematisk transparens.',
    keywords: 'riksdag nyheter, svenska riksdagen, propositioner, betänkanden, motioner, utskott, voteringar, politisk analys, Socialdemokraterna, Moderaterna, Sverigedemokraterna, koalitionspolitik, öppenhet, demokrati',
    breadcrumbs: { home: 'Hem', news: 'Nyheter' },
    backLink: 'Tillbaka till huvudsidan',
    filters: {
      type: 'Typ:', allTypes: 'Alla typer', prospective: 'Framåtblickande', retrospective: 'Återblickande', analysis: 'Analys', breaking: 'Senaste nytt',
      topic: 'Ämne:', allTopics: 'Alla ämnen', parliament: 'Riksdagen', government: 'Regeringen', defense: 'Försvar', environment: 'Miljö', committees: 'Utskott', legislation: 'Lagstiftning',
      sort: 'Sortera:', newest: 'Nyast först', oldest: 'Äldst först', titleSort: 'Titel'
    },
    noResults: 'Inga artiklar matchade filtren',
    i18n: { noArticles: 'Inga artiklar tillgängliga', loading: 'Laddar artiklar...', articleCount: '(n) => n === 1 ? \'1 artikel\' : \'\' + n + \' artiklar\'' },
    schemaDescription: 'Svensk riksdagsbevakning - Övervaka politisk aktivitet med systematisk transparens'
  },
  da: {
    name: 'Dansk', code: 'da', locale: 'da_DK',
    title: 'Nyheder',
    subtitle: 'Seneste nyheder og analyser fra Sveriges Rigsdag. Politisk journalistik i The Economist-stil.',
    keywords: 'riksdag nyheder, svensk parlament, regeringsforslag, udvalgsbetænkninger, afstemninger, politisk analyse, svenske partier, gennemsigtighed, demokrati',
    breadcrumbs: { home: 'Hjem', news: 'Nyheder' },
    backLink: 'Tilbage til hovedsiden',
    filters: {
      type: 'Type:', allTypes: 'Alle typer', prospective: 'Fremadrettet', retrospective: 'Tilbageblik', analysis: 'Analyse', breaking: 'Seneste nyt',
      topic: 'Emne:', allTopics: 'Alle emner', parliament: 'Parlamentet', government: 'Regeringen', defense: 'Forsvar', environment: 'Miljø', committees: 'Udvalg', legislation: 'Lovgivning',
      sort: 'Sorter:', newest: 'Nyeste først', oldest: 'Ældste først', titleSort: 'Titel'
    },
    noResults: 'Ingen artikler matchede filtrene',
    i18n: { noArticles: 'Ingen artikler tilgængelige', loading: 'Indlæser artikler...', articleCount: '(n) => n === 1 ? \'1 artikel\' : \'\' + n + \' artikler\'' },
    schemaDescription: 'Svensk parlamentsovervågning - Overvåg politisk aktivitet med systematisk gennemsigtighed'
  },
  no: {
    name: 'Norsk', code: 'no', locale: 'no_NO',
    title: 'Nyheter',
    subtitle: 'Siste nyheter og analyser fra Sveriges Riksdag. Politisk journalistikk i The Economist-stil.',
    keywords: 'riksdag nyheter, svensk parlament, regjeringsforslag, komitéinnstillinger, voteringer, politisk analyse, svenske partier, åpenhet, demokrati',
    breadcrumbs: { home: 'Hjem', news: 'Nyheter' },
    backLink: 'Tilbake til hovedsiden',
    filters: {
      type: 'Type:', allTypes: 'Alle typer', prospective: 'Fremtidsrettet', retrospective: 'Tilbakeblikk', analysis: 'Analyse', breaking: 'Siste nytt',
      topic: 'Emne:', allTopics: 'Alle emner', parliament: 'Parlamentet', government: 'Regjeringen', defense: 'Forsvar', environment: 'Miljø', committees: 'Utvalg', legislation: 'Lovgivning',
      sort: 'Sorter:', newest: 'Nyeste først', oldest: 'Eldste først', titleSort: 'Tittel'
    },
    noResults: 'Ingen artikler matchet filtrene',
    i18n: { noArticles: 'Ingen artikler tilgjengelige', loading: 'Laster artikler...', articleCount: '(n) => n === 1 ? \'1 artikkel\' : \'\' + n + \' artikler\'' },
    schemaDescription: 'Svensk parlamentsovervåking - Overvåk politisk aktivitet med systematisk åpenhet'
  },
  fi: {
    name: 'Suomi', code: 'fi', locale: 'fi_FI',
    title: 'Uutiset',
    subtitle: 'Viimeisimmät uutiset ja analyysit Ruotsin valtiopäivistä. The Economist -tyylistä poliittista journalismia.',
    keywords: 'riksdag uutiset, ruotsin parlamentti, hallituksen esitykset, valiokunnan mietinnöt, äänestykset, poliittinen analyysi, ruotsin puolueet, avoimuus, demokratia',
    breadcrumbs: { home: 'Etusivu', news: 'Uutiset' },
    backLink: 'Takaisin etusivulle',
    filters: {
      type: 'Tyyppi:', allTypes: 'Kaikki tyypit', prospective: 'Ennakoiva', retrospective: 'Takautuva', analysis: 'Analyysi', breaking: 'Viimeisimmät',
      topic: 'Aihe:', allTopics: 'Kaikki aiheet', parliament: 'Parlamentti', government: 'Hallitus', defense: 'Puolustus', environment: 'Ympäristö', committees: 'Valiokunnat', legislation: 'Lainsäädäntö',
      sort: 'Järjestä:', newest: 'Uusimmat ensin', oldest: 'Vanhimmat ensin', titleSort: 'Otsikko'
    },
    noResults: 'Mikään artikkeli ei vastannut suodattimia',
    i18n: { noArticles: 'Ei artikkeleita saatavilla', loading: 'Ladataan artikkeleita...', articleCount: '(n) => n === 1 ? \'1 artikkeli\' : \'\' + n + \' artikkelia\'' },
    schemaDescription: 'Ruotsin parlamenttiseuranta - Seuraa poliittista toimintaa järjestelmällisellä avoimuudella'
  },
  de: {
    name: 'Deutsch', code: 'de', locale: 'de_DE',
    title: 'Nachrichten',
    subtitle: 'Neueste Nachrichten und Analysen aus dem schwedischen Reichstag. Politischer Journalismus im Stil des Economist.',
    keywords: 'riksdag nachrichten, schwedisches parlament, regierungsvorlagen, ausschussberichte, abstimmungen, politische analyse, schwedische parteien, transparenz, demokratie',
    breadcrumbs: { home: 'Startseite', news: 'Nachrichten' },
    backLink: 'Zurück zur Hauptseite',
    filters: {
      type: 'Typ:', allTypes: 'Alle Typen', prospective: 'Vorausschauend', retrospective: 'Rückblickend', analysis: 'Analyse', breaking: 'Eilmeldungen',
      topic: 'Thema:', allTopics: 'Alle Themen', parliament: 'Parlament', government: 'Regierung', defense: 'Verteidigung', environment: 'Umwelt', committees: 'Ausschüsse', legislation: 'Gesetzgebung',
      sort: 'Sortieren:', newest: 'Neueste zuerst', oldest: 'Älteste zuerst', titleSort: 'Titel'
    },
    noResults: 'Keine Artikel entsprachen den Filtern',
    i18n: { noArticles: 'Keine Artikel verfügbar', loading: 'Artikel werden geladen...', articleCount: '(n) => n === 1 ? \'1 Artikel\' : \'\' + n + \' Artikel\'' },
    schemaDescription: 'Schwedische Parlamentsüberwachung - Politische Aktivitäten mit systematischer Transparenz verfolgen'
  },
  fr: {
    name: 'Français', code: 'fr', locale: 'fr_FR',
    title: 'Actualités',
    subtitle: 'Dernières nouvelles et analyses du Riksdag suédois. Journalisme politique dans le style de The Economist.',
    keywords: 'riksdag actualités, parlement suédois, projets de loi, rapports de commission, motions parlementaires, votes, analyse politique, partis suédois, transparence, démocratie',
    breadcrumbs: { home: 'Accueil', news: 'Actualités' },
    backLink: 'Retour à l\'accueil',
    filters: {
      type: 'Type :', allTypes: 'Tous types', prospective: 'Prospectif', retrospective: 'Rétrospectif', analysis: 'Analyse', breaking: 'Dernières nouvelles',
      topic: 'Sujet :', allTopics: 'Tous sujets', parliament: 'Parlement', government: 'Gouvernement', defense: 'Défense', environment: 'Environnement', committees: 'Comités', legislation: 'Législation',
      sort: 'Trier :', newest: 'Plus récent', oldest: 'Plus ancien', titleSort: 'Titre'
    },
    noResults: 'Aucun article ne correspond aux filtres',
    i18n: { noArticles: 'Aucun article disponible', loading: 'Chargement des articles...', articleCount: '(n) => n === 1 ? \'1 article\' : \'\' + n + \' articles\'' },
    schemaDescription: 'Surveillance du Parlement suédois - Suivre l\'activité politique avec une transparence systématique'
  },
  es: {
    name: 'Español', code: 'es', locale: 'es_ES',
    title: 'Noticias',
    subtitle: 'Últimas noticias y análisis del Parlamento sueco. Periodismo político al estilo de The Economist.',
    keywords: 'riksdag noticias, parlamento sueco, proyectos de ley, informes de comité, mociones parlamentarias, votaciones, análisis político, partidos suecos, transparencia, democracia',
    breadcrumbs: { home: 'Inicio', news: 'Noticias' },
    backLink: 'Volver a la página principal',
    filters: {
      type: 'Tipo:', allTypes: 'Todos los tipos', prospective: 'Prospectivo', retrospective: 'Retrospectivo', analysis: 'Análisis', breaking: 'Última hora',
      topic: 'Tema:', allTopics: 'Todos los temas', parliament: 'Parlamento', government: 'Gobierno', defense: 'Defensa', environment: 'Medio ambiente', committees: 'Comités', legislation: 'Legislación',
      sort: 'Ordenar:', newest: 'Más reciente', oldest: 'Más antiguo', titleSort: 'Título'
    },
    noResults: 'Ningún artículo coincidió con los filtros',
    i18n: { noArticles: 'No hay artículos disponibles', loading: 'Cargando artículos...', articleCount: '(n) => n === 1 ? \'1 artículo\' : \'\' + n + \' artículos\'' },
    schemaDescription: 'Monitoreo del Parlamento sueco - Seguimiento de la actividad política con transparencia sistemática'
  },
  nl: {
    name: 'Nederlands', code: 'nl', locale: 'nl_NL',
    title: 'Nieuws',
    subtitle: 'Laatste nieuws en analyses uit het Zweedse Parlement. Politieke journalistiek in de stijl van The Economist.',
    keywords: 'riksdag nieuws, zweeds parlement, wetsvoorstellen, commissieverslagen, parlementaire moties, stemmingen, politieke analyse, zweedse partijen, transparantie, democratie',
    breadcrumbs: { home: 'Home', news: 'Nieuws' },
    backLink: 'Terug naar hoofdpagina',
    filters: {
      type: 'Type:', allTypes: 'Alle types', prospective: 'Vooruitziend', retrospective: 'Terugblik', analysis: 'Analyse', breaking: 'Laatste nieuws',
      topic: 'Onderwerp:', allTopics: 'Alle onderwerpen', parliament: 'Parlement', government: 'Regering', defense: 'Defensie', environment: 'Milieu', committees: 'Commissies', legislation: 'Wetgeving',
      sort: 'Sorteren:', newest: 'Nieuwste eerst', oldest: 'Oudste eerst', titleSort: 'Titel'
    },
    noResults: 'Geen artikelen voldeden aan de filters',
    i18n: { noArticles: 'Geen artikelen beschikbaar', loading: 'Artikelen laden...', articleCount: '(n) => n === 1 ? \'1 artikel\' : \'\' + n + \' artikelen\'' },
    schemaDescription: 'Zweeds parlementair toezicht - Volg politieke activiteit met systematische transparantie'
  },
  ar: {
    name: 'العربية', code: 'ar', locale: 'ar_SA', rtl: true,
    title: 'أخبار',
    subtitle: 'آخر الأخبار والتحليلات من البرلمان السويدي. صحافة سياسية على طراز ذا إيكونوميست.',
    keywords: 'أخبار البرلمان, البرلمان السويدي, مشاريع القوانين, تقارير اللجان, التصويت, تحليل سياسي, الأحزاب السويدية, شفافية, ديمقراطية',
    breadcrumbs: { home: 'الرئيسية', news: 'أخبار' },
    backLink: 'العودة إلى الصفحة الرئيسية',
    filters: {
      type: 'النوع:', allTypes: 'جميع الأنواع', prospective: 'استشرافي', retrospective: 'استعادي', analysis: 'تحليل', breaking: 'أخبار عاجلة',
      topic: 'الموضوع:', allTopics: 'جميع المواضيع', parliament: 'البرلمان', government: 'الحكومة', defense: 'الدفاع', environment: 'البيئة', committees: 'اللجان', legislation: 'التشريعات',
      sort: 'الترتيب:', newest: 'الأحدث أولاً', oldest: 'الأقدم أولاً', titleSort: 'العنوان'
    },
    noResults: 'لا توجد مقالات تطابق الفلاتر',
    i18n: { noArticles: 'لا توجد مقالات متاحة', loading: 'جارٍ تحميل المقالات...', articleCount: '(n) => n === 1 ? \'مقال واحد\' : \'\' + n + \' مقالات\'' },
    schemaDescription: 'مراقبة البرلمان السويدي - متابعة النشاط السياسي بشفافية منهجية'
  },
  he: {
    name: 'עברית', code: 'he', locale: 'he_IL', rtl: true,
    title: 'חדשות',
    subtitle: 'חדשות ואנליזות אחרונות מהפרלמנט השוודי. עיתונות פוליטית בסגנון דה אקונומיסט.',
    keywords: 'חדשות הפרלמנט, הפרלמנט השוודי, הצעות חוק, דוחות ועדות, הצבעות, ניתוח פוליטי, מפלגות שוודיות, שקיפות, דמוקרטיה',
    breadcrumbs: { home: 'בית', news: 'חדשות' },
    backLink: 'חזרה לדף הבית',
    filters: {
      type: 'סוג:', allTypes: 'כל הסוגים', prospective: 'פרוספקטיבי', retrospective: 'רטרוספקטיבי', analysis: 'ניתוח', breaking: 'חדשות אחרונות',
      topic: 'נושא:', allTopics: 'כל הנושאים', parliament: 'פרלמנט', government: 'ממשלה', defense: 'הגנה', environment: 'סביבה', committees: 'ועדות', legislation: 'חקיקה',
      sort: 'מיון:', newest: 'החדש ביותר', oldest: 'הישן ביותר', titleSort: 'כותרת'
    },
    noResults: 'אין מאמרים שתואמים את הסינון',
    i18n: { noArticles: 'אין מאמרים זמינים', loading: 'טוען מאמרים...', articleCount: '(n) => n === 1 ? \'מאמר אחד\' : \'\' + n + \' מאמרים\'' },
    schemaDescription: 'ניטור הפרלמנט השוודי - מעקב אחר פעילות פוליטית בשקיפות שיטתית'
  },
  ja: {
    name: '日本語', code: 'ja', locale: 'ja_JP',
    title: 'ニュース',
    subtitle: 'スウェーデン国会からの最新ニュースと分析。エコノミスト・スタイルの政治ジャーナリズム。',
    keywords: '国会ニュース, スウェーデン議会, 政府法案, 委員会報告, 採決, 政治分析, スウェーデン政党, 透明性, 民主主義',
    breadcrumbs: { home: 'ホーム', news: 'ニュース' },
    backLink: 'ホームページに戻る',
    filters: {
      type: '種類:', allTypes: 'すべてのタイプ', prospective: '予測', retrospective: '振り返り', analysis: '分析', breaking: '速報',
      topic: 'トピック:', allTopics: 'すべてのトピック', parliament: '議会', government: '政府', defense: '防衛', environment: '環境', committees: '委員会', legislation: '立法',
      sort: '並び替え:', newest: '最新順', oldest: '古い順', titleSort: 'タイトル'
    },
    noResults: 'フィルターに一致する記事がありません',
    i18n: { noArticles: '記事がありません', loading: '記事を読み込み中...', articleCount: '(n) => n === 1 ? \'1件の記事\' : \'\' + n + \'件の記事\'' },
    schemaDescription: 'スウェーデン議会監視プラットフォーム - 体系的な透明性で政治活動を監視'
  },
  ko: {
    name: '한국어', code: 'ko', locale: 'ko_KR',
    title: '뉴스',
    subtitle: '스웨덴 의회의 최신 뉴스 및 분석. 이코노미스트 스타일의 정치 저널리즘.',
    keywords: '의회 뉴스, 스웨덴 의회, 정부 법안, 위원회 보고서, 표결, 정치 분석, 스웨덴 정당, 투명성, 민주주의',
    breadcrumbs: { home: '홈', news: '뉴스' },
    backLink: '홈페이지로 돌아가기',
    filters: {
      type: '유형:', allTypes: '모든 유형', prospective: '전망', retrospective: '회고', analysis: '분석', breaking: '속보',
      topic: '주제:', allTopics: '모든 주제', parliament: '의회', government: '정부', defense: '국방', environment: '환경', committees: '위원회', legislation: '입법',
      sort: '정렬:', newest: '최신순', oldest: '오래된 순', titleSort: '제목'
    },
    noResults: '필터와 일치하는 기사가 없습니다',
    i18n: { noArticles: '기사가 없습니다', loading: '기사 로딩 중...', articleCount: '(n) => n === 1 ? \'1개의 기사\' : \'\' + n + \'개의 기사\'' },
    schemaDescription: '스웨덴 의회 모니터링 플랫폼 - 체계적인 투명성으로 정치 활동 감시'
  },
  zh: {
    name: '中文', code: 'zh', locale: 'zh_CN',
    title: '新闻',
    subtitle: '来自瑞典议会的最新新闻和分析。经济学人风格的政治新闻报道。',
    keywords: '议会新闻, 瑞典议会, 政府法案, 委员会报告, 表决, 政治分析, 瑞典政党, 透明度, 民主',
    breadcrumbs: { home: '主页', news: '新闻' },
    backLink: '返回主页',
    filters: {
      type: '类型:', allTypes: '所有类型', prospective: '前瞻', retrospective: '回顾', analysis: '分析', breaking: '最新消息',
      topic: '主题:', allTopics: '所有主题', parliament: '议会', government: '政府', defense: '国防', environment: '环境', committees: '委员会', legislation: '立法',
      sort: '排序:', newest: '最新优先', oldest: '最旧优先', titleSort: '标题'
    },
    noResults: '没有与过滤器匹配的文章',
    i18n: { noArticles: '没有可用的文章', loading: '正在加载文章...', articleCount: '(n) => n === 1 ? \'1篇文章\' : \'\' + n + \'篇文章\'' },
    schemaDescription: '瑞典议会监督平台 - 以系统化透明度监测政治活动'
  }
};

// Language flags mapping for badges
const LANGUAGE_FLAGS = {
  en: '🇬🇧', sv: '🇸🇪', da: '🇩🇰', no: '🇳🇴', fi: '🇫🇮',
  de: '🇩🇪', fr: '🇫🇷', es: '🇪🇸', nl: '🇳🇱', ar: '🇸🇦',
  he: '🇮🇱', ja: '🇯🇵', ko: '🇰🇷', zh: '🇨🇳'
};

// "Available in" translations for each language
const AVAILABLE_IN_TRANSLATIONS = {
  en: 'Available in', sv: 'Tillgänglig på', da: 'Tilgængelig på', no: 'Tilgjengelig på', fi: 'Saatavilla kielellä',
  de: 'Verfügbar in', fr: 'Disponible en', es: 'Disponible en', nl: 'Beschikbaar in', ar: 'متاح في',
  he: 'זמין ב', ja: '利用可能な言語', ko: '사용 가능 언어', zh: '可用语言'
};

/**
 * Generate language badge HTML for an article
 * @param {string} lang - Language code (e.g., 'en', 'sv')
 * @param {boolean} isRTL - Whether the current display language is RTL
 * @returns {string} HTML for language badge
 */
function generateLanguageBadge(lang, isRTL = false) {
  const flag = LANGUAGE_FLAGS[lang] || '🌐';
  const langUpper = lang.toUpperCase();
  const dirAttr = isRTL ? ' dir="ltr"' : '';
  return `<span class="language-badge"${dirAttr} aria-label="${LANGUAGES[lang]?.name || lang} language"><span aria-hidden="true">${flag}</span> ${langUpper}</span>`;
}

/**
 * Generate language switcher navigation for news index pages
 * @param {string} currentLang - Current language code
 * @returns {string} HTML for language switcher nav
 */
function generateLanguageSwitcherNav(currentLang) {
  const langEntries = Object.entries(LANGUAGES);
  const links = langEntries.map(([code, data]) => {
    const flag = LANGUAGE_FLAGS[code] || '🌐';
    const filename = code === 'en' ? 'index.html' : `index_${code}.html`;
    const activeClass = code === currentLang ? ' active' : '';
    return `  <a href="${filename}" class="lang-link${activeClass}" hreflang="${code}">${flag} ${data.name}</a>`;
  }).join('\n');
  return `<nav class="language-switcher" role="navigation" aria-label="Language selection">\n${links}\n</nav>`;
}

/**
 * Generate "Available in" text with language badges
 * @param {Array} languages - Array of language codes
 * @param {string} currentLang - Current display language
 * @returns {string} HTML for available languages display
 */
function generateAvailableLanguages(languages, currentLang) {
  if (!languages || languages.length <= 1) return '';
  
  const isRTL = ['ar', 'he'].includes(currentLang);
  const availableText = AVAILABLE_IN_TRANSLATIONS[currentLang] || 'Available in';
  const badges = languages.map(lang => generateLanguageBadge(lang, isRTL)).join(' ');
  
  return `<p class="available-languages"><strong>${availableText}:</strong> ${badges}</p>`;
}

console.log('🗂️ Dynamic News Index Generation');
console.log('📍 Scanning news directory:', NEWS_DIR);

/**
 * Parse HTML file to extract article metadata
 */
function parseArticleMetadata(filePath) {
  try {
    const content = fs.readFileSync(filePath, 'utf-8');
    const fileName = path.basename(filePath);
    
    // Extract language from filename (e.g., article-en.html → en, article-da.html → da)
    const langMatch = fileName.match(/-(en|sv|da|no|fi|de|fr|es|nl|ar|he|ja|ko|zh)\.html$/);
    if (!langMatch) {
      console.warn(`  ⚠️ Skipping ${fileName}: no language suffix`);
      return null;
    }
    
    const lang = langMatch[1];
    
    // Extract metadata from HTML meta tags
    const metadata = {
      slug: fileName,
      lang,
      title: extractMetaContent(content, 'og:title') || extractTitle(content) || 'Untitled',
      description: extractMetaContent(content, 'og:description') || extractMetaContent(content, 'description') || '',
      date: normalizeDateString(
        extractMetaContent(content, 'article:published_time') || 
        extractMetaContent(content, 'date') || 
        extractDateFromJSONLD(content) || 
        extractFromFilename(fileName)
      ),
      type: classifyArticleType(content, fileName),
      topics: extractTopics(content),
      tags: extractTags(content)
    };
    
    return metadata;
  } catch (error) {
    console.error(`  ❌ Error parsing ${path.basename(filePath)}:`, error.message);
    return null;
  }
}

/**
 * Extract content from meta tags
 * 
 * Fixed: regex now properly handles apostrophes and special characters in content
 */
function extractMetaContent(html, property) {
  // Match double-quoted attributes
  const doubleQuotePattern = new RegExp(`<meta\\s+(?:property|name)="${property}"\\s+content="([^"]+)"`, 'i');
  const doubleQuoteMatch = html.match(doubleQuotePattern);
  if (doubleQuoteMatch) return doubleQuoteMatch[1];
  
  // Match single-quoted attributes
  const singleQuotePattern = new RegExp(`<meta\\s+(?:property|name)='${property}'\\s+content='([^']+)'`, 'i');
  const singleQuoteMatch = html.match(singleQuotePattern);
  if (singleQuoteMatch) return singleQuoteMatch[1];
  
  // Try reversed order (content before property/name)
  const reversedDoublePattern = new RegExp(`<meta\\s+content="([^"]+)"\\s+(?:property|name)="${property}"`, 'i');
  const reversedDoubleMatch = html.match(reversedDoublePattern);
  if (reversedDoubleMatch) return reversedDoubleMatch[1];
  
  const reversedSinglePattern = new RegExp(`<meta\\s+content='([^']+)'\\s+(?:property|name)='${property}'`, 'i');
  const reversedSingleMatch = html.match(reversedSinglePattern);
  if (reversedSingleMatch) return reversedSingleMatch[1];
  
  return null;
}

/**
 * Extract title from <title> tag
 */
function extractTitle(html) {
  const match = html.match(/<title>([^<]+)<\/title>/i);
  return match ? match[1].replace(' - Riksdagsmonitor', '').trim() : null;
}

/**
 * Normalize date string to YYYY-MM-DD format
 * Handles full ISO timestamps, simple dates, etc.
 * @param {string} dateStr - Date string in various formats
 * @returns {string} Date in YYYY-MM-DD format
 */
function normalizeDateString(dateStr) {
  if (!dateStr) return null;
  
  // If already in YYYY-MM-DD format, return as-is
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
    return dateStr;
  }
  
  // If ISO timestamp (with time), extract just the date part
  if (dateStr.includes('T')) {
    return dateStr.split('T')[0];
  }
  
  // If has timezone offset like +01:00, remove it first
  const cleaned = dateStr.replace(/[+-]\d{2}:\d{2}$/, '');
  if (cleaned.includes('T')) {
    return cleaned.split('T')[0];
  }
  
  return dateStr;
}

/**
 * Extract date from JSON-LD structured data
 * @param {string} html - HTML content
 * @returns {string|null} Date in YYYY-MM-DD format or null
 */
function extractDateFromJSONLD(html) {
  try {
    // Extract JSON-LD script tag content
    const jsonLdMatch = html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/i);
    if (!jsonLdMatch) return null;
    
    const jsonLdText = jsonLdMatch[1].trim();
    const jsonData = JSON.parse(jsonLdText);
    
    // Extract datePublished from NewsArticle schema
    if (jsonData.datePublished) {
      // Handle both full ISO timestamps and simple YYYY-MM-DD dates
      const dateStr = jsonData.datePublished.split('T')[0];
      return dateStr;
    }
    
    return null;
  } catch (error) {
    // Silently fail - this is a fallback mechanism
    return null;
  }
}

/**
 * Extract date from filename (YYYY-MM-DD format)
 */
function extractFromFilename(fileName) {
  const match = fileName.match(/^(\d{4}-\d{2}-\d{2})/);
  return match ? match[1] : new Date().toISOString().split('T')[0];
}

/**
 * Classify article type based on content and filename.
 * Supports detection keywords in all 14 languages.
 */
function classifyArticleType(content, fileName) {
  const lowerContent = content.toLowerCase();
  
  // Prospective: week-ahead / upcoming previews
  const prospectiveKeywords = [
    'week ahead', 'week-ahead', 'upcoming', 'preview', 'look ahead',           // en
    'veckan som kommer', 'kommande', 'framåtblick',                             // sv
    'ugen der kommer', 'kommende', 'fremadrettet',                              // da
    'uken som kommer', 'fremtidsrettet',                                        // no
    'tuleva viikko', 'tulevat', 'ennakko',                                      // fi
    'woche voraus', 'vorschau',                                                 // de
    'semaine à venir', 'aperçu',                                                // fr
    'semana por delante', 'adelanto',                                            // es
    'week vooruit', 'vooruitblik',                                               // nl
    'الأسبوع المقبل', 'القادم',                                                  // ar
    'השבוע הבא', 'הקרוב',                                                       // he
    '来週の展望', '今後',                                                          // ja
    '주간 전망', '다가오는',                                                       // ko
    '一周展望', '即将'                                                             // zh
  ];
  
  if (fileName.includes('week-ahead') || prospectiveKeywords.some(kw => lowerContent.includes(kw.toLowerCase()))) {
    return 'prospective';
  }
  
  // Analysis: committee reports, propositions, motions
  const analysisKeywords = [
    'committee reports', 'analysis', 'review', 'assessment',                     // en
    'utskottsbetänkanden', 'analys', 'granskning', 'betänkande',                // sv
    'udvalgsrapporter', 'analyse', 'gennemgang', 'udvalgsbetænkning',           // da
    'komitérapporter', 'gjennomgang', 'komitéinnstilling',                      // no
    'valiokuntaraportit', 'analyysi', 'katsaus', 'valiokunnan mietintö',        // fi
    'ausschussberichte', 'überprüfung', 'ausschussbericht',                     // de
    'rapports de commission', 'examen', 'rapport de commission',                 // fr
    'informes de comité', 'análisis', 'revisión', 'informe de comité',          // es
    'commissierapporten', 'beoordeling', 'commissieverslag',                     // nl
    'تقارير اللجان', 'تحليل', 'تقرير اللجنة',                                  // ar
    'דוחות ועדות', 'ניתוח', 'דוח ועדה',                                         // he
    '委員会報告', '分析',                                                          // ja
    '위원회 보고서', '분석',                                                       // ko
    '委员会报告', '分析'                                                           // zh
  ];
  
  if (fileName.includes('committee-reports') || fileName.includes('propositions') || fileName.includes('motions') ||
      analysisKeywords.some(kw => lowerContent.includes(kw.toLowerCase()))) {
    return 'analysis';
  }
  
  // Breaking: urgent/alert news
  const breakingKeywords = [
    'breaking', 'urgent', 'alert', 'flash',                                      // en
    'senaste nytt', 'akut', 'brådskande',                                        // sv
    'seneste nyt', 'hastesag',                                                   // da
    'siste nytt', 'haster',                                                      // no
    'viimeisimmät', 'kiireellinen', 'hälytys',                                   // fi
    'eilmeldungen', 'dringend', 'alarm',                                         // de
    'dernières nouvelles', 'alerte',                                              // fr
    'última hora', 'urgente', 'alerta',                                           // es
    'laatste nieuws', 'alert',                                                    // nl
    'أخبار عاجلة', 'عاجل',                                                       // ar
    'חדשות אחרונות', 'דחוף',                                                     // he
    '速報', '緊急',                                                               // ja
    '속보', '긴급',                                                               // ko
    '突发新闻', '紧急'                                                             // zh
  ];
  
  if (fileName.includes('breaking') || breakingKeywords.some(kw => lowerContent.includes(kw.toLowerCase()))) {
    return 'breaking';
  }
  
  return 'retrospective';
}

/**
 * Extract topics from article tags.
 * Supports topic detection keywords in all 14 languages.
 */
function extractTopics(content) {
  const topics = [];
  const tagPattern = /<meta\s+property=["']article:tag["']\s+content=["']([^"']+)["']/gi;
  let match;
  
  while ((match = tagPattern.exec(content)) !== null) {
    const tag = match[1].toLowerCase();
    if (tag.includes('eu')) topics.push('eu');
    if (tag.includes('parliament') || tag.includes('riksdag') || tag.includes('parlamentet') || tag.includes('議会') || tag.includes('의회') || tag.includes('议会') || tag.includes('البرلمان') || tag.includes('פרלמנט')) topics.push('parliament');
    if (tag.includes('government') || tag.includes('regering') || tag.includes('regjeringen') || tag.includes('hallitus') || tag.includes('regierung') || tag.includes('gouvernement') || tag.includes('gobierno') || tag.includes('政府') || tag.includes('정부') || tag.includes('الحكومة') || tag.includes('ממשלה')) topics.push('government');
    if (tag.includes('defense') || tag.includes('defence') || tag.includes('försvar') || tag.includes('forsvar') || tag.includes('puolustus') || tag.includes('verteidigung') || tag.includes('défense') || tag.includes('defensa') || tag.includes('defensie') || tag.includes('الدفاع') || tag.includes('הגנה') || tag.includes('防衛') || tag.includes('국방') || tag.includes('国防')) topics.push('defense');
    if (tag.includes('environment') || tag.includes('miljö') || tag.includes('miljø') || tag.includes('ympäristö') || tag.includes('umwelt') || tag.includes('environnement') || tag.includes('medio ambiente') || tag.includes('milieu') || tag.includes('البيئة') || tag.includes('סביבה') || tag.includes('環境') || tag.includes('환경') || tag.includes('环境')) topics.push('environment');
    if (tag.includes('committee') || tag.includes('utskott') || tag.includes('udvalg') || tag.includes('utvalg') || tag.includes('valiokunt') || tag.includes('ausschuss') || tag.includes('commission') || tag.includes('comité') || tag.includes('commissie') || tag.includes('لجنة') || tag.includes('ועדה') || tag.includes('委員会') || tag.includes('위원회') || tag.includes('委员会')) topics.push('committees');
    if (tag.includes('legislation') || tag.includes('lagstiftning') || tag.includes('lovgivning') || tag.includes('lainsäädäntö') || tag.includes('gesetzgebung') || tag.includes('législation') || tag.includes('legislación') || tag.includes('wetgeving') || tag.includes('التشريعات') || tag.includes('חקיקה') || tag.includes('立法') || tag.includes('입법')) topics.push('legislation');
  }
  
  return [...new Set(topics)].slice(0, 5); // Unique, max 5
}

/**
 * Extract tags from article:tag meta tags
 */
function extractTags(content) {
  const tags = [];
  const tagPattern = /<meta\s+property=["']article:tag["']\s+content=["']([^"']+)["']/gi;
  let match;
  
  while ((match = tagPattern.exec(content)) !== null) {
    tags.push(match[1]);
  }
  
  return tags.slice(0, 4); // Max 4 tags for display
}

/**
 * Scan news directory and group articles by language
 */
function scanNewsArticles() {
  console.log('\n📰 Scanning for articles...');
  
  const files = fs.readdirSync(NEWS_DIR)
    .filter(file => file.endsWith('.html'))
    .filter(file => !file.startsWith('index')); // Exclude index files
  
  console.log(`  Found ${files.length} article files`);
  
  // Initialize buckets for all 14 supported languages
  const articlesByLang = Object.fromEntries(
    Object.keys(LANGUAGES).map(lang => [lang, []])
  );
  
  files.forEach(file => {
    const filePath = path.join(NEWS_DIR, file);
    const metadata = parseArticleMetadata(filePath);
    
    if (metadata && articlesByLang[metadata.lang]) {
      articlesByLang[metadata.lang].push(metadata);
    }
  });
  
  // Sort by date descending (newest first)
  Object.keys(articlesByLang).forEach(lang => {
    articlesByLang[lang].sort((a, b) => new Date(b.date) - new Date(a.date));
  });
  
  const langCounts = Object.entries(articlesByLang)
    .filter(([, arr]) => arr.length > 0)
    .map(([lang, arr]) => `${lang.toUpperCase()} ${arr.length}`);
  console.log(`  📊 Articles by language: ${langCounts.length > 0 ? langCounts.join(', ') : 'none found'}`);
  
  return articlesByLang;
}

/**
 * Build map of base slugs to available languages for cross-language discovery
 * 
 * Detects articles with the same base slug (e.g., "2026-02-14-week-ahead")
 * across different languages and maps slug -> [language codes].
 * 
 * @param {Object} articlesByLang - Articles grouped by language
 * @returns {Object} Map of slug -> array of language codes
 */
function buildSlugToLanguagesMap(articlesByLang) {
  const slugToLanguages = {};
  
  // Iterate through all articles in all languages
  Object.entries(articlesByLang).forEach(([lang, articles]) => {
    articles.forEach(article => {
      // Strip language suffix from slug to get base slug
      // e.g., "2026-02-14-article-en.html" -> "2026-02-14-article.html"
      const baseSlug = article.slug.replace(/-(en|sv|da|no|fi|de|fr|es|nl|ar|he|ja|ko|zh)\.html$/, '.html');
      
      if (!slugToLanguages[article.slug]) {
        // Initialize with base slug mapping
        slugToLanguages[article.slug] = [];
      }
      
      // Find all articles with the same base slug across languages
      Object.entries(articlesByLang).forEach(([otherLang, otherArticles]) => {
        otherArticles.forEach(otherArticle => {
          const otherBaseSlug = otherArticle.slug.replace(/-(en|sv|da|no|fi|de|fr|es|nl|ar|he|ja|ko|zh)\.html$/, '.html');
          
          if (baseSlug === otherBaseSlug && !slugToLanguages[article.slug].includes(otherLang)) {
            slugToLanguages[article.slug].push(otherLang);
          }
        });
      });
    });
  });
  
  return slugToLanguages;
}

/**
 * Get all articles with language information for cross-language discovery
 * 
 * NOTE: This function is currently UNUSED in production but preserved for potential
 * future use. It was implemented for Issue #155's cross-language discovery feature
 * but the requirement changed to language-specific filtering (each index shows only
 * articles in its target language).
 * 
 * If cross-language discovery is needed again, this function can be used instead of
 * passing articlesByLang[langKey] to generateIndexHTML() on line 958.
 * 
 * This function collects ALL articles from all languages and enriches each
 * with metadata about which language versions are available for the same slug.
 * 
 * @param {Object} articlesByLang - Articles grouped by language
 * @returns {Array} All articles with availableLanguages field
 * @deprecated Currently unused - kept for potential future cross-language discovery
 */
function getAllArticlesWithLanguageInfo(articlesByLang) {
  // Build a map of slugs to available languages
  const slugToLanguages = new Map();
  
  Object.entries(articlesByLang).forEach(([lang, articles]) => {
    articles.forEach(article => {
      // Extract base slug (remove language suffix)
      const baseSlug = article.slug.replace(/-(en|sv|da|no|fi|de|fr|es|nl|ar|he|ja|ko|zh)\.html$/, '');
      
      if (!slugToLanguages.has(baseSlug)) {
        slugToLanguages.set(baseSlug, []);
      }
      slugToLanguages.get(baseSlug).push(lang);
    });
  });
  
  // Collect all articles and enrich with language info
  const allArticles = [];
  
  Object.entries(articlesByLang).forEach(([lang, articles]) => {
    articles.forEach(article => {
      const baseSlug = article.slug.replace(/-(en|sv|da|no|fi|de|fr|es|nl|ar|he|ja|ko|zh)\.html$/, '');
      const availableLanguages = slugToLanguages.get(baseSlug) || [lang];
      
      allArticles.push({
        ...article,
        availableLanguages: availableLanguages.sort(),
        baseSlug
      });
    });
  });
  
  // Sort by date descending (newest first)
  allArticles.sort((a, b) => new Date(b.date) - new Date(a.date));
  
  return allArticles;
}

/**
 * Generate index HTML for a specific language
 * 
 * Each language index displays only articles in that specific language.
 * Articles include metadata about which other languages they're available in
 * for cross-language discovery indicators.
 * 
 * @param {string} langKey - Language code (en, sv, etc.)
 * @param {Array} languageArticles - Articles in the target language only
 * @param {Object} allArticlesByLang - All articles grouped by language
 */
function generateIndexHTML(langKey, languageArticles, allArticlesByLang) {
  const lang = LANGUAGES[langKey];
  const f = lang.filters;
  const filename = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
  const mainIndex = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
  const isRTL = ['ar', 'he'].includes(langKey);
  
  // Display only articles in this language
  const displayArticles = languageArticles;
  const needsLanguageNotice = languageArticles.length === 0;
  
  const escapedSubtitle = escapeHtml(lang.subtitle);

  const html = `<!DOCTYPE html>
<html lang="${lang.code}"${lang.rtl ? ' dir="rtl"' : ''}>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${escapeHtml(lang.title)} - Riksdagsmonitor</title>
  <meta name="description" content="${escapedSubtitle}">
  <meta name="keywords" content="${escapeHtml(lang.keywords)}">
  <meta name="author" content="James Pether Sörling, CISSP, CISM">
  <link rel="canonical" href="https://riksdagsmonitor.com/news/${filename}">
  
  <!-- Open Graph -->
  <meta property="og:title" content="${escapeHtml(lang.title)} - Riksdagsmonitor">
  <meta property="og:description" content="${escapedSubtitle}">
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://riksdagsmonitor.com/news/${filename}">
  <meta property="og:image" content="https://hack23.com/cia-icon-140.webp">
  <meta property="og:site_name" content="Riksdagsmonitor">
  <meta property="og:locale" content="${lang.locale}">
  
  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="${escapeHtml(lang.title)} - Riksdagsmonitor">
  <meta name="twitter:description" content="${escapedSubtitle}">
  <meta name="twitter:image" content="https://hack23.com/cia-icon-140.webp">
  
  <!-- Hreflang -->
${generateHreflangTags()}
  
  <!-- Schema.org ItemList structured data for article aggregation -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "ItemList",
    "name": "${escapeHtml(lang.title)}",
    "description": "${escapedSubtitle}",
    "numberOfItems": ${displayArticles.length},
    "itemListElement": [${displayArticles.slice(0, 10).map((article, index) => `
      {
        "@type": "ListItem",
        "position": ${index + 1},
        "item": {
          "@type": "NewsArticle",
          "headline": "${escapeHtml(article.title)}",
          "url": "https://riksdagsmonitor.com/news/${article.slug}",
          "datePublished": "${article.date}",
          "description": "${escapeHtml(article.description).substring(0, 150)}",
          "inLanguage": "${article.lang || lang.code}",
          "author": {
            "@type": "Organization",
            "name": "Riksdagsmonitor"
          },
          "publisher": {
            "@type": "Organization",
            "name": "Hack23 AB",
            "logo": {
              "@type": "ImageObject",
              "url": "https://hack23.com/cia-icon-140.webp"
            }
          },
          "articleSection": "${escapeHtml(lang.breadcrumbs.news)}",
          "about": {
            "@type": "GovernmentOrganization",
            "name": "Riksdag",
            "alternateName": "Swedish Parliament",
            "url": "https://www.riksdagen.se/"
          }
        }
      }`).join(',')}
    ]
  }
  </script>
  
  <!-- BreadcrumbList structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "${escapeHtml(lang.breadcrumbs.home)}",
        "item": "https://riksdagsmonitor.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "${escapeHtml(lang.breadcrumbs.news)}",
        "item": "https://riksdagsmonitor.com/news/${filename}"
      }
    ]
  }
  </script>
  
  <!-- WebSite structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebSite",
    "name": "Riksdagsmonitor",
    "url": "https://riksdagsmonitor.com",
    "description": "${escapeHtml(lang.schemaDescription || 'Swedish Parliament Intelligence Platform - Monitor political activity with systematic transparency')}",
    "inLanguage": "${lang.code}",
    "publisher": {
      "@type": "Organization",
      "name": "Hack23 AB",
      "logo": {
        "@type": "ImageObject",
        "url": "https://hack23.com/cia-icon-140.webp"
      }
    },
    "potentialAction": {
      "@type": "SearchAction",
      "target": "https://riksdagsmonitor.com/news/${filename}?q={search_term_string}",
      "query-input": "required name=search_term_string"
    }
  }
  </script>
  
  <!-- Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@400;500;600;700&display=swap" rel="stylesheet">
  
  <link rel="stylesheet" href="../styles.css">
  ${generateRTLStyles(lang.rtl)}
</head>
<body class="news-page">
  <header class="header-section">
    <div class="header-content">
      <h1>${escapeHtml(lang.title)}</h1>
      <p class="subtitle">${lang.subtitle}</p>
      <a href="../${mainIndex}" class="back-link">\u2190 ${escapeHtml(lang.backLink)}</a>
    </div>
  </header>
  ${generateLanguageSwitcherNav(langKey)}
  <main role="main">
  <div class="container">
${needsLanguageNotice ? generateLanguageNotice(langKey) : ''}
    
    <!-- Filter Bar -->
    <div class="filter-bar">
      <div class="filter-group">
        <label for="filter-type">${f.type}</label>
        <select id="filter-type">
          <option value="all">${escapeHtml(f.allTypes)}</option>
          <option value="prospective">${escapeHtml(f.prospective)}</option>
          <option value="retrospective">${escapeHtml(f.retrospective)}</option>
          <option value="analysis">${escapeHtml(f.analysis)}</option>
          <option value="breaking">${escapeHtml(f.breaking)}</option>
        </select>
      </div>
      
      <div class="filter-group">
        <label for="filter-topic">${f.topic}</label>
        <select id="filter-topic">
          <option value="all">${escapeHtml(f.allTopics)}</option>
          <option value="parliament">${escapeHtml(f.parliament)}</option>
          <option value="government">${escapeHtml(f.government)}</option>
          <option value="eu">EU</option>
          <option value="defense">${escapeHtml(f.defense)}</option>
          <option value="environment">${escapeHtml(f.environment)}</option>
          <option value="committees">${escapeHtml(f.committees)}</option>
          <option value="legislation">${escapeHtml(f.legislation)}</option>
        </select>
      </div>
      
      <div class="filter-group">
        <label for="filter-sort">${f.sort}</label>
        <select id="filter-sort">
          <option value="date-desc">${escapeHtml(f.newest)}</option>
          <option value="date-asc">${escapeHtml(f.oldest)}</option>
          <option value="title">${escapeHtml(f.titleSort)}</option>
        </select>
      </div>
    </div>
    
    <!-- Articles Grid -->
    <div class="articles-grid" id="articles-grid"></div>
    
    <div id="no-results" style="display: none; text-align: center; padding: 3rem; color: #888;">
      ${escapeHtml(lang.noResults)}
    </div>
  </div>
  
  <script>
    // Language flags mapping (shared with server-side)
    const LANGUAGE_FLAGS = ${JSON.stringify(LANGUAGE_FLAGS)};
    
    // Available in translation (for current language)
    const AVAILABLE_IN_TEXT = '${escapeHtml(AVAILABLE_IN_TRANSLATIONS[langKey] || 'Available in')}';
    
    // Dynamic articles array - generated from news/ directory
    const articles = ${JSON.stringify(displayArticles.map(a => ({
      title: a.title,
      date: a.date,
      type: a.type,
      slug: a.slug,
      lang: a.lang,
      availableLanguages: a.availableLanguages || [a.lang],
      excerpt: a.description.substring(0, 200),
      topics: a.topics,
      tags: a.tags
    })), null, 2)};
    
    let filteredArticles = [...articles];
    
    function renderArticles(articlesToRender) {
      const grid = document.getElementById('articles-grid');
      const noResults = document.getElementById('no-results');
      
      if (articlesToRender.length === 0) {
        grid.innerHTML = '';
        noResults.style.display = 'block';
        return;
      }
      
      noResults.style.display = 'none';
      
      grid.innerHTML = articlesToRender.map(article => {
        // Generate language badge for the article using shared LANGUAGE_FLAGS
        const flag = LANGUAGE_FLAGS[article.lang] || '🌐';
        const langBadge = \`<span class="language-badge" aria-label="\${article.lang} language"><span aria-hidden="true">\${flag}</span> \${article.lang.toUpperCase()}</span>\`;
        
        // Generate available languages display if multiple languages exist
        const availableLangs = article.availableLanguages || [article.lang];
        let availableDisplay = '';
        if (availableLangs.length > 1) {
          const availableBadges = availableLangs.map(l => {
            const f = LANGUAGE_FLAGS[l] || '🌐';
            return \`<span class="lang-badge-sm"><span aria-hidden="true">\${f}</span> \${l.toUpperCase()}</span>\`;
          }).join(' ');
          availableDisplay = \`<p class="available-languages"><strong>\${AVAILABLE_IN_TEXT}:</strong> \${availableBadges}</p>\`;
        }
        
        return \`
        <article class="article-card">
          <div class="article-meta">
            <time class="article-date" datetime="\${article.date}">\${formatDate(article.date)}</time>
            <span class="article-type">\${localizeType(article.type)}</span>
            \${langBadge}
          </div>
          <h2 class="article-title">
            <a href="\${article.slug}">\${article.title}</a>
          </h2>
          <p class="article-excerpt">\${article.excerpt}</p>
          \${availableDisplay}
          <div class="article-tags">
            \${article.tags.map(tag => \`<span class="tag">\${tag}</span>\`).join('')}
          </div>
        </article>
      \`;
      }).join('');
    }
    
    const typeLabels = ${JSON.stringify({
      prospective: f.prospective,
      retrospective: f.retrospective,
      analysis: f.analysis,
      breaking: f.breaking
    })};
    
    function localizeType(type) {
      return typeLabels[type] || type;
    }
    
    function formatDate(dateStr) {
      const date = new Date(dateStr);
      return date.toLocaleDateString('${lang.code}', { year: 'numeric', month: 'long', day: 'numeric' });
    }
    
    function filterArticles() {
      const typeFilter = document.getElementById('filter-type').value;
      const topicFilter = document.getElementById('filter-topic').value;
      const sortFilter = document.getElementById('filter-sort').value;
      
      let filtered = [...articles];
      
      // Apply type filter
      if (typeFilter !== 'all') {
        filtered = filtered.filter(article => article.type === typeFilter);
      }
      
      // Apply topic filter
      if (topicFilter !== 'all') {
        filtered = filtered.filter(article => article.topics.includes(topicFilter));
      }
      
      // Apply sorting
      switch(sortFilter) {
        case 'date-desc':
          filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
          break;
        case 'date-asc':
          filtered.sort((a, b) => new Date(a.date) - new Date(b.date));
          break;
        case 'title':
          filtered.sort((a, b) => a.title.localeCompare(b.title));
          break;
      }
      
      filteredArticles = filtered;
      renderArticles(filteredArticles);
    }
    
    // Event listeners
    document.getElementById('filter-type').addEventListener('change', filterArticles);
    document.getElementById('filter-topic').addEventListener('change', filterArticles);
    document.getElementById('filter-sort').addEventListener('change', filterArticles);
    
    // Initial render
    filterArticles();
  </script>

  <!-- Dynamic Content Loader -->
  <script>
    // Localization data
    const i18n = {
      noArticles: '${lang.i18n.noArticles}',
      loading: '${lang.i18n.loading}',
      articleCount: ${lang.i18n.articleCount}
    };
    
    // Dynamic content loader
    document.addEventListener('DOMContentLoaded', () => {
      const articlesGrid = document.querySelector('.articles-grid');
      if (!articlesGrid) return;
      
      const articleCards = articlesGrid.querySelectorAll('.article-card');
      const articleCount = articleCards.length;
      
      // Update article count if element exists
      const countElement = document.querySelector('.article-count');
      if (countElement) {
        countElement.textContent = i18n.articleCount(articleCount);
      }
      
      // Show no articles message if empty
      if (articleCount === 0) {
        articlesGrid.innerHTML = \`<p class="no-articles">\${i18n.noArticles}</p>\`;
      }
    });
  </script>

  </main>
  <footer class="footer-section">
    <p>&copy; 2026 Riksdagsmonitor - Swedish Parliament Intelligence</p>
  </footer>
</body>
</html>`;
  
  return html;
}

/**
 * Generate hreflang tags for all languages
 */
function generateHreflangTags() {
  const tags = [];
  
  Object.keys(LANGUAGES).forEach(langKey => {
    const filename = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
    const hrefLang = LANGUAGES[langKey].code;
    tags.push(`  <link rel="alternate" hreflang="${hrefLang}" href="https://riksdagsmonitor.com/news/${filename}">`);
  });
  
  tags.push(`  <link rel="alternate" hreflang="x-default" href="https://riksdagsmonitor.com/news/index.html">`);
  
  return tags.join('\n');
}

/**
 * Generate inline CSS
 */
/**
 * Generate minimal RTL-specific styles
 * All other styles are now in styles.css under .news-page scope
 */
function generateRTLStyles(isRTL) {
  if (!isRTL) return '';
  
  return `
  <style>
    /* RTL-specific overrides for Arabic and Hebrew */
    .news-page .language-notice {
      border-left: none;
      border-right: 4px solid var(--primary-yellow, #ffbe0b);
    }
    
    .news-page .language-badge {
      margin-left: 0;
      margin-right: 0.5rem;
    }
    
    .news-page .back-link:hover {
      transform: translateX(5px); /* Reverse direction for RTL */
    }
  </style>`;
}

/**
 * Generate language availability notice for non-EN/SV indexes
 */
function generateLanguageNotice(langKey) {
  const messages = {
    da: { title: 'Artikler tilgængelige på engelsk', text: 'Artikler er i øjeblikket kun tilgængelige på engelsk og svensk. Automatisk oversættelse til dansk kommer snart.' },
    no: { title: 'Artikler tilgjengelige på engelsk', text: 'Artikler er for tiden kun tilgjengelige på engelsk og svensk. Automatisk oversettelse til norsk kommer snart.' },
    fi: { title: 'Artikkelit saatavilla englanniksi', text: 'Artikkelit ovat tällä hetkellä saatavilla vain englanniksi ja ruotsiksi. Automaattinen käännös suomeksi tulossa pian.' },
    de: { title: 'Artikel auf Englisch verfügbar', text: 'Artikel sind derzeit nur auf Englisch und Schwedisch verfügbar. Automatische Übersetzung ins Deutsche folgt in Kürze.' },
    fr: { title: 'Articles disponibles en anglais', text: 'Les articles ne sont actuellement disponibles qu\'en anglais et en suédois. La traduction automatique en français arrive bientôt.' },
    es: { title: 'Artículos disponibles en inglés', text: 'Los artículos actualmente solo están disponibles en inglés y sueco. La traducción automática al español estará disponible pronto.' },
    nl: { title: 'Artikelen beschikbaar in het Engels', text: 'Artikelen zijn momenteel alleen beschikbaar in het Engels en Zweeds. Automatische vertaling naar het Nederlands komt binnenkort.' },
    ar: { title: 'المقالات متاحة بالإنجليزية', text: 'المقالات متاحة حالياً باللغتين الإنجليزية والسويدية فقط. الترجمة الآلية إلى العربية قريباً.' },
    he: { title: 'מאמרים זמינים באנגלית', text: 'מאמרים זמינים כעת רק באנגלית ובשוודית. תרגום אוטומטי לעברית בקרוב.' },
    ja: { title: '英語で利用可能な記事', text: '記事は現在、英語とスウェーデン語のみで利用可能です。日本語への自動翻訳は近日公開予定です。' },
    ko: { title: '영어로 제공되는 기사', text: '기사는 현재 영어와 스웨덴어로만 제공됩니다. 한국어 자동 번역이 곧 제공될 예정입니다.' },
    zh: { title: '文章以英文提供', text: '文章目前仅提供英文和瑞典文版本。中文自动翻译即将推出。' }
  };
  
  const msg = messages[langKey];
  if (!msg) return '';
  
  const isRTL = ['ar', 'he'].includes(langKey);
  
  return `    <div class="language-notice">
      <h2>${msg.title}</h2>
      <p>${msg.text} <span class="language-badge"${isRTL ? ' dir="ltr"' : ''} aria-label="English language"><span aria-hidden="true">🇬🇧</span> EN</span></p>
    </div>
`;
}

/**
 * Main generation function
 */
function generateAllIndexes() {
  console.log('\n🚀 Generating dynamic news indexes...');
  
  // Scan news directory
  const articlesByLang = scanNewsArticles();
  
  // Build slug-to-languages map for cross-language discovery
  const slugToLanguages = buildSlugToLanguagesMap(articlesByLang);
  
  // Generate index for each language
  console.log('\n📝 Generating index files...');
  
  let successCount = 0;
  let errorCount = 0;
  
  Object.keys(LANGUAGES).forEach(langKey => {
    try {
      const filename = langKey === 'en' ? 'index.html' : `index_${langKey === 'no' ? 'no' : langKey}.html`;
      const filePath = path.join(NEWS_DIR, filename);
      
      // Use language-specific articles and enrich with availableLanguages
      const languageArticles = articlesByLang[langKey] || [];
      
      // Enrich each article with availableLanguages for cross-language discovery
      const enrichedArticles = languageArticles.map(article => ({
        ...article,
        availableLanguages: slugToLanguages[article.slug] || [article.lang]
      }));
      
      const html = generateIndexHTML(langKey, enrichedArticles, articlesByLang);
      fs.writeFileSync(filePath, html, 'utf-8');
      
      console.log(`  ✅ Generated: ${filename} (${languageArticles.length} articles)`);
      successCount++;
    } catch (error) {
      console.error(`  ❌ Failed to generate ${langKey}:`, error.message);
      errorCount++;
    }
  });
  
  console.log('\n✨ Generation complete!');
  console.log(`  ✅ Success: ${successCount} files`);
  console.log(`  ❌ Errors: ${errorCount} files`);
  const totalArticles = Object.values(articlesByLang).reduce((sum, arr) => sum + arr.length, 0);
  console.log(`  📊 Total articles: ${totalArticles}`);
  
  return {
    success: errorCount === 0,
    successCount,
    errorCount,
    articles: articlesByLang
  };
}

// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
  try {
    const result = generateAllIndexes();
    process.exit(result.success ? 0 : 1);
  } catch (error) {
    console.error('\n❌ Fatal error:', error.message);
    console.error(error.stack);
    process.exit(1);
  }
}

export { 
  generateAllIndexes, 
  parseArticleMetadata, 
  scanNewsArticles,
  getAllArticlesWithLanguageInfo,
  generateLanguageBadge,
  generateAvailableLanguages
};