<?php



/**
 * CodePressCMS - Lightweight file-based content management system
 * 
 * Features:
 * - Markdown, PHP, and HTML content support
 * - Dynamic navigation with dropdown menus
 * - Search functionality
 * - Breadcrumb navigation
 * - Auto-linking between pages
 * - Bootstrap 5 styling
 * - File-based organization
 * - SEO friendly URLs
 * - Responsive design
 * 
 * @author Edwin Noorlander
 * @version 1.0.0
 * @license MIT
 */
class CodePressCMS {
    public $config;
    public $currentLanguage;
    public $searchResults = [];
    private $menu = [];
    private $translations = [];
    private $pluginManager;
    
    /**
     * Constructor - Initialize the CMS with configuration
     * 
     * @param array $config Configuration array containing site settings
     */
    public function __construct($config) {
        $this->config = $config;
        
        // Load version information
        $versionFile = __DIR__ . '/../../../version.php';
        if (file_exists($versionFile)) {
            $this->config['version_info'] = include $versionFile;
        }
        
        $this->currentLanguage = $this->getCurrentLanguage();
        $this->translations = $this->loadTranslations($this->currentLanguage);
        
        // Initialize plugin manager (files already loaded in cms/core/index.php)
        $this->pluginManager = new PluginManager(__DIR__ . '/../../../plugins');
        $api = new CMSAPI($this);
        $this->pluginManager->setAPI($api);
        
        $this->buildMenu();
        
        if (isset($_GET['search'])) {
            $this->performSearch($_GET['search']);
        }
    }
    
