Source: scripts/article-template.js

/**
 * @module Intelligence Operations/Article Template Generation
 * @category Intelligence Operations - Intelligence Report Templates
 * 
 * @description
 * Advanced HTML article template engine generating professional intelligence reports
 * with semantic markup compliance and accessibility standards. This module implements
 * template-based content generation for automated news articles, maintaining consistent
 * visual hierarchy and information architecture across all 14 supported languages.
 * 
 * The template engine provides intelligent document structure rendering:
 * 
 * Semantic HTML5 Structure:
 * - Article headers with publication metadata and source attribution
 * - Navigation breadcrumbs with language-localized labels
 * - Event calendar grid visualization (for Week Ahead article type)
 * - Content sections with intelligent hierarchy (h2-h4)
 * - Context boxes for supplementary intelligence (facts, quotes, analysis)
 * - Related links section pointing to source documents
 * - Watch section extracting critical points and risk indicators
 * - Footer with publication metadata and change history
 * 
 * Design System Integration:
 * - Cyberpunk visual theme with custom CSS variables
 * - Responsive grid layout (CSS Grid, 320px-1440px+ viewport support)
 * - Dark theme optimization for reduced eye strain during extended reading
 * - Accessibility features: ARIA landmarks, semantic heading hierarchy, color contrast
 * - Print-friendly styling preserving readability in physical distribution
 * 
 * Multi-Language Rendering (14 languages):
 * - Dynamic breadcrumb translation for navigation
 * - Localized date formatting and timezone handling
 * - RTL language support (Arabic, Hebrew) with automatic text direction
 * - Language-specific typography and character spacing adjustments
 * - Multi-language footer labels and publication information
 * 
 * Article Type Templates:
 * - Week Ahead: Event grid with day-by-day parliamentary schedule
 * - Committee Reports: Structured committee analysis with member lists
 * - Propositions: Government proposal analysis with impact assessment
 * - Motions: Parliamentary motion analysis with voting predictions
 * - Breaking: Rapid-response template with minimal processing delay
 * 
 * SEO Optimization:
 * - Structured data (Schema.org JSON-LD) for search engine discovery
 * - Meta tag generation (OpenGraph, Twitter Card) for social sharing
 * - Canonical URL prevention of duplicate content penalties
 * - Keyword placement in headers, metadata, and internal linking
 * 
 * @intelligence
 * Content Architecture Methodology:
 * Implements inverted pyramid news structure with intelligence-specific adaptations:
 * - Lede: Most significant political development with immediate impact implications
 * - Summary: Key facts and context necessary for understanding
 * - Analysis: Deeper dive into causation, stakeholders, and political implications
 * - Source Documentation: Full links to primary sources for verification
 * - Predictions: Forward-looking implications and related upcoming events
 * 
 * Visual Information Design:
 * - Event calendars as primary visual element for week-ahead articles
 * - Risk indicator icons (⚠️ critical, ⚡ urgent, 📌 watch)
 * - Timeline visualization for multi-day legislative processes
 * - Party affiliation color-coding for quick visual analysis
 * - Committee jurisdiction mapping for organizational intelligence
 * 
 * Template Customization Patterns:
 * - Data binding for dynamic content injection
 * - Conditional rendering for optional sections (context, related links)
 * - Layout variants based on article metadata
 * - Theme customization via CSS custom properties
 * 
 * @osint
 * Source Presentation Strategy:
 * - Hyperlinked document references to official Riksdagen sources
 * - MCP tool references for reproducible source verification
 * - Publication dates aligned with source data collection timestamp
 * - Source status indicators (official, preliminary, final) based on data freshness
 * 
 * Document Link Integration:
 * - Generate direct URLs to Riksdagen.se and Regeringen.se documents
 * - Construct deep links for specific sections of long documents
 * - Fallback link strategies when document IDs are incomplete
 * - Link health monitoring via CI/CD validation workflows
 * 
 * @risk
 * Template Rendering Risks:
 * 
 * Threat: XSS Injection via Content
 * - Malicious scripts in MCP response data
 * - Mitigation: HTML entity escaping via escapeHtml() utility
 * 
 * Threat: CSS Injection
 * - Style-based information disclosure or visual manipulation
 * - Mitigation: Style sandboxing, no user-controlled style attributes
 * 
 * Threat: Layout Rendering Failures
 * - Article display broken across different browsers/devices
 * - Mitigation: Cross-browser testing, responsive design validation
 * 
 * Threat: SEO Poisoning
 * - Template generating duplicate meta tags or manipulated structured data
 * - Mitigation: Schema validation, canonical URL enforcement
 * 
 * @gdpr
 * Data Protection in Template Rendering:
 * 
 * - HTML Output: No personal data in HTML output (template-level)
 * - Link Sanitization: No tracking parameters or analytics IDs in generated links
 * - Historical Preservation: Article HTML may be archived indefinitely
 * - Cookie Compliance: Template does not set cookies (third-party responsibility)
 * - Analytics Transparency: If analytics codes added, user consent required
 * 
 * @security
 * Content Security Analysis:
 * 
 * Input Validation:
 * - Content parameters validated against expected types
 * - HTML strings escaped via escapeHtml() helper
 * - URLs validated and normalized before href insertion
 * 
 * Output Encoding:
 * - UTF-8 character encoding enforced
 * - HTML entities for special characters
 * - CSS class names sanitized (alphanumeric + hyphen only)
 * 
 * Dependency Security:
 * - html-utils module provides sanitization functions
 * - No external script dependencies or CDN reliance
 * - Inline styling only, no dynamic CSS imports
 * 
 * @author Hack23 AB - Intelligence Operations Team
 * @license Apache-2.0
 * @version 2.0.0
 * 
 * @see {@link ./html-utils.js} HTML sanitization utilities (escapeHtml)
 * @see {@link ./data-transformers.js} Data transformation producing template input
 * @see {@link ./generate-news-enhanced.js} Article generation orchestration
 * @see {@link ./editorial-pillars.js} Editorial structure definitions
 * @see {@link docs/TEMPLATE_ARCHITECTURE.md} Template design documentation
 * @see {@link docs/ACCESSIBILITY_STANDARDS.md} WCAG 2.1 AA compliance guide
 * @see {@link docs/SEO_OPTIMIZATION.md} Search engine optimization strategy
 */