    /**
     * Build a clean URL for a page
     * 
     * @param string $page Page path (e.g., 'blog/leren/artikel')
     * @param string|null $lang Language code
     * @param array $params Additional query parameters
     * @return string Clean URL
     */
    public function buildUrl($page = 'index', $lang = null, $params = []) {
        $lang = $lang ?: $this->currentLanguage;
        $url = '/' . $lang;
        if ($page && $page !== 'index') {
            $url .= '/' . $page;
        }
        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }
        return $url;
    }

    /**
     * Build a clean admin URL
     * 
     * @param string $route Admin route
     * @param array $params Additional query parameters
     * @return string Clean admin URL
     */
    public static function buildAdminUrl($route = '', $params = []) {
        $url = $route ? '/admin/' . $route : '/admin';
        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }
        return $url;
    }

    /**
     * Build URL string for template use (non-static, with current lang context)
     */
    public function url($page = 'index', $extraParams = []) {
        return $this->buildUrl($page, $this->currentLanguage, $extraParams);
    }

    /**
     * Get current language from request or config
     * 
     * @return string Current language code
     */
    private function getCurrentLanguage() {
        $lang = $_GET['lang'] ?? $this->config['language']['default'] ?? 'nl';
        // Validate language parameter to prevent XSS
        $allowedLanguages = ['nl', 'en'];
        return in_array($lang, $allowedLanguages) ? $lang : ($this->config['language']['default'] ?? 'nl');
    }
    
    /**
     * Get all available languages from lang directory
     * 
     * @return array Available languages with their codes and names
     */
    public function getAvailableLanguages() {
        $langDir = __DIR__ . '/../../lang/';
        $languages = [];
        
        if (!is_dir($langDir)) {
            return $languages;
        }
        
        $files = scandir($langDir);
        foreach ($files as $file) {
            if (preg_match('/^([a-z]{2})\.php$/', $file, $matches)) {
                $langCode = $matches[1];
                $langFile = $langDir . $file;
                
                if (file_exists($langFile)) {
                    $translations = include $langFile;
                    $languages[$langCode] = [
                        'code' => $langCode,
                        'name' => $translations['site_title'] ?? strtoupper($langCode),
                        'native_name' => $this->getNativeLanguageName($langCode)
                    ];
                }
            }
        }
        
        return $languages;
    }
    
    /**
     * Load translations for specified language
     * 
     * @param string $lang Language code
     * @return array Translations array
     */
    private function loadTranslations($lang) {
        $langFile = __DIR__ . '/../../lang/' . $lang . '.php';
        if (file_exists($langFile)) {
            $translations = include $langFile;
            return $translations;
        }
        // Fallback to default language
        $defaultLang = $this->config['language']['default'] ?? 'nl';
        $defaultLangFile = __DIR__ . '/../../lang/' . $defaultLang . '.php';
        if (file_exists($defaultLangFile)) {
            return include $defaultLangFile;
        }
        // Return empty array if no translation found
        return [];
    }
    
    /**
     * Get native language name for language code
     * 
     * @param string $langCode Language code
     * @return string Native language name
     */
    private function getNativeLanguageName($langCode) {
        $names = [
            'nl' => 'Nederlands',
            'en' => 'English',
            'fr' => 'Français',
            'de' => 'Deutsch',
            'es' => 'Español',
            'it' => 'Italiano',
            'pt' => 'Português',
            'ru' => 'Русский',
            'zh' => '中文',
            'ja' => '日本語',
            'ar' => 'العربية'
        ];
        
        return $names[$langCode] ?? strtoupper($langCode);
    }
    
    /**
     * Get translated text
     * 
     * @param string $key Translation key
     * @return string Translated text
     */
    public function t($key) {
        return $this->translations[$key] ?? $key;
    }
    
    /**
     * Build menu structure from content directory
     * 
     * @return void
     */
    private function buildMenu() {
        $this->menu = $this->scanDirectory($this->config['content_dir'], '');
    }
    
    /**
     * Recursively scan directory for content files and folders
     * 
     * @param string $dir Directory path to scan
     * @param string $prefix Relative path prefix
     * @return array Array of menu items
     */
    private function scanDirectory($dir, $prefix) {
        if (!is_dir($dir)) return [];
        
        $items = scandir($dir);
        sort($items);
        $result = [];
        
        foreach ($items as $item) {
            if ($item[0] === '.' || $item[0] === '-') continue;
            
            // Skip assets directory (old name, kept for safety)
            if ($item === 'assets' && is_dir($dir . '/' . $item)) continue;
            
            // Skip language-specific content that doesn't match current language
            $availableLangs = array_keys($this->getAvailableLanguages());
            $langPattern = '/^(' . implode('|', $availableLangs) . ')\./';
            if (preg_match($langPattern, $item, $langMatch)) {
                if ($langMatch[1] !== $this->currentLanguage) {
                    continue;
                }
            }
            
            $path = $dir . '/' . $item;
            $relativePath = $prefix ? $prefix . '/' . $item : $item;
            
            if (is_dir($path)) {
                $result[] = [
                    'type' => 'folder',
                    'title' => $this->formatDisplayName($item),
                    'path' => $relativePath,
                    'children' => $this->scanDirectory($path, $relativePath)
                ];
            } elseif (preg_match('/\.(md|php|html)$/', $item)) {
                // Always use filename for navigation (not H1 titles from content)
                $filename = pathinfo($item, PATHINFO_FILENAME);
                $title = $this->formatDisplayName($filename);
                $pathWithoutExt = preg_replace('/\.[^.]+$/', '', $relativePath);
                $result[] = [
                    'type' => 'file',
                    'title' => $title,
                    'path' => $pathWithoutExt,
                    'url' => $this->buildUrl($pathWithoutExt)
                ];
            }
        }
        
        return $result;
    }
    
    /**
     * Perform search across all content files
     * 
     * @param string $query Search query string
     * @return void
     */
    private function performSearch($query) {
        $this->searchResults = [];
        $this->searchInDirectory($this->config['content_dir'], '', $query);
    }
    
    /**
     * Recursively search for query in directory files
     * 
     * @param string $dir Directory to search in
     * @param string $prefix Relative path prefix
     * @param string $query Search query
     * @return void
     */
    private function searchInDirectory($dir, $prefix, $query) {
        if (!is_dir($dir)) return;
        
        $items = scandir($dir);
        
        foreach ($items as $item) {
            if ($item[0] === '.') continue;
            
            $path = $dir . '/' . $item;
            $relativePath = $prefix ? $prefix . '/' . $item : $item;
            
            if (is_dir($path)) {
                $this->searchInDirectory($path, $relativePath, $query);
            } elseif (preg_match('/\.(md|php|html)$/', $item)) {
                $content = file_get_contents($path);
                if (stripos($content, $query) !== false || stripos($item, $query) !== false) {
                    $title = ucfirst(pathinfo($item, PATHINFO_FILENAME));
                    $this->searchResults[] = [
                        'title' => $title,
                        'path' => $relativePath,
                        'url' => $this->buildUrl($relativePath),
                        'snippet' => $this->createSnippet($content, $query)
                    ];
                }
            }
        }
    }
    
    /**
     * Create search snippet with highlighted query
     * 
     * @param string $content Full content to create snippet from
     * @param string $query Search query to highlight
     * @return string Formatted snippet
     */
    private function createSnippet($content, $query) {
        $content = strip_tags($content);
        $pos = stripos($content, $query);
        if ($pos === false) return substr($content, 0, 100) . '...';
        
        $start = max(0, $pos - 50);
        $snippet = substr($content, $start, 150);
        return '...' . $snippet . '...';
    }
    
    /**
     * Get current page content based on request
     * 
     * @return array Page data with title and content
     */
    public function getPage() {
        if (isset($_GET['search'])) {
            return $this->getSearchResults();
        }
        
        // Check if guide is requested (either via ?guide or /guide clean URL)
        $pageCheck = $_GET['page'] ?? '';
        if (isset($_GET['guide']) || $pageCheck === 'guide') {
            return $this->getGuidePage();
        }
        
        // Check if content directory is empty
        if ($this->isContentDirEmpty()) {
            return $this->getGuidePage();
        }
        
        $page = $_GET['page'] ?? $this->config['default_page'];
        // Limit length
        $page = substr($page, 0, 255);
        // Only remove file extension at the end, not all dots
        $pageWithoutExt = preg_replace('/\.(md|php|html)$/', '', $page);
        
        $filePath = $this->config['content_dir'] . '/' . $pageWithoutExt;
        
        // Prevent path traversal using realpath validation
        $realContentDir = realpath($this->config['content_dir']);
        $realFilePath = realpath($filePath);
        if ($realFilePath && $realContentDir && strpos($realFilePath, $realContentDir) !== 0) {
            return $this->getError404();
        }
        
        // Check if directory exists FIRST (directories take precedence over files)
        if (is_dir($filePath)) {
            return $this->getDirectoryListing($pageWithoutExt, $filePath);
        }
        
        $actualFilePath = null;
        
        // Check for exact file matches if no directory found
        if (file_exists($filePath . '.md')) {
            $actualFilePath = $filePath . '.md';

            $result = $this->parseMarkdown(file_get_contents($actualFilePath), $actualFilePath);
        } elseif (file_exists($filePath . '.php')) {
            $actualFilePath = $filePath . '.php';
            $result = $this->parsePHP($actualFilePath);
        } elseif (file_exists($filePath . '.html')) {
            $actualFilePath = $filePath . '.html';
            $result = $this->parseHTML(file_get_contents($actualFilePath));
        } elseif (file_exists($filePath)) {
            $actualFilePath = $filePath;
            $extension = pathinfo($filePath, PATHINFO_EXTENSION);
            if ($extension === 'md') {
                $result = $this->parseMarkdown(file_get_contents($actualFilePath), $actualFilePath);
            } elseif ($extension === 'php') {
                $result = $this->parsePHP($actualFilePath);
            } elseif ($extension === 'html') {
                $result = $this->parseHTML(file_get_contents($actualFilePath));
            }
        }
        
        // If no exact match found, check for language-specific versions
        if (!isset($result)) {
            $result = null; // Reset result before language-specific search
            $langPrefix = $this->currentLanguage;
            
            if (file_exists($this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.md')) {
                $actualFilePath = $this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.md';
                $result = $this->parseMarkdown(file_get_contents($actualFilePath), $actualFilePath);
            } elseif (file_exists($this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.php')) {
                $actualFilePath = $this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.php';
                $result = $this->parsePHP($actualFilePath);
            } elseif (file_exists($this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.html')) {
                $actualFilePath = $this->config['content_dir'] . '/' . $langPrefix . '.' . $pageWithoutExt . '.html';
                $result = $this->parseHTML(file_get_contents($actualFilePath));
            }
        }
        
        // If no file found, check if it's a directory (directories take precedence)
        if (!isset($result) && is_dir($filePath)) {
            return $this->getDirectoryListing($pageWithoutExt, $filePath);
        }
        
        if (isset($result) && $actualFilePath) {
            $result['file_info'] = $this->getFileInfo($actualFilePath);
            return $result;
        }
        
        return $this->getError404();
    }
    
    /**
     * Get file information including creation and modification dates
     * 
     * @param string $filePath Path to the file
     * @return array|null File information or null if file doesn't exist
     */
    private function getFileInfo($filePath) {
        if (!file_exists($filePath)) {
            return null;
        }
        
        $stats = stat($filePath);
        $created = date('d-m-Y H:i', $stats['ctime']);
        $modified = date('d-m-Y H:i', $stats['mtime']);
        
        return [
            'created' => $created,
            'modified' => $modified,
            'size' => $this->formatFileSize($stats['size'])
        ];
    }
    
    /**
     * Format file size in human readable format
     * 
     * @param int $bytes File size in bytes
     * @return string Formatted file size
     */
    private function formatFileSize($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        
        $bytes /= pow(1024, $pow);
        
        return round($bytes, 2) . ' ' . $units[$pow];
    }
    
    /**
     * Get search results page content
     * 
     * @return array Search results page data
     */
    private function getSearchResults() {
        $query = $_GET['search'];
        $content = '<h2>' . $this->t('search') . ' ' . $this->t('results_found') . ': "' . htmlspecialchars($query) . '"</h2>';
        
        if (empty($this->searchResults)) {
            $content .= '<p>' . $this->t('no_results') . '.</p>';
        } else {
            $content .= '<p>' . count($this->searchResults) . ' ' . $this->t('results_found') . ':</p>';
            foreach ($this->searchResults as $result) {
                $content .= '<div class="card mb-3">';
                $content .= '<div class="card-body">';
                $content .= '<h5 class="card-title"><a href="' . htmlspecialchars($result['url']) . '">' . htmlspecialchars($result['title']) . '</a></h5>';
                $content .= '<p class="card-text text-muted">' . htmlspecialchars($result['path']) . '</p>';
                $content .= '<p class="card-text">' . htmlspecialchars($result['snippet']) . '</p>';
                $content .= '</div></div>';
            }
        }
        
        return [
            'title' => 'Search Results',
            'content' => $content
        ];
    }
    
    /**
     * Parse metadata from content
     * 
     * @param string $content Raw content
     * @return array Parsed metadata and content without meta block
     */
    private function parseMetadata($content) {
        $metadata = [];
        $contentWithoutMeta = $content;
        
        // Check for YAML frontmatter (--- at start and end)
        if (preg_match('/^---\s*\n(.*?)\n---\s*\n(.*)$/s', $content, $matches)) {
            $metaContent = $matches[1];
            $contentWithoutMeta = $matches[2];
            
            // Parse YAML-like metadata
            $lines = explode("\n", $metaContent);
            foreach ($lines as $line) {
                if (strpos($line, ':') !== false) {
                    list($key, $value) = explode(':', $line, 2);
                    $key = trim($key);
                    $value = trim($value, ' "\'');
                    
                    // Handle boolean values
                    if ($value === 'true') $value = true;
                    elseif ($value === 'false') $value = false;
                    
                    $metadata[$key] = $value;
                }
            }
        }
        
        return [
            'metadata' => $metadata,
            'content' => $contentWithoutMeta
        ];
    }
    
    /**
     * Parse Markdown content to HTML using League CommonMark
     * 
     * @param string $content Raw Markdown content
     * @return array Parsed content with title and body
     */
    public function parseMarkdown($content, $actualFilePath = '') {
        // Parse metadata first
        $parsed = $this->parseMetadata($content);
        $metadata = $parsed['metadata'];
        $content = $parsed['content'];
        
        // Extract title from first H1 or metadata
        $title = $metadata['title'] ?? '';
        if (empty($title) && preg_match('/^#\s+(.+)$/m', $content, $matches)) {
            $title = trim($matches[1]);
        }
        
        // Configure CommonMark environment (autoloader already loaded in bootstrap)
        $config = [
            'html_input' => 'strip',
            'allow_unsafe_links' => false,
            'max_nesting_level' => 100,
        ];
        
        // Create environment with extensions
        $environment = new \League\CommonMark\Environment\Environment($config);
        $environment->addExtension(new \League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Autolink\AutolinkExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Strikethrough\StrikethroughExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Table\TableExtension());
        $environment->addExtension(new \League\CommonMark\Extension\TaskList\TaskListExtension());
        
        // Create converter
        $converter = new \League\CommonMark\MarkdownConverter($environment);
        
        // Convert to HTML
        $body = $converter->convert($content)->getContent();
        
        // Extract clean filename for title (without language prefix and extension)
        $filename = basename($actualFilePath);
        $cleanName = $this->formatDisplayName($filename);
        

        
        // Auto-link page titles to existing content pages (but not in H1 tags)
        $body = $this->autoLinkPageTitles($body, $cleanName);
        
        // Convert relative internal links to clean URLs
        $body = preg_replace('/href="\/blog\/([^"]+)"/', 'href="/' . $this->currentLanguage . '/blog/$1"', $body);
        $body = preg_replace('/href="\/([^"]+)"/', 'href="/' . $this->currentLanguage . '/$1"', $body);
        
        return [
            'title' => $title ?: $cleanName ?: 'Untitled',
            'content' => $body,
            'metadata' => $metadata,
            'layout' => $metadata['layout'] ?? 'sidebar-content'
        ];
    }
    
    /**
     * Auto-link page titles found in content
     * 
     * @param string $content Content to process for auto-linking
     * @param string $excludeTitle Title to exclude from auto-linking (current page title)
     * @return string Content with auto-linked page titles
     */
    private function autoLinkPageTitles($content, $excludeTitle = '') {
        // Get all available pages with their titles
        $pages = $this->getAllPageTitles();
        
        foreach ($pages as $pagePath => $pageTitle) {
            // Create a pattern that matches the exact page title (case-insensitive)
            // Use word boundaries and avoid H1 tags to prevent linking inside headings
            $pattern = '/\b' . preg_quote($pageTitle, '/') . '\b(?!(?=<\/h1>))/i';
            
            // Replace with link, but avoid linking inside existing links, headings, or markdown
            $replacement = function($matches) use ($pageTitle, $pagePath, $excludeTitle) {
                $text = $matches[0];
                
                // Check if we're inside an existing link or markdown syntax, or if it's the current page title
                if (preg_match('/\[.*?\]\(.*?\)/', $text) || 
                    preg_match('/\[.*?\]:/', $text) ||
                    preg_match('/<a[^>]*>/', $text) ||
                    preg_match('/href=/', $text) ||
                    preg_match('/<h1>/', $text) ||
                    strtolower($text) === strtolower($excludeTitle)) {
                    return $text; // Don't link existing links, current page title, or H1 headings
                }
                
                return '<a href="' . $this->buildUrl($pagePath) . '" class="auto-link" title="' . $this->t('go_to') . ' ' . htmlspecialchars($pageTitle) . '">' . $text . '</a>';
            };
            
            $content = preg_replace_callback($pattern, $replacement, $content);
        }
        
        return $content;
    }
    
    /**
     * Get all page titles from content directory
     * 
     * @return array Associative array of page paths to titles
     */
    public function getAllPageTitles() {
        $pages = [];
        $this->scanForPageTitles($this->config['content_dir'], '', $pages);
        return $pages;
    }
    
    /**
     * Recursively scan for page titles in directory
     * 
     * @param string $dir Directory to scan
     * @param string $prefix Relative path prefix
     * @param array &$pages Reference to pages array to populate
     * @return void
     */
    private function scanForPageTitles($dir, $prefix, &$pages) {
        if (!is_dir($dir)) return;
        
        $items = scandir($dir);
        sort($items);
        
        foreach ($items as $item) {
            if ($item[0] === '.') continue;
            
            $path = $dir . '/' . $item;
            $relativePath = $prefix ? $prefix . '/' . $item : $item;
            
            if (is_dir($path)) {
                $this->scanForPageTitles($path, $relativePath, $pages);
            } elseif (preg_match('/\.(md|php|html)$/', $item)) {
                $title = $this->extractPageTitle($path);
                if ($title && !empty(trim($title))) {
                    $pagePath = preg_replace('/\.[^.]+$/', '', $relativePath);
                    $pages[$pagePath] = $title;
                } else {
                    // Fallback to clean filename if no title found in content
                    $filename = basename($path, pathinfo($path, PATHINFO_EXTENSION));
                    $cleanName = $this->formatDisplayName($filename);
                    $pagePath = preg_replace('/\.[^.]+$/', '', $relativePath);
                    $pages[$pagePath] = $cleanName;
                }
            }
        }
    }
    
    /**
     * Format display name from filename
     * 
     * @param string $filename Filename without extension
     * @return string Formatted display name
     */
    private function formatDisplayName($filename) {
        // Preserve leading dash before processing
        $hasLeadingDash = $filename[0] === '-';
        if ($hasLeadingDash) {
            $filename = substr($filename, 1);
        }
        
        // Remove language prefixes dynamically based on available languages
        $availableLangs = array_keys($this->getAvailableLanguages());
        $langPattern = '/^(' . implode('|', $availableLangs) . ')\.(.+)$/';
        if (preg_match($langPattern, $filename, $matches)) {
            $filename = $matches[2];
        }
        
        // Remove file extensions (.md, .php, .html) from display names
        $filename = preg_replace('/\.(md|php|html)$/', '', $filename);
        
        // Handle special cases (case-sensitive display names)
        $specialCases = [
            'phpinfo' => 'phpinfo',
            'ict' => 'ICT',
        ];
        if (isset($specialCases[strtolower($filename)])) {
            return ($hasLeadingDash ? '- ' : '') . $specialCases[strtolower($filename)];
        }
        
        // Replace hyphens and underscores with spaces, then title case
        $name = str_replace(['-', '_'], ' ', $filename);
        $name = trim($name);
        $name = ucwords(strtolower($name));
        
        // Post-process special cases in compound names
        foreach ($specialCases as $lower => $correct) {
            $name = str_ireplace(ucfirst($lower), $correct, $name);
        }
        
        return ($hasLeadingDash ? '- ' : '') . $name;
    }
    
    /**
     * Extract page title from file content
     * 
     * @param string $filePath Path to the file
     * @return string|null Extracted title or null if not found
     */
    private function extractPageTitle($filePath) {
        $content = file_get_contents($filePath);
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
        
        if ($extension === 'md') {
            // Extract first H1 from Markdown
            if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
                return trim($matches[1]);
            }
        } elseif ($extension === 'php') {
            // Extract title from PHP file
            if (preg_match('/\$title\s*=\s*["\']([^"\']+)["\']/', $content, $matches)) {
                return trim($matches[1]);
            }
        } elseif ($extension === 'html') {
            // Extract title from HTML file
            if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
                return trim(strip_tags($matches[1]));
            }
            if (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
                return trim(strip_tags($matches[1]));
            }
        }
        
        return null;
    }
    
    /**
     * Parse PHP file and capture output
     * 
     * @param string $filePath Path to PHP file
     * @return array Parsed content with title and body
     */
    private function parsePHP($filePath) {
        // Read file content first to extract metadata
        $fileContent = file_get_contents($filePath);
        $parsed = $this->parseMetadata($fileContent);
        $metadata = $parsed['metadata'];
        
        // Extract title from metadata or PHP variables
        $title = $metadata['title'] ?? '';
        
        ob_start();
        // Make metadata available to the included file
        $pageMetadata = $metadata;
        include $filePath;
        $content = ob_get_clean();
        
        // Remove any remaining metadata from PHP output
        $content = preg_replace('/^---\s*\n.*?\n---\s*\n/s', '', $content);
        
        // Remove metadata from content if it was included
        $parsed = $this->parseMetadata($content);
        $content = $parsed['content'];
        
        // Extract filename for title
        $filename = basename($filePath);
        $cleanName = $this->formatDisplayName($filename);
        
        return [
            'title' => $title ?: $cleanName ?: 'Untitled',
            'content' => $content,
            'metadata' => $metadata,
            'layout' => $metadata['layout'] ?? 'sidebar-content'
        ];
    }
    
    /**
     * Parse HTML content and extract title
     * 
     * @param string $content Raw HTML content
     * @return array Parsed content with title and body
     */
    private function parseHTML($content, $actualFilePath = '') {
        // Parse metadata first
        $parsed = $this->parseMetadata($content);
        $metadata = $parsed['metadata'];
        $content = $parsed['content'];
        
        // Extract title from metadata or HTML tags
        $title = $metadata['title'] ?? '';
        if (empty($title)) {
            if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
                $title = trim(strip_tags($matches[1]));
            } elseif (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
                $title = trim(strip_tags($matches[1]));
            }
        }
        
        // Extract filename for title
        $filename = basename($actualFilePath);
        $cleanName = $this->formatDisplayName($filename);
        
        return [
            'title' => $title ?: $cleanName ?: 'Untitled',
            'content' => $content,
            'metadata' => $metadata,
            'layout' => $metadata['layout'] ?? 'sidebar-content'
        ];
    }
    
    /**
     * Check if content directory is empty
     * 
     * @return bool True if content directory is empty or doesn't exist
     */
    public function isContentDirEmpty() {
        $contentDir = $this->config['content_dir'];
        if (!is_dir($contentDir)) {
            return true;
        }
        
        $files = scandir($contentDir);
        $files = array_diff($files, ['.', '..']);
        
        return empty($files);
    }
    
    /**
     * Get guide page content based on user language
     * 
     * @return array Guide page data
     */