import { escapeHtml } from './html-utils.js';

/**
 * Breadcrumb translations for all supported languages
 */
const BREADCRUMB_TRANSLATIONS = {
  en: { home: 'Home', news: 'News' },
  sv: { home: 'Hem', news: 'Nyheter' },
  da: { home: 'Hjem', news: 'Nyheder' },
  no: { home: 'Hjem', news: 'Nyheter' },
  fi: { home: 'Etusivu', news: 'Uutiset' },
  de: { home: 'Startseite', news: 'Nachrichten' },
  fr: { home: 'Accueil', news: 'Actualités' },
  es: { home: 'Inicio', news: 'Noticias' },
  nl: { home: 'Home', news: 'Nieuws' },
  ar: { home: 'الرئيسية', news: 'أخبار' },
  he: { home: 'בית', news: 'חדשות' },
  ja: { home: 'ホーム', news: 'ニュース' },
  ko: { home: '홈', news: '뉴스' },
  zh: { home: '主页', news: '新闻' }
};

/**
 * Get breadcrumb name for a given language
 * @param {string} lang - Language code
 * @param {string} type - Breadcrumb type ('home' or 'news')
 * @returns {string} Translated breadcrumb name
 */
function getBreadcrumbName(lang, type) {
  return BREADCRUMB_TRANSLATIONS[lang]?.[type] || BREADCRUMB_TRANSLATIONS.en[type];
}

/**
 * Footer label translations for all 14 languages
 */