private function getGuidePage() {
        $lang = $this->currentLanguage;
        $guideFile = __DIR__ . '/../../../guide/' . $lang . '.codepress.md';
        
        if (!file_exists($guideFile)) {
            $guideFile = __DIR__ . '/../../../guide/en.codepress.md'; // Fallback to English
    }
        
        $content = file_get_contents($guideFile);
        
        // Parse metadata first
        $parsed = $this->parseMetadata($content);
        $metadata = $parsed['metadata'];
        $contentWithoutMeta = $parsed['content'];
        
        // Configure CommonMark environment (autoloader already loaded in bootstrap)
        $config = [
            'html_input' => 'strip',
            'allow_unsafe_links' => false,
            'max_nesting_level' => 100,
        ];
        
        // Create environment with extensions
        $environment = new \League\CommonMark\Environment\Environment($config);
        $environment->addExtension(new \League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Autolink\AutolinkExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Strikethrough\StrikethroughExtension());
        $environment->addExtension(new \League\CommonMark\Extension\Table\TableExtension());
        $environment->addExtension(new \League\CommonMark\Extension\TaskList\TaskListExtension());
        
        // Create converter
        $converter = new \League\CommonMark\MarkdownConverter($environment);
        
        // Convert to HTML
        $body = $converter->convert($contentWithoutMeta)->getContent();
        
        // Extract title from metadata or first H1
        $title = $metadata['title'] ?? '';
        if (empty($title) && preg_match('/^#\s+(.+)$/m', $contentWithoutMeta, $matches)) {
            $title = trim($matches[1]);
        }
        
        // Set special title for guide
        $title = $this->t('manual') . ' - CodePress CMS';
        
        return [
            'title' => $title,
            'content' => $body,
            'metadata' => $metadata,
            'layout' => $metadata['layout'] ?? 'content'
        ];
    }
    
    /**
     * Detect user language from browser headers
     * 
     * @return string Language code ('nl' or 'en')
     */
    /**
     * Generate directory listing page
     * 
     * @param string $pagePath Relative path to directory
     * @param string $dirPath Absolute path to directory
     * @return array Directory listing page data
     */
    private function getDirectoryListing($pagePath, $dirPath) {
        // Get the directory name from the path, not from a potential file
        $pathParts = explode('/', $pagePath);
        $dirName = end($pathParts);
        

        
        $title = $this->formatDisplayName($dirName) ?: 'Home';
        
        $content = '<h1>' . htmlspecialchars($title) . '</h1>';
        $result = [
            'title' => $title,
            'content' => $content
        ];
        
        // Debug: ensure we're returning the right title
        
        if (!is_dir($dirPath)) {
            return [
                'title' => $title,
                'content' => $content . '<p>Directory not found.</p>'
            ];
        }
        
        $items = scandir($dirPath);
        sort($items);
        $hasContent = false;
        
        // Collect all items
        $allItems = [];
        
        foreach ($items as $item) {
            if ($item[0] === '.') continue;
            
            $itemPath = $dirPath . '/' . $item;
            $relativePath = $pagePath ? $pagePath . '/' . $item : $item;
            
            if (is_dir($itemPath)) {
                $allItems[] = [
                    'name' => ucfirst($item),
                    'path' => $relativePath,
                    'url' => $this->buildUrl($relativePath),
                    'icon' => 'bi-folder',
                    'type' => 'directory'
                ];
            } elseif (preg_match('/\.(md|php|html)$/', $item)) {
                $extractedTitle = $this->extractPageTitle($itemPath);
                $fileTitle = $extractedTitle ?: ucfirst(pathinfo($item, PATHINFO_FILENAME));
                $pathWithoutExt = preg_replace('/\.[^.]+$/', '', $relativePath);
                $icon = pathinfo($item, PATHINFO_EXTENSION) === 'md' ? 'bi-file-text' : 
                        (pathinfo($item, PATHINFO_EXTENSION) === 'php' ? 'bi-file-code' : 'bi-file-earmark');
                $allItems[] = [
                    'name' => $fileTitle,
                    'path' => $pathWithoutExt,
                    'url' => $this->buildUrl($pathWithoutExt),
                    'icon' => $icon,
                    'type' => 'file'
                ];
            }
        }
        
        // Display all items in a single column
        if (!empty($allItems)) {
            $content .= '<div class="list-group">';
            foreach ($allItems as $item) {
                $content .= '<a href="' . htmlspecialchars($item['url']) . '" class="list-group-item list-group-item-action d-flex align-items-center">';
                $content .= '<i class="bi ' . $item['icon'] . ' me-3"></i>';
                $content .= '<span>' . htmlspecialchars($item['name']) . '</span>';
                $content .= '</a>';
            }
            $content .= '</div>';
            $hasContent = true;
        }
        
        if (!$hasContent) {
            $content .= '<p>' . $this->t('directory_empty') . '.</p>';
        }
        
        return [
            'title' => $title,
            'content' => $content
        ];
    }
    
    /**
     * Get 404 error page content
     * 
     * @return array 404 page data
     */
    private function getError404() {
        return [
            'title' => $this->t('page_not_found'),
            'content' => '<h1>404 - ' . $this->t('page_not_found') . '</h1><p>' . $this->t('page_not_found_text') . '</p>'
        ];
    }
    
    /**
     * Get menu structure
     * 
     * @return array Menu structure for navigation
     */
    public function getMenu() {
        return $this->menu;
    }
    
    /**
     * Render the complete page with template
     * 
     * @return void
     */
    public function render() {
        $page = $this->getPage();
        $menu = $this->getMenu();
        $breadcrumb = $this->generateBreadcrumb();
        
        // Get homepage title
        $homepageTitle = $this->getHomepageTitle();
        
        // Get sidebar content from plugins (filtered per-page if specified in frontmatter)
        $allowedPlugins = null;
        if (!empty($page['metadata']['plugins'])) {
            $allowedPlugins = array_map('trim', explode(',', $page['metadata']['plugins']));
        }
        $sidebarContent = $this->pluginManager->getSidebarContent($allowedPlugins);
        
        // Get layout from page metadata
        $layout = $page['layout'] ?? 'sidebar-content';
        
        // Prepare template data
        $templateData = [
            'site_title' => $this->config['site_title'],
            'page_title' => htmlspecialchars($page['title']),

            'content' => $this->processContent($page['content']),
            'sidebar_content' => $sidebarContent,
            'layout' => $layout,
            'page_metadata' => $page['metadata'] ?? [],
            'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
            'menu' => $this->renderMenu($menu),
            'breadcrumb' => $breadcrumb,
            'default_page' => $this->config['default_page'],
            'homepage' => $this->config['default_page'],
            'homepage_title' => $homepageTitle,
            'is_homepage' => (!isset($_GET['page']) || $_GET['page'] === $this->config['default_page']),
            'home_active_class' => (!isset($_GET['page']) || $_GET['page'] === $this->config['default_page']) ? 'active' : '',
            'is_guide_page' => isset($_GET['guide']),
            'lang_switch_url' => '',
            'author_name' => $this->config['author']['name'] ?? 'CodePress Developer',
            'author_website' => $this->config['author']['website'] ?? '#',
            'author_git' => 'https://git.noorlander.info/E.Noorlander',
            'seo_description' => $this->config['seo']['description'] ?? 'CodePress CMS - Lightweight file-based content management system',
            'seo_keywords' => $this->config['seo']['keywords'] ?? 'cms, php, content management, file-based',
            'cms_version' => ($this->config['show_version'] ?? true) && isset($this->config['version_info']) ? 'v' . $this->config['version_info']['version'] : '',
            // Theme colors
            'header_color' => $this->config['theme']['header_color'] ?? '#0d6efd',
            'header_font_color' => $this->config['theme']['header_font_color'] ?? '#ffffff',
            'header_height' => $this->config['theme']['header_height'] ?? '56',
            'navigation_color' => $this->config['theme']['navigation_color'] ?? '#f8f9fa',
            'navigation_font_color' => $this->config['theme']['navigation_font_color'] ?? '#000000',
            'nav_height' => $this->config['theme']['nav_height'] ?? '42',
            'sidebar_background' => $this->config['theme']['sidebar_background'] ?? '#f8f9fa',
            'sidebar_border' => $this->config['theme']['sidebar_border'] ?? '#dee2e6',
            'background_image_css' => $this->getBackgroundImageCss(),
            'background_image_opacity' => $this->getBackgroundImageOpacity(),
            // Language
            'current_lang' => $this->currentLanguage,
            'current_lang_upper' => strtoupper($this->currentLanguage),
            'available_langs' => array_map(function($lang) {
                $lang['is_current'] = $lang['code'] === $this->currentLanguage;
                return $lang;
            }, $this->getAvailableLanguages()),
            // Translations
            't_home' => $this->t('home'),
            't_search' => $this->t('search'),
            't_search_placeholder' => $this->t('search_placeholder'),
            't_search_button' => $this->t('search_button'),
            't_welcome' => $this->t('welcome'),
            't_created' => $this->t('created'),
            't_modified' => $this->t('modified'),
            't_author' => $this->t('author'),
            't_manual' => $this->t('manual'),
            't_no_content' => $this->t('no_content'),
            't_no_results' => $this->t('no_results'),
            't_results_found' => $this->t('results_found'),
            't_breadcrumb_home' => $this->t('breadcrumb_home'),
            't_file_details' => $this->t('file_details'),
            't_guide' => $this->t('guide'),
            't_powered_by' => $this->t('powered_by'),
            't_directory_empty' => $this->t('directory_empty'),
            't_page_not_found' => $this->t('page_not_found'),
            't_page_not_found_text' => $this->t('page_not_found_text'),
            't_mappen' => $this->t('mappen'),
            't_paginas' => $this->t('paginas'),
            't_author_website' => $this->t('author_website'),
            't_author_git' => $this->t('author_git')
        ];
        
        // File info for footer
        if (isset($page['file_info'])) {
            $templateData['created'] = htmlspecialchars($page['file_info']['created']);
            $templateData['modified'] = htmlspecialchars($page['file_info']['modified']);
            $templateData['file_info_block'] = true;
        } else {
            $templateData['created'] = '';
            $templateData['modified'] = '';
            $templateData['file_info_block'] = false;
        }
        

        
        // Check if content exists for guide link
        $hasContent = !$this->isContentDirEmpty();
        $templateData['has_content'] = $hasContent;
        
        // Don't show site title link on guide page
        $templateData['show_site_link'] = !$this->isContentDirEmpty() && !isset($_GET['guide']);
        
        // Load and render all templates with data
        $layoutTemplate = file_get_contents($this->config['templates_dir'] . '/layout.mustache');
        $headerTemplate = file_get_contents($this->config['templates_dir'] . '/assets/header.mustache');
        $navigationTemplate = file_get_contents($this->config['templates_dir'] . '/assets/navigation.mustache');
        $footerTemplate = file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache');
        
        // Determine content type and load appropriate template
        $contentType = $this->getContentType($page);
        $contentTemplateFile = $this->config['templates_dir'] . '/' . $contentType . '_content.mustache';
        $contentTemplate = file_exists($contentTemplateFile) ? file_get_contents($contentTemplateFile) : '<div class="content">{{{content}}}</div>';
        

        
        // Render all templates with data
        $renderedHeader = SimpleTemplate::render($headerTemplate, $templateData);
        $renderedNavigation = SimpleTemplate::render($navigationTemplate, $templateData);
        $renderedFooter = SimpleTemplate::render($footerTemplate, $templateData);
        $renderedContent = SimpleTemplate::render($contentTemplate, $templateData);
        
        // Replace partials in layout
        $finalTemplate = str_replace('{{>header}}', $renderedHeader, $layoutTemplate);
        $finalTemplate = str_replace('{{>navigation}}', $renderedNavigation, $finalTemplate);
        $finalTemplate = str_replace('{{>footer}}', $renderedFooter, $finalTemplate);
        $finalTemplate = str_replace('{{>content_template}}', $renderedContent, $finalTemplate);
        
        // Render the final layout with all template data
        $renderedLayout = SimpleTemplate::render($finalTemplate, $templateData);
        
        echo $renderedLayout;
    }
    
    /**
     * Generate breadcrumb navigation HTML
     * 
     * @return string Breadcrumb HTML
     */
    public function generateBreadcrumb() {
        // Sidebar toggle button (shown before home icon in breadcrumb)
        $sidebarToggle = '<li class="breadcrumb-item sidebar-toggle-item"><button type="button" class="sidebar-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar" aria-label="Toggle Sidebar" aria-expanded="true"><i class="bi bi-layout-sidebar-inset"></i></button></li>';
        
        if (isset($_GET['search'])) {
            return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item"><a href="/' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $this->t('search') . '</li></ol></nav>';
        }
        
        $page = $_GET['page'] ?? $this->config['default_page'];
        $page = htmlspecialchars($page, ENT_QUOTES, 'UTF-8');
        $page = preg_replace('/\.[^.]+$/', '', $page);
        
        // Convert page to clean URL format for breadcrumb
        if ($page === $this->config['default_page']) {
            return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item active"><i class="bi bi-house"></i></li></ol></nav>';
        }
        
        $breadcrumb = '<nav aria-label="breadcrumb"><ol class="breadcrumb">';
        
        // Start with sidebar toggle, then home icon linking to default page (root)
        $breadcrumb .= $sidebarToggle;
        $breadcrumb .= '<li class="breadcrumb-item"><a href="/' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li>';
        
        // Split page path and build breadcrumb items
        $parts = explode('/', $page);
        $currentPath = '';
        
        foreach ($parts as $i => $part) {
            $currentPath .= ($currentPath ? '/' : '') . $part;
            $title = htmlspecialchars(ucfirst($part), ENT_QUOTES, 'UTF-8');
            $safePath = htmlspecialchars($currentPath, ENT_QUOTES, 'UTF-8');
            
            if ($i === count($parts) - 1) {
                // Last part - active page
                $breadcrumb .= '<li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $title . '</li>';
            } else {
                // Parent directory - clickable link with separator
                $breadcrumb .= '<li class="breadcrumb-item"> > </li><li class="breadcrumb-item"><a href="/' . $this->currentLanguage . '/' . $safePath . '">' . $title . '</a></li>';
            }
        }
        
        $breadcrumb .= '</ol></nav>';
        return $breadcrumb;
    }
    
    /**
     * Render menu HTML with dropdown support
     * 
     * @param array $items Menu items to render
     * @param int $level Current nesting level
     * @return string Rendered menu HTML
     */
    private function renderMenu($items, $level = 0) {
        $html = '';
        foreach ($items as $item) {
            if ($item['type'] === 'folder') {
                $hasChildren = !empty($item['children']);
                
                if ($hasChildren) {
                    $folderId = 'folder-' . str_replace('/', '-', $item['path']);
                    $isExpanded = $this->folderContainsActivePage($item['children']);
                    
                    if ($level === 0) {
                        // Root level folders
                        $html .= '<li class="nav-item dropdown">';
                        $html .= '<a class="nav-link dropdown-toggle" href="#" id="' . $folderId . '" role="button" data-bs-toggle="dropdown" aria-expanded="' . ($isExpanded ? 'true' : 'false') . '">';
                        $html .= htmlspecialchars($item['title']) . ' <i class="bi bi-chevron-down"></i>';
                        $html .= '</a>';
                        $html .= '<ul class="dropdown-menu" aria-labelledby="' . $folderId . '">';
                        $html .= $this->renderMenu($item['children'], $level + 1);
                        $html .= '</ul>';
                        $html .= '</li>';
                    } else {
                        // Nested folders in dropdown
                        $html .= '<li class="dropdown-submenu">';
                        $html .= '<a class="dropdown-item dropdown-toggle" href="#" id="' . $folderId . '">';
                        $html .= htmlspecialchars($item['title']) . ' <i class="bi bi-chevron-down"></i>';
                        $html .= '</a>';
                        $html .= '<ul class="dropdown-menu" aria-labelledby="' . $folderId . '">';
                        $html .= $this->renderMenu($item['children'], $level + 1);
                        $html .= '</ul>';
                        $html .= '</li>';
                    }
                } else {
                    if ($level === 0) {
                        $html .= '<li class="nav-item">';
                        $html .= '<span class="nav-link text-muted">' . htmlspecialchars($item['title']) . '</span>';
                        $html .= '</li>';
                    } else {
                        $html .= '<li><span class="dropdown-item text-muted">' . htmlspecialchars($item['title']) . '</span></li>';
                    }
                }
            } else {
                // Show files in root as tabs, files in folders as dropdown items
                if ($level === 0) {
                    // Don't show the homepage file as a separate tab since it's already the Home button
                    if ($item['path'] === $this->config['default_page']) {
                        continue;
                    }
                    
                    $active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
                    $html .= '<li class="nav-item">';
                    $html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">';
                    $html .= htmlspecialchars($item['title']);
                    $html .= '</a>';
                    $html .= '</li>';
                } else {
                    // Show files in dropdown menus
                    $active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
                    $html .= '<li><a class="dropdown-item ' . $active . '" href="' . htmlspecialchars($item['url']) . '">';
                    $html .= htmlspecialchars($item['title']);
                    $html .= '</a></li>';
                }
            }
        }
        return $html;
    }
    
    /**
     * Determine content type for current page
     * 
     * @param array $page Page data
     * @return string Content type (markdown, php, html)
     */
    private function getContentType($page) {
        // Try to determine content type from page request
        $pagePath = $_GET['page'] ?? $this->config['default_page'];
        $pagePath = preg_replace('/\.[^.]+$/', '', $pagePath);
        
        $filePath = $this->config['content_dir'] . '/' . $pagePath;
        
        // Check for different file extensions
        if (file_exists($filePath . '.md')) {
            return 'markdown';
        } elseif (file_exists($filePath . '.php')) {
            return 'php';
        } elseif (file_exists($filePath . '.html')) {
            return 'html';
        } elseif (file_exists($filePath)) {
            $extension = pathinfo($filePath, PATHINFO_EXTENSION);
            return in_array($extension, ['md', 'php', 'html']) ? $extension : 'markdown';
        }
        
        // Default to markdown
        return 'markdown';
    }
    
    /**
     * Get homepage title
     * 
     * @return string Homepage title
     */
    private function getHomepageTitle() {
        // Use formatted filename for homepage title in navigation
        return $this->formatDisplayName($this->config['default_page']);
    }
    
    /**
     * Check if folder contains the currently active page
     * 
     * @param array $children Array of child items
     * @return bool True if active page is found in children
     */
    private function folderContainsActivePage($children) {
        foreach ($children as $child) {
            if ($child['type'] === 'folder') {
                if (!empty($child['children']) && $this->folderContainsActivePage($child['children'])) {
                    return true;
                }
            } else {
                if (isset($_GET['page']) && $_GET['page'] === $child['path']) {
                    return true;
                }
            }
        }
        return false;
    }

    private function getBackgroundImageCss(): string
    {
        $bg = $this->config['theme']['background_image'] ?? '';
        if (empty($bg)) {
            return 'none';
        }
        if (str_starts_with($bg, 'http')) {
            return 'url(' . $bg . ')';
        }
        return 'url(/themes/' . $bg . ')';
    }

    private function getBackgroundImageOpacity(): int
    {
        $opacity = intval($this->config['theme']['background_image_opacity'] ?? 100);
        return max(0, min(100, $opacity));
    }

    private function processContent(string $content): string
    {
        return str_replace('-/assets/', '/-assets/', $content);
    }
}