const FOOTER_LABELS = {
  en: { sourcesTitle: 'Sources and Data', dataSources: 'Data Sources', generatedBy: 'Generated by', generatedByValue: 'Automated News System using riksdag-regering-mcp', analysisTools: 'Analysis Tools', analysisToolsValue: 'AI-assisted journalism with human editorial oversight', backToNews: 'Back to News' },
  sv: { sourcesTitle: 'Källor och data', dataSources: 'Datakällor', generatedBy: 'Genererad av', generatedByValue: 'Automatiserat nyhetssystem med riksdag-regering-mcp', analysisTools: 'Analysverktyg', analysisToolsValue: 'AI-assisterad journalistik med mänsklig granskning', backToNews: 'Tillbaka till nyheter' },
  da: { sourcesTitle: 'Kilder og data', dataSources: 'Datakilder', generatedBy: 'Genereret af', generatedByValue: 'Automatiseret nyhedssystem med riksdag-regering-mcp', analysisTools: 'Analyseværktøjer', analysisToolsValue: 'AI-assisteret journalistik med redaktionel gennemgang', backToNews: 'Tilbage til nyheder' },
  no: { sourcesTitle: 'Kilder og data', dataSources: 'Datakilder', generatedBy: 'Generert av', generatedByValue: 'Automatisert nyhetssystem med riksdag-regering-mcp', analysisTools: 'Analyseverktøy', analysisToolsValue: 'AI-assistert journalistikk med redaksjonell gjennomgang', backToNews: 'Tilbake til nyheter' },
  fi: { sourcesTitle: 'Lähteet ja data', dataSources: 'Datalähteet', generatedBy: 'Luonut', generatedByValue: 'Automatisoitu uutisjärjestelmä riksdag-regering-mcp:llä', analysisTools: 'Analyysityökalut', analysisToolsValue: 'Tekoälyavusteinen journalismi toimituksellisella valvonnalla', backToNews: 'Takaisin uutisiin' },
  de: { sourcesTitle: 'Quellen und Daten', dataSources: 'Datenquellen', generatedBy: 'Erstellt von', generatedByValue: 'Automatisiertes Nachrichtensystem mit riksdag-regering-mcp', analysisTools: 'Analysewerkzeuge', analysisToolsValue: 'KI-gestützter Journalismus mit redaktioneller Aufsicht', backToNews: 'Zurück zu Nachrichten' },
  fr: { sourcesTitle: 'Sources et données', dataSources: 'Sources de données', generatedBy: 'Généré par', generatedByValue: 'Système automatisé avec riksdag-regering-mcp', analysisTools: 'Outils d\'analyse', analysisToolsValue: 'Journalisme assisté par IA avec supervision rédactionnelle', backToNews: 'Retour aux actualités' },
  es: { sourcesTitle: 'Fuentes y datos', dataSources: 'Fuentes de datos', generatedBy: 'Generado por', generatedByValue: 'Sistema automatizado con riksdag-regering-mcp', analysisTools: 'Herramientas de análisis', analysisToolsValue: 'Periodismo asistido por IA con supervisión editorial', backToNews: 'Volver a noticias' },
  nl: { sourcesTitle: 'Bronnen en data', dataSources: 'Databronnen', generatedBy: 'Gegenereerd door', generatedByValue: 'Geautomatiseerd nieuwssysteem met riksdag-regering-mcp', analysisTools: 'Analysetools', analysisToolsValue: 'AI-ondersteunde journalistiek met redactioneel toezicht', backToNews: 'Terug naar nieuws' },
  ar: { sourcesTitle: 'المصادر والبيانات', dataSources: 'مصادر البيانات', generatedBy: 'تم إنشاؤه بواسطة', generatedByValue: 'نظام أخبار آلي مع riksdag-regering-mcp', analysisTools: 'أدوات التحليل', analysisToolsValue: 'صحافة بمساعدة الذكاء الاصطناعي مع إشراف تحريري', backToNews: 'العودة إلى الأخبار' },
  he: { sourcesTitle: 'מקורות ונתונים', dataSources: 'מקורות נתונים', generatedBy: 'נוצר על ידי', generatedByValue: 'מערכת חדשות אוטומטית עם riksdag-regering-mcp', analysisTools: 'כלי ניתוח', analysisToolsValue: 'עיתונות בסיוע AI עם פיקוח עריכתי', backToNews: 'חזרה לחדשות' },
  ja: { sourcesTitle: 'ソースとデータ', dataSources: 'データソース', generatedBy: '生成者', generatedByValue: 'riksdag-regering-mcpによる自動ニュースシステム', analysisTools: '分析ツール', analysisToolsValue: 'AI支援ジャーナリズム(編集監督付き)', backToNews: 'ニュースに戻る' },
  ko: { sourcesTitle: '출처 및 데이터', dataSources: '데이터 소스', generatedBy: '생성자', generatedByValue: 'riksdag-regering-mcp 자동 뉴스 시스템', analysisTools: '분석 도구', analysisToolsValue: '편집 감독이 있는 AI 지원 저널리즘', backToNews: '뉴스로 돌아가기' },
  zh: { sourcesTitle: '来源和数据', dataSources: '数据来源', generatedBy: '生成者', generatedByValue: '使用riksdag-regering-mcp的自动新闻系统', analysisTools: '分析工具', analysisToolsValue: '人工编辑监督下的AI辅助新闻', backToNews: '返回新闻' }
};

function getFooterLabel(lang, key) {
  return FOOTER_LABELS[lang]?.[key] || FOOTER_LABELS.en[key];
}

function getNewsIndexFilename(lang) {
  if (lang === 'en') return 'index.html';
  return `index_${lang}.html`;
}

/**
 * Sanitize article body content for JSON-LD structured data
 * Removes newlines and normalizes whitespace to prevent invalid JSON
 * @param {string} htmlContent - Article HTML content
 * @returns {string} Sanitized content suitable for JSON-LD
 */
function sanitizeArticleBody(htmlContent) {
  return htmlContent
    .substring(0, 500)
    .replace(/\n/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

/**
 * Generate complete article HTML
 * 
 * @param {Object} data - Article data
 * @param {string} data.slug - Article slug (e.g., "2026-02-12-week-ahead")
 * @param {string} data.title - Article title
 * @param {string} data.subtitle - Article subtitle/lede
 * @param {string} data.date - Publication date (ISO format)
 * @param {string} data.type - Article type (prospective, retrospective, analysis, breaking)
 * @param {string} data.readTime - Estimated read time (e.g., "6 min read")
 * @param {string} data.lang - Language code (en, sv)
 * @param {string} data.langFull - Full language name (English, Svenska)
 * @param {string} data.locale - Locale code (en_US, sv_SE)
 * @param {string} data.content - Main article HTML content
 * @param {Array} data.events - Calendar events (for Week Ahead articles)
 * @param {Array} data.watchPoints - Key points to watch
 * @param {Array} data.sources - Data sources/tools used
 * @param {Array} data.keywords - SEO keywords
 * @param {Array} data.topics - Article topics for categorization
 * @param {Array} data.tags - Article tags for display
 * @returns {string} Complete HTML article
 */
/**
 * Site tagline translations for all 14 languages
 */
const SITE_TAGLINE = {
  en: 'Latest news and analysis from Sweden\'s Riksdag. The Economist-style political journalism covering parliament, government, and agencies with systematic transparency.',
  sv: 'Senaste nyheter och analyser från Sveriges riksdag. Politisk journalistik i The Economist-stil som bevakar riksdagen, regeringen och myndigheter med systematisk transparens.',
  da: 'Seneste nyheder og analyser fra Sveriges Riksdag. Politisk journalistik i The Economist-stil, der dækker parlament, regering og myndigheder med systematisk gennemsigtighed.',
  no: 'Siste nyheter og analyser fra Sveriges riksdag. Politisk journalistikk i The Economist-stil som dekker parlament, regjering og myndigheter med systematisk åpenhet.',
  fi: 'Uusimmat uutiset ja analyysit Ruotsin valtiopäiviltä. The Economist -tyylinen poliittinen journalismi, joka kattaa eduskunnan, hallituksen ja viranomaiset järjestelmällisellä läpinäkyvyydellä.',
  de: 'Aktuelle Nachrichten und Analysen aus dem schwedischen Riksdag. Politischer Journalismus im Economist-Stil über Parlament, Regierung und Behörden mit systematischer Transparenz.',
  fr: 'Dernières nouvelles et analyses du Riksdag suédois. Journalisme politique de style The Economist couvrant le parlement, le gouvernement et les agences avec une transparence systématique.',
  es: 'Últimas noticias y análisis del Riksdag sueco. Periodismo político al estilo The Economist que cubre el parlamento, el gobierno y las agencias con transparencia sistemática.',
  nl: 'Laatste nieuws en analyses van de Zweedse Riksdag. Politieke journalistiek in Economist-stijl over parlement, regering en instanties met systematische transparantie.',
  ar: 'أحدث الأخبار والتحليلات من البرلمان السويدي. صحافة سياسية بأسلوب ذا إيكونوميست تغطي البرلمان والحكومة والوكالات بشفافية منهجية.',
  he: 'חדשות ניתוחים אחרונים מהריקסדאג השוודי. עיתונות פוליטית בסגנון האקונומיסט המכסה פרלמנט, ממשלה וסוכנויות עם שקיפות שיטתית.',
  ja: 'スウェーデン議会リクスダーグの最新ニュースと分析。議会、政府、機関を体系的な透明性で報道するエコノミスト・スタイルの政治ジャーナリズム。',
  ko: '스웨덴 의회 릭스다그의 최신 뉴스와 분석. 체계적인 투명성으로 의회, 정부, 기관을 다루는 이코노미스트 스타일의 정치 저널리즘.',
  zh: '来自瑞典议会的最新新闻和分析。以经济学人风格的政治新闻,以系统性透明度报道议会、政府和机构。'
};

/**
 * OG locale map for all 14 languages
 */
const OG_LOCALE_MAP = {
  en: 'en_US', sv: 'sv_SE', da: 'da_DK', no: 'nb_NO', fi: 'fi_FI',
  de: 'de_DE', fr: 'fr_FR', es: 'es_ES', nl: 'nl_NL', ar: 'ar_SA',
  he: 'he_IL', ja: 'ja_JP', ko: 'ko_KR', zh: 'zh_CN'
};

export function generateArticleHTML(data) {
  const {
    slug,
    title,
    subtitle,
    date,
    type,
    readTime = '5 min read',
    lang = 'en',
    locale,
    content,
    events = [],
    watchPoints = [],
    sources = [],
    keywords = [],
    tags = []
  } = data;
  
  // Use proper OG locale for the language
  const ogLocale = locale || OG_LOCALE_MAP[lang] || 'en_US';
  
  const dateObj = new Date(date);
  const formattedDate = formatDate(dateObj, lang);
  const isoDate = dateObj.toISOString().split('T')[0];
  
  // Determine type label with fallback to English for unsupported languages
  const typeLabels = {
    en: { prospective: 'The Week Ahead', retrospective: 'Weekly Review', analysis: 'Analysis', breaking: 'Breaking News' },
    sv: { prospective: 'Veckan som kommer', retrospective: 'Veckans återblick', analysis: 'Analys', breaking: 'Senaste nytt' },
    da: { prospective: 'Ugen fremover', retrospective: 'Ugens tilbageblik', analysis: 'Analyse', breaking: 'Seneste nyt' },
    no: { prospective: 'Uka som kommer', retrospective: 'Ukens tilbakeblikk', analysis: 'Analyse', breaking: 'Siste nytt' },
    fi: { prospective: 'Tuleva viikko', retrospective: 'Viikon katsaus', analysis: 'Analyysi', breaking: 'Viimeisimmät' },
    de: { prospective: 'Woche voraus', retrospective: 'Wochenrückblick', analysis: 'Analyse', breaking: 'Eilmeldung' },
    fr: { prospective: 'Semaine à venir', retrospective: 'Revue de la semaine', analysis: 'Analyse', breaking: 'Dernière heure' },
    es: { prospective: 'Semana próxima', retrospective: 'Revisión semanal', analysis: 'Análisis', breaking: 'Última hora' },
    nl: { prospective: 'Week vooruit', retrospective: 'Weekoverzicht', analysis: 'Analyse', breaking: 'Laatste nieuws' },
    ar: { prospective: 'الأسبوع القادم', retrospective: 'مراجعة الأسبوع', analysis: 'تحليل', breaking: 'أخبار عاجلة' },
    he: { prospective: 'השבוע הקרוב', retrospective: 'סיכום שבועי', analysis: 'ניתוח', breaking: 'חדשות אחרונות' },
    ja: { prospective: '来週の展望', retrospective: '週間レビュー', analysis: '分析', breaking: '速報' },
    ko: { prospective: '다음 주 전망', retrospective: '주간 리뷰', analysis: '분석', breaking: '속보' },
    zh: { prospective: '下周展望', retrospective: '每周回顾', analysis: '分析', breaking: '突发新闻' }
  };
  
  // Fall back to English labels if language not supported
  const typeLabel = typeLabels[lang]?.[type] || typeLabels.en[type] || 'News';
  
  // Generate hreflang tags for all available language variants
  const ALL_LANG_CODES = ['en', 'sv', 'da', 'no', 'fi', 'de', 'fr', 'es', 'nl', 'ar', 'he', 'ja', 'ko', 'zh'];
  const isRTL = lang === 'ar' || lang === 'he';
  const dirAttr = isRTL ? ' dir="rtl"' : '';
  const baseSlug = slug.replace(`-${lang}.html`, '');
  const altLang = lang === 'en' ? 'sv' : 'en';
  const altSlug = slug.replace(`-${lang}.html`, `-${altLang}.html`);
  
  return `<!DOCTYPE html>
<html lang="${lang}"${dirAttr}>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${title}</title>
  <meta name="description" content="${escapeHtml(subtitle).substring(0, 160)}">
  <meta name="keywords" content="${keywords.join(', ')}">
  <meta name="author" content="James Pether Sörling, CISSP, CISM">
  <link rel="canonical" href="https://riksdagsmonitor.com/news/${slug}">
  
  <!-- Open Graph / Social Media -->
  <meta property="og:title" content="${escapeHtml(title)}">
  <meta property="og:description" content="${escapeHtml(subtitle).substring(0, 200)}">
  <meta property="og:type" content="article">
  <meta property="og:url" content="https://riksdagsmonitor.com/news/${slug}">
  <meta property="og:image" content="https://hack23.com/cia-icon-140.webp">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:image:alt" content="Riksdagsmonitor - Swedish Parliament Intelligence">
  <meta property="og:locale" content="${ogLocale}">
  <meta property="og:site_name" content="Riksdagsmonitor - Swedish Parliament Intelligence">
  <meta property="article:published_time" content="${dateObj.toISOString()}">
  <meta property="article:modified_time" content="${dateObj.toISOString()}">
  <meta property="article:author" content="James Pether Sörling">
  <meta property="article:section" content="${typeLabel}">
${tags.map(tag => `  <meta property="article:tag" content="${escapeHtml(tag)}">`).join('\n')}
  
  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="${escapeHtml(title)}">
  <meta name="twitter:description" content="${escapeHtml(subtitle).substring(0, 200)}">
  <meta name="twitter:image" content="https://hack23.com/cia-icon-140.webp">
  <meta name="twitter:image:alt" content="Riksdagsmonitor - Swedish Parliament Intelligence">
  <meta name="twitter:site" content="@riksdagsmonitor">
  <meta name="twitter:creator" content="@jamessorling">
  <meta name="twitter:label1" content="Reading time">
  <meta name="twitter:data1" content="${readTime}">
  <meta name="twitter:label2" content="Article type">
  <meta name="twitter:data2" content="${typeLabel}">
  
  <!-- Hreflang for language alternatives -->
${ALL_LANG_CODES.map(l => `  <link rel="alternate" hreflang="${l}" href="https://riksdagsmonitor.com/news/${baseSlug}-${l}.html">`).join('\n')}
  <link rel="alternate" hreflang="x-default" href="https://riksdagsmonitor.com/news/${baseSlug}-en.html">
  
  <!-- Google 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&family=Share+Tech+Mono&display=swap" rel="stylesheet">
  
  <!-- Main stylesheet - contains all article styles -->
  <link rel="stylesheet" href="../styles.css">
  
  <!-- Schema.org NewsArticle structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "NewsArticle",
    "headline": "${escapeHtml(title)}",
    "alternativeHeadline": "${escapeHtml(subtitle).substring(0, 100)}",
    "description": "${escapeHtml(subtitle).substring(0, 200)}",
    "datePublished": "${dateObj.toISOString()}",
    "dateModified": "${dateObj.toISOString()}",
    "author": {
      "@type": "Person",
      "name": "James Pether Sörling",
      "jobTitle": "Political Intelligence Analyst",
      "affiliation": {
        "@type": "Organization",
        "name": "Hack23 AB"
      },
      "url": "https://riksdagsmonitor.com"
    },
    "publisher": {
      "@type": "Organization",
      "name": "Riksdagsmonitor",
      "url": "https://riksdagsmonitor.com",
      "logo": {
        "@type": "ImageObject",
        "url": "https://hack23.com/cia-icon-140.webp",
        "width": 600,
        "height": 60
      }
    },
    "image": {
      "@type": "ImageObject",
      "url": "https://hack23.com/cia-icon-140.webp",
      "width": 1200,
      "height": 630
    },
    "articleSection": "${typeLabel}",
    "articleBody": "${sanitizeArticleBody(escapeHtml(content))}...",
    "wordCount": ${Math.ceil(content.length / 5)},
    "inLanguage": "${lang}",
    "keywords": "${keywords.join(', ')}",
    "about": {
      "@type": "Thing",
      "name": "Swedish Parliament",
      "sameAs": "https://www.wikidata.org/wiki/Q1968818"
    },
    "isAccessibleForFree": true,
    "isPartOf": {
      "@type": "WebSite",
      "name": "Riksdagsmonitor",
      "url": "https://riksdagsmonitor.com"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://riksdagsmonitor.com/news/${slug}"
    }${tags.length > 0 ? `,
    "mentions": [${tags.map(tag => `
      {
        "@type": "Thing",
        "name": "${escapeHtml(tag)}"
      }`).join(',')}
    ]` : ''}
  }
  </script>
  
  <!-- BreadcrumbList structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "${getBreadcrumbName(lang, 'home')}",
        "item": "https://riksdagsmonitor.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "${getBreadcrumbName(lang, 'news')}",
        "item": "https://riksdagsmonitor.com/news/index.html"
      },
      {
        "@type": "ListItem",
        "position": 3,
        "name": "${escapeHtml(title).substring(0, 50)}",
        "item": "https://riksdagsmonitor.com/news/${slug}"
      }
    ]
  }
  </script>
  
  <!-- Organization structured data -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Organization",
    "name": "Riksdagsmonitor",
    "url": "https://riksdagsmonitor.com",
    "logo": "https://hack23.com/cia-icon-140.webp",
    "description": "Swedish Parliament Intelligence Platform - Monitor political activity with systematic transparency",
    "foundingDate": "2020",
    "founder": {
      "@type": "Person",
      "name": "James Pether Sörling"
    },
    "sameAs": [
      "https://github.com/Hack23/riksdagsmonitor"
    ],
    "contactPoint": {
      "@type": "ContactPoint",
      "contactType": "Technical Support",
      "url": "https://github.com/Hack23/riksdagsmonitor/issues"
    }
  }
  </script>
  
</head>
<body>
<article class="news-article">
  <header class="article-header">
    <div class="site-tagline">${SITE_TAGLINE[lang] || SITE_TAGLINE.en}</div>
    <h1>${title}</h1>
    <div class="article-meta">
      <time datetime="${isoDate}">${formattedDate}</time>
      <span class="separator">•</span>
      <span>${typeLabel}</span>
      <span class="separator">•</span>
      <span>${readTime}</span>
    </div>
  </header>

${events.length > 0 ? generateEventCalendar(events, lang) : ''}

  <div class="article-content">
    <p class="lede">
      ${subtitle}
    </p>

${content}

${watchPoints.length > 0 ? generateWatchSection(watchPoints, lang) : ''}
  </div>

  <footer class="article-footer">
    <div class="article-sources">
      <h3>${getFooterLabel(lang, 'sourcesTitle')}</h3>
      <p><strong>${getFooterLabel(lang, 'dataSources')}:</strong> ${sources.join(', ')}</p>
      <p><strong>${getFooterLabel(lang, 'generatedBy')}:</strong> ${getFooterLabel(lang, 'generatedByValue')}</p>
      <p><strong>${getFooterLabel(lang, 'analysisTools')}:</strong> ${getFooterLabel(lang, 'analysisToolsValue')}</p>
    </div>
    
    <div class="article-nav">
      <a href="${getNewsIndexFilename(lang)}" class="back-to-news">
        \u2190 ${getFooterLabel(lang, 'backToNews')}
      </a>
    </div>
  </footer>
</article>

<script src="../scripts/back-to-top.js"></script>
</body>
</html>`;
}

/**
 * Generate event calendar section
 */
const EVENT_CALENDAR_TITLES = {
  en: 'Event Calendar',
  sv: 'Veckans händelser',
  da: 'Ugens begivenheder',
  no: 'Ukens hendelser',
  fi: 'Viikon tapahtumat',
  de: 'Veranstaltungskalender',
  fr: 'Calendrier des événements',
  es: 'Calendario de eventos',
  nl: 'Evenementenkalender',
  ar: 'تقويم الأحداث',
  he: 'לוח אירועים',
  ja: 'イベントカレンダー',
  ko: '일정 캘린더',
  zh: '活动日历'
};

function generateEventCalendar(events, lang = 'en') {
  const title = EVENT_CALENDAR_TITLES[lang] || EVENT_CALENDAR_TITLES.en;
  const weekLabel = events.length > 0 && events[0].date ? 
    `${formatDateRange(events, lang)}` : '';
  
  return `
  <section class="event-calendar" aria-label="${title}">
    <h2>${title}${weekLabel ? `: ${weekLabel}` : ''}</h2>
    <div class="calendar-grid">
${events.map(event => `      <div class="calendar-day${event.isToday ? ' today' : ''}" aria-label="${event.dayLabel}">
        <div class="day-header">${event.dayName}</div>
        <span class="day-date">${event.dayNumber}</span>
        <ul class="event-list">
${event.items.map(item => `          <li class="event-item">
            <span class="event-time">${item.time}</span>
            <span class="event-title">${item.title}</span>
          </li>`).join('\n')}
        </ul>
      </div>`).join('\n')}
    </div>
  </section>`;
}

/**
 * Generate "Watch Section" with key points
 */
const WATCH_SECTION_TITLES = {
  en: 'What to Watch This Week',
  sv: 'Vad man ska följa denna vecka',
  da: 'Hvad man skal følge denne uge',
  no: 'Hva man bør følge denne uken',
  fi: 'Mitä seurata tällä viikolla',
  de: 'Worauf diese Woche zu achten ist',
  fr: 'À suivre cette semaine',
  es: 'Qué observar esta semana',
  nl: 'Wat te volgen deze week',
  ar: 'ما يجب متابعته هذا الأسبوع',
  he: 'מה לעקוב אחריו השבוע',
  ja: '今週の注目ポイント',
  ko: '이번 주 주목할 사항',
  zh: '本周关注要点'
};

function generateWatchSection(watchPoints, lang = 'en') {
  const title = WATCH_SECTION_TITLES[lang] || WATCH_SECTION_TITLES.en;
  
  return `
    <section class="watch-section">
      <h2>${title}</h2>
      <ul class="watch-list">
${watchPoints.map(point => `        <li>
          <strong>${point.title}:</strong> ${point.description}
        </li>`).join('\n')}
      </ul>
    </section>`;
}



/**
 * Locale map for all 14 supported languages
 */
const LOCALE_MAP = {
  en: 'en-GB', sv: 'sv-SE', da: 'da-DK', no: 'no-NO', fi: 'fi-FI',
  de: 'de-DE', fr: 'fr-FR', es: 'es-ES', nl: 'nl-NL', ar: 'ar-SA',
  he: 'he-IL', ja: 'ja-JP', ko: 'ko-KR', zh: 'zh-CN'
};

/**
 * Helper: Format date for display using locale-appropriate formatting
 */
function formatDate(date, lang = 'en') {
  const locale = LOCALE_MAP[lang] || 'en-GB';
  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  try {
    return date.toLocaleDateString(locale, options);
  } catch {
    return date.toLocaleDateString('en-GB', options);
  }
}

/**
 * Helper: Format date range for calendar title
 */
function formatDateRange(events, lang = 'en') {
  if (events.length === 0) return '';
  
  const firstEvent = events[0];
  const lastEvent = events[events.length - 1];
  
  if (!firstEvent.date || !lastEvent.date) return '';
  
  const locale = LOCALE_MAP[lang] || 'en-GB';
  const longOptions = { month: 'long', day: 'numeric', year: 'numeric' };
  const shortOptions = { month: 'long', day: 'numeric' };
  
  try {
    const startDate = new Date(firstEvent.date).toLocaleDateString(locale, longOptions);
    const endDate = new Date(lastEvent.date).toLocaleDateString(locale, shortOptions);
    return `${startDate} – ${endDate}`;
  } catch {
    const startDate = new Date(firstEvent.date).toLocaleDateString('en-GB', longOptions);
    const endDate = new Date(lastEvent.date).toLocaleDateString('en-GB', shortOptions);
    return `${startDate} – ${endDate}`;
  }
}

export default {
  generateArticleHTML,
  generateEventCalendar,
  generateWatchSection
};