<?php

/**
 * CodePress Admin Console - Entry Point
 * Access via: //admin/login|dashboard|content|config|plugins|users|logout
 */

// Security headers
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
header_remove('X-Powered-By');

// Load Composer autoloader for CommonMark and Twig
$autoloader = __DIR__ . '/../vendor/autoload.php';
if (file_exists($autoloader)) {
    require_once $autoloader;
}

// Load admin components
$appConfig = require __DIR__ . '/../admin/config/app.php';
require_once __DIR__ . '/../admin/src/AdminAuth.php';
require_once __DIR__ . '/../cms/core/class/RequestLogger.php';
require_once __DIR__ . '/../cms/core/class/BotGuard.php';
require_once __DIR__ . '/../cms/core/class/Cache.php';
require_once __DIR__ . '/../cms/core/class/GeoIP.php';
require_once __DIR__ . '/../cms/core/class/Analytics.php';
require_once __DIR__ . '/../cms/core/class/LogManager.php';
require_once __DIR__ . '/../cms/core/class/ContentBackup.php';
require_once __DIR__ . '/../cms/core/plugin/PluginAPIInterface.php';
require_once __DIR__ . '/../cms/core/plugin/CMSAPI.php';
require_once __DIR__ . '/../cms/core/plugin/AdminPluginAPI.php';
require_once __DIR__ . '/../cms/core/plugin/PluginManager.php';

// Initialize dynamic logging from site config
$siteConfigForLogging = file_exists($appConfig['config_json'])
    ? (json_decode(file_get_contents($appConfig['config_json']), true) ?? [])
    : [];
LogManager::init($siteConfigForLogging['logging'] ?? []);

$auth = new AdminAuth($appConfig);

// Initialize Twig (admin theme: admin/theme/default/views)
$adminTheme = 'default';
$twigLoader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../admin/theme/' . $adminTheme . '/views');
$twig = new \Twig\Environment($twigLoader, [
    'cache' => __DIR__ . '/../var/cache/twig',
    'auto_reload' => true,
    'debug' => true,
]);

// Add Twig functions
$twig->addFunction(new \Twig\TwigFunction('get_country_flag', function($country) {
    return GeoIP::getCountryFlagEmoji($country);
}));
$twig->addFunction(new \Twig\TwigFunction('get_country_name', function($country) {
    return GeoIP::getCountryName($country);
}));

// Add Twig global for user role and permission checks
$twig->addGlobal('user_role', $auth->getCurrentRole());
$twig->addFunction(new \Twig\TwigFunction('has_permission', function($route) use ($auth) {
    return $auth->hasPermission($route);
}));
$twig->addFunction(new \Twig\TwigFunction('role_label', function($role) {
    return AdminAuth::getRoleLabel($role);
}));

// Load admin interface translations
// admin_language is resolved from: config admin_language -> language.default -> 'nl'
function loadAdminTranslations(?array $siteConfig = null): array {
    $adminLang = $siteConfig['admin_language'] ?? ($siteConfig['language']['default'] ?? 'nl');
    $langDir = __DIR__ . '/../language/';
    $file = $langDir . $adminLang . '/admin.php';
    if (file_exists($file)) {
        $t = include $file;
        if (is_array($t)) {
            return $t;
        }
    }
    // Fallback to Dutch admin translations
    $fallback = $langDir . 'nl/admin.php';
    if (file_exists($fallback)) {
        $t = include $fallback;
        if (is_array($t)) {
            return $t;
        }
    }
    return [];
}

$siteConfigForI18n = file_exists($appConfig['config_json'])
    ? (json_decode(file_get_contents($appConfig['config_json']), true) ?? [])
    : [];
$adminTranslations = loadAdminTranslations($siteConfigForI18n);
$adminLangCode = $siteConfigForI18n['admin_language'] ?? ($siteConfigForI18n['language']['default'] ?? 'nl');

$twig->addGlobal('ta', $adminTranslations);
$twig->addGlobal('admin_lang', $adminLangCode);
$twig->addFunction(new \Twig\TwigFunction('ta', function($key) use ($adminTranslations) {
    return $adminTranslations[$key] ?? $key;
}));

// Initialize admin PluginManager for plugin-provided admin pages and menu items
$enabledPluginsList = $siteConfigForI18n['enabled_plugins'] ?? [];
$adminPluginManager = new PluginManager(__DIR__ . '/../plugins', $enabledPluginsList);
$adminPluginAPI = new AdminPluginAPI($siteConfigForI18n);
$adminPluginManager->setAPI($adminPluginAPI);
$adminPluginMenuItems = $adminPluginManager->getAdminMenuItems();
$twig->addGlobal('plugin_admin_menu', $adminPluginMenuItems);

// Routing
$route = $_GET['route'] ?? '';

// Helper to get sidebar color
function getSidebarColor($config) {
    $siteConfig = file_exists($config['config_json']) 
        ? json_decode(file_get_contents($config['config_json']), true) 
        : [];
    $activeTheme = $siteConfig['active_theme'] ?? 'default';
    $themeFile = __DIR__ . "/../themes/{$activeTheme}/theme.json";
    if (file_exists($themeFile)) {
        $theme = json_decode(file_get_contents($themeFile), true);
        return $theme['header_color'] ?? '#0a369d';
    }
    return '#0a369d';
}

// Essential plugins that cannot be disabled or deleted
function getProtectedPlugins(): array {
    return ['Navigation'];
}

function isProtectedPlugin(string $pluginName): bool {
    return in_array($pluginName, getProtectedPlugins(), true);
}

// Public routes (no auth required)
if ($route === 'login') {
    $error = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $result = $auth->login($_POST['username'] ?? '', $_POST['password'] ?? '', $_POST['csrf_token'] ?? '');
        if ($result['success']) {
            header('Location: /admin/dashboard');
            exit;
        }
        $error = $result['message'];
    }
    
    echo $twig->render('login.twig', [
        'error' => $error,
        'csrf_token' => $auth->getCsrfToken(),
    ]);
    exit;
}

// All other routes require authentication
if (!$auth->isAuthenticated()) {
    header('Location: /admin/login');
    exit;
}

$user = $auth->getCurrentUser();
$csrf = $auth->getCsrfToken();
$userRole = $auth->getCurrentRole();
$siteConfig = file_exists($appConfig['config_json']) 
    ? json_decode(file_get_contents($appConfig['config_json']), true) 
    : [];

// Check route permission (except dashboard which everyone can access)
if ($route !== '' && $route !== 'dashboard' && !$auth->hasPermission($route)) {
    http_response_code(403);
    echo $twig->render('pages/error.twig', [
        'user' => $user,
        'route' => '',
        'csrf_token' => $csrf,
        'error_code' => 403,
        'error_title' => 'Geen toegang',
        'error_message' => 'Je hebt geen rechten om deze pagina te bekijken.',
        'sidebar_color' => getSidebarColor($appConfig),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
    exit;
}

// Authenticated routes

// Check if a plugin handles this route (e.g. 'statistics', 'logs')
$pluginRouteMatch = $adminPluginManager->resolveAdminRoute($route);
if ($pluginRouteMatch !== null) {
    // Permission check: plugins require 'plugins' permission for now
    if (!$auth->hasPermission('plugins')) {
        http_response_code(403);
        echo $twig->render('pages/error.twig', [
            'user' => $user,
            'route' => '',
            'csrf_token' => $csrf,
            'error_code' => 403,
            'error_title' => $adminTranslations['no_permission_title'] ?? 'Geen toegang',
            'error_message' => $adminTranslations['no_permission'] ?? 'Geen toegang.',
            'sidebar_color' => getSidebarColor($appConfig),
            'needs_editor' => false,
            'message' => '',
            'message_type' => 'info',
        ]);
        exit;
    }
    $pluginHtml = $adminPluginManager->dispatchAdminRoute($pluginRouteMatch['plugin'], $pluginRouteMatch['action']);
    if ($pluginHtml !== null) {
        // Wrap plugin output in admin layout
        echo $twig->render('pages/plugin-page.twig', [
            'user' => $user,
            'route' => $route,
            'csrf_token' => $csrf,
            'sidebar_color' => getSidebarColor($appConfig),
            'needs_editor' => false,
            'message' => '',
            'message_type' => 'info',
            'plugin_content' => $pluginHtml,
        ]);
        exit;
    }
    // If plugin returned null, fall through to 404
}

switch ($route) {
    case 'logout':
        $auth->logout();
        header('Location: /admin/login');
        exit;

    case 'content':
        handleContent($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
        break;

    case 'content-edit':
        handleContentEdit($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
        break;

    case 'content-new':
        handleContentNew($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
        break;

    case 'content-delete':
        handleContentDelete($auth, $appConfig);
        break;

    case 'content-dir-create':
        handleContentDirCreate($auth, $appConfig);
        break;

    case 'content-dir-rename':
        handleContentDirRename($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'content-move':
        handleContentMove($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'content-backup':
        handleContentBackup($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'content-restore':
        handleContentRestore($auth, $appConfig);
        break;

    case 'content-git-init':
        handleContentGitInit($auth, $appConfig);
        break;

    case 'content-git-commit':
        handleContentGitCommit($auth, $appConfig);
        break;

    case 'content-git-restore':
        handleContentGitRestore($auth, $appConfig);
        break;

    case 'content-dir-delete':
        handleContentDirDelete($auth, $appConfig);
        break;

    case 'config':
        handleConfig($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'security':
        handleSecurity($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'theme':
        handleTheme($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
        break;

    case 'theme-new':
        handleThemeNew($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'plugins':
        handlePlugins($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'plugins-new':
        handlePluginsNew($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'plugins-edit':
        handlePluginsEdit($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'plugins-config':
        handlePluginsConfig($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'plugins-toggle':
        handlePluginsToggle($auth, $appConfig);
        break;

    case 'plugins-delete':
        handlePluginsDelete($auth, $appConfig);
        break;

    case 'users':
        handleUsers($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'guide':
        handleGuide($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'update':
        handleUpdate($auth, $appConfig, $twig, $user, $csrf);
        break;

    case 'media':
        handleMedia($auth, $appConfig, $twig, $user, $csrf);
        break;

    default:
        handleDashboard($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
}

// ============================================================================
// HANDLER FUNCTIONS
// ============================================================================

function handleDashboard($auth, $config, $twig, $user, $csrf, $siteConfig): void
{
    $contentDir = $config['content_dir'];
    $pluginsDir = $config['plugins_dir'];
    $configJson = $config['config_json'];

    $versionFile = __DIR__ . '/../version.php';
    $versionInfo = [];
    if (file_exists($versionFile)) {
        $verData = include $versionFile;
        $versionInfo = is_array($verData) ? $verData : [];
    }
    if (empty($versionInfo['version'])) {
        $versionInfo['version'] = '0.0.0';
    }

    $stats = [
        'pages' => countFiles($contentDir, ['md', 'php', 'html']),
        'directories' => countDirs($contentDir),
        'config_exists' => file_exists($configJson),
        'content_size' => formatSize(dirSize($contentDir)),
        'php_version' => PHP_VERSION,
        'cms_version' => $versionInfo['version'],
        'os' => PHP_OS_FAMILY . ' (' . php_uname('r') . ')',
    ];

    // Build plugin overview (name => enabled status)
    $pluginOverview = [];
    $enabledPlugins = $siteConfig['enabled_plugins'] ?? [];
    if (is_dir($pluginsDir)) {
        foreach (glob($pluginsDir . '/*', GLOB_ONLYDIR) as $pluginDir) {
            $pluginName = basename($pluginDir);
            $pluginOverview[$pluginName] = [
                'enabled' => in_array($pluginName, $enabledPlugins, true),
            ];
        }
    }
    ksort($pluginOverview);

    echo $twig->render('pages/dashboard.twig', [
        'user' => $user,
        'route' => 'dashboard',
        'csrf_token' => $csrf,
        'stats' => $stats,
        'site_config' => $siteConfig,
        'plugin_overview' => $pluginOverview,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
}


function handleContent($auth, $config, $twig, $user, $csrf, $siteConfig): void
{
    $contentDir = $config['content_dir'];
    $subdir = $_GET['dir'] ?? '';

    // Prevent path traversal
    $subdir = str_replace(['../', '..\\'], '', $subdir);
    $subdir = trim($subdir, '/');
    if ($subdir === '.' || $subdir === '') {
        $subdir = '';
    }
    $fullPath = rtrim($contentDir, '/') . '/' . $subdir;

    if (!is_dir($fullPath)) {
        $fullPath = $contentDir;
        $subdir = '';
    }

    $message = '';
    $messageType = '';

    // Handle file upload
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_FILES['file']['name'][0])) {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'pdf', 'zip', 'mp4', 'webm', 'ogg', 'mp3', 'wav', 'doc', 'docx', 'xls', 'xlsx'];
            $uploaded = 0;
            $errors = [];
            foreach ($_FILES['file']['name'] as $i => $name) {
                if ($_FILES['file']['error'][$i] !== UPLOAD_ERR_OK) continue;
                $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
                if (!in_array($ext, $allowedExt)) {
                    $errors[] = htmlspecialchars($name) . ' (niet toegestaan type)';
                    continue;
                }
                $filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $name);
                $dest = rtrim($fullPath, '/') . '/' . $filename;
                $n = 1;
                while (file_exists($dest)) {
                    $p = pathinfo($filename);
                    $dest = rtrim($fullPath, '/') . '/' . $p['filename'] . '_' . $n . '.' . ($p['extension'] ?? $ext);
                    $n++;
                }
                if (move_uploaded_file($_FILES['file']['tmp_name'][$i], $dest)) {
                    $uploaded++;
                } else {
                    $errors[] = htmlspecialchars($name);
                }
            }
            if ($uploaded > 0) {
                $message = $uploaded . ' bestand(en) geüpload.';
                $messageType = 'success';
            }
            if (!empty($errors)) {
                $message .= ' Fouten: ' . implode(', ', $errors);
                $messageType = $messageType ?: 'danger';
            }
        }
    }

    $items = scanContentDir($fullPath, $subdir);

    echo $twig->render('pages/content.twig', [
        'user' => $user,
        'route' => 'content',
        'csrf_token' => $csrf,
        'subdir' => $subdir,
        'items' => $items,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleContentEdit($auth, $config, $twig, $user, $csrf, $siteConfig): void
{
    $contentDir = $config['content_dir'];
    $file = $_GET['file'] ?? '';
    $file = str_replace(['../', '..\\'], '', $file);
    $filePath = rtrim($contentDir, '/') . '/' . $file;
    $message = '';
    $messageType = '';

    // Validate path
    $realPath = realpath($filePath);
    $realContentDir = realpath($contentDir);
    if (!$realPath || !$realContentDir || strpos($realPath, $realContentDir) !== 0) {
        header('Location: /admin/content');
        exit;
    }

    $fileExt = pathinfo($filePath, PATHINFO_EXTENSION);
    $isEditable = in_array($fileExt, ['md', 'php', 'html']);

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            // Handle rename
            $newFilename = trim($_POST['filename'] ?? '');
            $wasRenamed = false;
            if (!empty($newFilename)) {
                $newFilename = preg_replace('/[^a-zA-Z0-9._-]/', '-', $newFilename);
                $newFilename .= '.' . $fileExt;
                $parentDir = dirname($filePath);
                $newFilePath = $parentDir . '/' . $newFilename;

                $realParentDir = realpath($parentDir);
                if ($realParentDir && strpos($realParentDir, $realContentDir) === 0) {
                    if ($newFilePath !== $filePath && !file_exists($newFilePath)) {
                        rename($filePath, $newFilePath);
                        $wasRenamed = true;
                        adminLog($config, 'info', $user['username'] . ' hernoemde ' . basename($filePath) . ' naar ' . $newFilename);
                        $filePath = $newFilePath;
                        $newFile = dirname($file) . '/' . $newFilename;
                        $file = ltrim($newFile, './');
                    }
                }
            }

            // Save content only for editable files
            if ($isEditable) {
                $content = $_POST['content'] ?? '';
                $layout = $_POST['layout'] ?? '';
                if ($layout) {
                    $content = updateContentFrontmatter($content, 'layout', $layout);
                }
                $plugins = isset($_POST['plugins']) && is_array($_POST['plugins'])
                    ? implode(', ', array_map('trim', $_POST['plugins']))
                    : '';
                $content = updateContentFrontmatter($content, 'plugins', $plugins);
                backupContentFile($filePath);
                file_put_contents($filePath, $content);
                if (!$wasRenamed) {
                    adminLog($config, 'info', $user['username'] . ' bewerkte ' . basename($filePath));
                }
                $message = 'Bestand opgeslagen.';
                $messageType = 'success';
            }
        }
    }

    $fileName = basename($filePath);
    $fileContent = $isEditable ? file_get_contents($filePath) : '';
    $currentLayout = extractFrontmatterValue($fileContent, 'layout');
    $currentPlugins = extractFrontmatterValue($fileContent, 'plugins');
    $selectedPlugins = array_map('trim', explode(',', $currentPlugins));
    if ($selectedPlugins === ['']) $selectedPlugins = [];

    // Get available layouts from active theme's theme.json
    $themeLayouts = [];
    $themeDefaultLayout = 'full_content';
    $activeThemeName = $siteConfig['active_theme'] ?? 'default';
    $themeDir = __DIR__ . "/../themes/{$activeThemeName}";
    $themeJsonFile = $themeDir . '/theme.json';
    if (file_exists($themeJsonFile)) {
        $themeJson = json_decode(file_get_contents($themeJsonFile), true);
        $themeDefaultLayout = $themeJson['config']['default_template'] ?? 'full_content';
        if (isset($themeJson['template']) && is_array($themeJson['template'])) {
            foreach ($themeJson['template'] as $key => $twigFile) {
                // Skip internal layouts that users shouldn't select
                if (in_array($key, ['guide'], true)) continue;
                $themeLayouts[$key] = $twigFile;
            }
        }
    } else {
        // Fallback: scan theme directory for .twig files
        if (is_dir($themeDir)) {
            foreach (glob($themeDir . '/*.twig') as $layoutFile) {
                $layoutName = basename($layoutFile, '.twig');
                if (!in_array($layoutName, ['base', 'guide'], true)) {
                    $themeLayouts[$layoutName] = $layoutName . '.twig';
                }
            }
        }
    }

    // Get available plugins
    $pluginsDir = $config['plugins_dir'];
    $availablePlugins = [];
    if (is_dir($pluginsDir)) {
        foreach (glob($pluginsDir . '*', GLOB_ONLYDIR) as $pluginDir) {
            $pluginName = basename($pluginDir);
            if ($pluginName !== '.' && $pluginName !== '..' && file_exists($pluginDir . '/plugin.json')) {
                $availablePlugins[] = $pluginName;
            }
        }
    }

    $currentLang = extractLanguagePrefix($file);
    $route = 'content-edit';
    $fileDir = trim(dirname($file), '.\\/');

    echo $twig->render('pages/content-edit.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'file' => $file,
        'fileDir' => $fileDir,
        'fileName' => $fileName,
        'fileExt' => $fileExt,
        'fileContent' => $fileContent,
        'isEditable' => $isEditable,
        'currentLayout' => $currentLayout ?: $themeDefaultLayout,
        'themeLayouts' => $themeLayouts,
        'themeDefaultLayout' => $themeDefaultLayout,
        'activeThemeName' => $activeThemeName,
        'availablePlugins' => $availablePlugins,
        'selectedPlugins' => $selectedPlugins,
        'currentLang' => $currentLang,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => $isEditable,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleContentNew($auth, $config, $twig, $user, $csrf, $siteConfig): void
{
    $contentDir = $config['content_dir'];
    $dir = $_GET['dir'] ?? '';
    $dir = str_replace(['../', '..\\'], '', $dir);
    $fullPath = rtrim($contentDir, '/') . '/' . $dir;

    $message = '';
    $messageType = '';

    // Get available layouts from active theme's theme.json
    $themeLayouts = [];
    $themeDefaultLayout = 'full_content';
    $activeThemeName = $siteConfig['active_theme'] ?? 'default';
    $themeDir = __DIR__ . "/../themes/{$activeThemeName}";
    $themeJsonFile = $themeDir . '/theme.json';
    if (file_exists($themeJsonFile)) {
        $themeJson = json_decode(file_get_contents($themeJsonFile), true);
        $themeDefaultLayout = $themeJson['config']['default_template'] ?? 'full_content';
        if (isset($themeJson['template']) && is_array($themeJson['template'])) {
            foreach ($themeJson['template'] as $key => $twigFile) {
                if (in_array($key, ['guide'], true)) continue;
                $themeLayouts[$key] = $twigFile;
            }
        }
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $filename = trim($_POST['filename'] ?? '');
            $ext = $_POST['extension'] ?? 'md';
            $layout = $_POST['layout'] ?? $themeDefaultLayout;
            if (!empty($filename)) {
                $filename = preg_replace('/[^a-zA-Z0-9._-]/', '-', $filename);
                $filename .= '.' . $ext;
                $dest = rtrim($fullPath, '/') . '/' . $filename;
                if (!file_exists($dest)) {
                    $content = "---\nlayout: " . $layout . "\n---\n\n# Nieuwe pagina\n\n";
                    file_put_contents($dest, $content);
                    adminLog($config, 'info', $user['username'] . ' creëerde ' . $filename);
                    header('Location: /admin/content-edit?file=' . urlencode($dir . '/' . $filename));
                    exit;
                } else {
                    $message = 'Bestand bestaat al.';
                    $messageType = 'danger';
                }
            }
        }
    }

    $route = 'content-new';
    $availableExtensions = ['md' => 'Markdown', 'php' => 'PHP', 'html' => 'HTML'];

    echo $twig->render('pages/content-new.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'dir' => $dir,
        'availableExtensions' => $availableExtensions,
        'themeLayouts' => $themeLayouts,
        'themeDefaultLayout' => $themeDefaultLayout,
        'activeThemeName' => $activeThemeName,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleContentDelete($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content');
        exit;
    }

    $contentDir = $config['content_dir'];
    $file = $_POST['file'] ?? $_GET['file'] ?? '';
    $file = str_replace(['../', '..\\'], '', $file);
    $filePath = rtrim($contentDir, '/') . '/' . $file;

    if (file_exists($filePath) && is_file($filePath)) {
        unlink($filePath);
        adminLog($config, 'info', $user['username'] . ' verwijderde ' . $file);
    }

    header('Location: /admin/content?dir=' . urlencode(dirname($file)));
    exit;
}

function handleContentDirCreate($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content');
        exit;
    }

    $contentDir = $config['content_dir'];
    $subdir = $_GET['dir'] ?? '';
    $subdir = str_replace(['../', '..\\'], '', $subdir);
    $dirname = trim($_POST['dirname'] ?? '');

    if (!empty($dirname)) {
        $dirname = preg_replace('/[^a-zA-Z0-9._-]/', '-', $dirname);
        $newPath = rtrim($contentDir, '/') . '/' . ($subdir ? $subdir . '/' : '') . $dirname;
        if (!file_exists($newPath)) {
            @mkdir($newPath, 0755, true);
            adminLog($config, 'info', $user['username'] . ' creëerde map ' . $dirname);
        }
    }

    header('Location: /admin/content?dir=' . urlencode($subdir));
    exit;
}

function handleContentDirRename($auth, $config, $twig, $user, $csrf): void
{
    $contentDir = $config['content_dir'];
    $dir = $_GET['dir'] ?? '';
    $dir = str_replace(['../', '..\\'], '', $dir);
    $fullPath = rtrim($contentDir, '/') . '/' . $dir;

    $message = '';
    $messageType = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $newName = trim($_POST['newname'] ?? '');
            if (!empty($newName)) {
                $newName = preg_replace('/[^a-zA-Z0-9._-]/', '-', $newName);
                $parentDir = dirname($fullPath);
                $newPath = $parentDir . '/' . $newName;
                if (!file_exists($newPath) && $newPath !== $fullPath) {
                    rename($fullPath, $newPath);
                    adminLog($config, 'info', $user['username'] . ' hernoemde map ' . basename($dir) . ' naar ' . $newName);
                    header('Location: /admin/content?dir=' . urlencode(dirname($dir) . '/' . $newName));
                    exit;
                } else {
                    $message = 'Map bestaat al of ongeldige naam.';
                    $messageType = 'danger';
                }
            }
        }
    }

    $route = 'content-dir-rename';

    echo $twig->render('pages/content-dir-form.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'dir' => $dir,
        'fullPath' => $fullPath,
        'currentName' => basename($dir),
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleContentDirDelete($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content');
        exit;
    }

    $contentDir = $config['content_dir'];
    $dir = $_GET['dir'] ?? $_POST['dir'] ?? '';
    $dir = str_replace(['../', '..\\'], '', $dir);
    $fullPath = rtrim($contentDir, '/') . '/' . $dir;

    if (is_dir($fullPath)) {
        $files = scandir($fullPath);
        $files = array_diff($files, ['.', '..']);
        if (empty($files)) {
            rmdir($fullPath);
            adminLog($config, 'info', $user['username'] . ' verwijderde map ' . $dir);
        }
    }

    header('Location: /admin/content?dir=' . urlencode(dirname($dir)));
    exit;
}

function handleContentMove($auth, $config, $twig, $user, $csrf): void
{
    $contentDir = $config['content_dir'];
    $item = $_GET['item'] ?? '';
    $item = str_replace(['../', '..\\'], '', $item);
    $fullPath = rtrim($contentDir, '/') . '/' . $item;

    $message = '';
    $messageType = '';

    // Get all directories for destination selection
    $directories = [];
    $realContentDir = realpath($contentDir);
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($contentDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($iterator as $file) {
        if ($file->isDir()) {
            $path = str_replace($realContentDir, '', realpath($file->getPathname()));
            $path = trim($path, '/\\');
            if ($path && $path !== $item && !str_starts_with($path, '-') && !str_starts_with($path, '.')) {
                $directories[] = $path;
            }
        }
    }
    sort($directories);

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $dest = trim($_POST['destination'] ?? '');
            if (!empty($dest)) {
                $dest = str_replace(['../', '..\\'], '', $dest);
                $destPath = rtrim($contentDir, '/') . '/' . $dest;
                if (is_dir($destPath)) {
                    $newPath = $destPath . '/' . basename($item);
                    if (!file_exists($newPath)) {
                        rename($fullPath, $newPath);
                        adminLog($config, 'info', $user['username'] . ' verplaatste ' . $item . ' naar ' . $dest);
                        header('Location: /admin/content?dir=' . urlencode($dest));
                        exit;
                    } else {
                        $message = 'Bestand of map bestaat al op de bestemming.';
                        $messageType = 'danger';
                    }
                }
            }
        }
    }

    $route = 'content-move';
    $isDir = is_dir($fullPath);
    $itemDir = trim(dirname($item), '.\\/');

    echo $twig->render('pages/content-move-form.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'item' => $item,
        'itemDir' => $itemDir,
        'fullPath' => $fullPath,
        'itemName' => basename($item),
        'isDir' => $isDir,
        'directories' => $directories,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}


function handleContentBackup($auth, $config, $twig, $user, $csrf): void
{
    $contentDir = $config['content_dir'];
    $projectRoot = $config['codepress_root'];
    $backup = new ContentBackup($contentDir, $projectRoot);

    $message = '';
    $messageType = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } elseif (isset($_POST['action'])) {
            $action = $_POST['action'];

            if ($action === 'download_zip') {
                $backupDir = $projectRoot . '/var/tmp';
                if (!is_dir($backupDir)) {
                    @mkdir($backupDir, 0755, true);
                }
                $backupFile = $backupDir . '/content-backup-' . date('YmdHis') . '.zip';

                if ($backup->createZipBackup($backupFile)) {
                    adminLog($config, 'info', $user['username'] . ' maakte een content ZIP backup aan');
                    header('Content-Type: application/zip');
                    header('Content-Disposition: attachment; filename="' . basename($backupFile) . '"');
                    header('Content-Length: ' . filesize($backupFile));
                    readfile($backupFile);
                    unlink($backupFile);
                    exit;
                } else {
                    $message = 'Kon geen ZIP backup maken.';
                    $messageType = 'danger';
                }
            }
        }
    }

    // Get git info
    $gitAvailable = $backup->isGitAvailable();
    $hasGitRepo = $backup->hasGitRepo();
    $gitCommits = [];

    if ($hasGitRepo) {
        $logResult = $backup->gitLog(20);
        $gitCommits = $logResult['commits'] ?? [];
    }

    $route = 'content-backup';

    echo $twig->render('pages/content-backup.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'git_available' => $gitAvailable,
        'has_git_repo' => $hasGitRepo,
        'git_commits' => $gitCommits,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}


function handleContentRestore($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content-backup');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content-backup?error=csrf');
        exit;
    }

    if (empty($_FILES['zipfile']['tmp_name'])) {
        header('Location: /admin/content-backup?error=nofile');
        exit;
    }

    $contentDir = $config['content_dir'];
    $projectRoot = $config['codepress_root'];
    $backup = new ContentBackup($contentDir, $projectRoot);

    $result = $backup->restoreFromZip($_FILES['zipfile']['tmp_name']);

    if ($result['success']) {
        adminLog($config, 'info', $user['username'] . ' herstelde content uit ZIP backup');
        header('Location: /admin/content-backup?restored=1');
    } else {
        adminLog($config, 'warning', $user['username'] . ' - content restore mislukt: ' . $result['message']);
        header('Location: /admin/content-backup?error=' . urlencode($result['message']));
    }
    exit;
}


function handleContentGitInit($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content-backup');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content-backup?error=csrf');
        exit;
    }

    $backup = new ContentBackup($config['content_dir'], $config['codepress_root']);
    $result = $backup->gitInit();

    if ($result['success']) {
        adminLog($config, 'info', $user['username'] . ' initialiseerde git in content/');
    }

    header('Location: /admin/content-backup?git=' . urlencode($result['message']));
    exit;
}


function handleContentGitCommit($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content-backup');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content-backup?error=csrf');
        exit;
    }

    $message = trim($_POST['commit_message'] ?? '');
    $backup = new ContentBackup($config['content_dir'], $config['codepress_root']);
    $result = $backup->gitCommit($message);

    if ($result['success']) {
        adminLog($config, 'info', $user['username'] . ' committe content: ' . $message);
    }

    header('Location: /admin/content-backup?git=' . urlencode($result['message']));
    exit;
}


function handleContentGitRestore($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/content-backup');
        exit;
    }

    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/content-backup?error=csrf');
        exit;
    }

    $commitHash = $_POST['commit'] ?? '';
    $backup = new ContentBackup($config['content_dir'], $config['codepress_root']);
    $result = $backup->gitRestore($commitHash);

    if ($result['success']) {
        adminLog($config, 'info', $user['username'] . ' herstelde content naar git commit ' . $commitHash);
    }

    header('Location: /admin/content-backup?git=' . urlencode($result['message']));
    exit;
}
{
    $configFile = $config['config_json'];
    $message = '';
    $messageType = '';
    $ta = loadAdminTranslations(file_exists($configFile)
        ? (json_decode(file_get_contents($configFile), true) ?? [])
        : []);

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = $ta['invalid_csrf'] ?? 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $newConfig = json_decode(file_get_contents($configFile), true) ?? [];
            $newConfig['site_title'] = $_POST['site_title'] ?? '';
            $defaultPage = $_POST['default_page'] ?? 'auto';
            if ($defaultPage === 'specific') {
                $defaultPage = $_POST['default_page_specific'] ?? 'auto';
            }
            $newConfig['default_page'] = $defaultPage;
            $newConfig['language']['default'] = $_POST['content_language'] ?? 'nl';
            $newConfig['admin_language'] = $_POST['admin_language'] ?? 'nl';
            
            backupContentFile($configFile);
            file_put_contents($configFile, json_encode($newConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
            adminLog($config, 'info', $user['username'] . ' bewerkte configuratie');

            // Redirect after successful save (PRG pattern) so the new admin
            // language is applied immediately without a manual reload.
            header('Location: /admin/config?saved=1');
            exit;
        }
    }

    // Show success message after redirect
    if (isset($_GET['saved'])) {
        $message = $ta['saved'] ?? 'Configuratie opgeslagen.';
        $messageType = 'success';
    }

    $currentConfig = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : [];
    $route = 'config';

    $contentPages = collectContentPages($config['content_dir']);
    $currentDefaultPage = $currentConfig['default_page'] ?? 'auto';

    echo $twig->render('pages/config.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'config' => $currentConfig,
        'content_pages' => $contentPages,
        'current_default_page' => $currentDefaultPage,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleSecurity($auth, $config, $twig, $user, $csrf): void
{
    $adminConfigFile = $config['admin_config'];
    $message = '';
    $messageType = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $adminConfig = json_decode(file_get_contents($adminConfigFile), true) ?? [];
            $adminConfig['security']['force_ssl'] = isset($_POST['force_ssl']);
            $adminConfig['security']['session_timeout'] = (int)($_POST['session_timeout'] ?? 3600);
            $adminConfig['security']['max_login_attempts'] = (int)($_POST['max_login_attempts'] ?? 5);
            
            file_put_contents($adminConfigFile, json_encode($adminConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
            adminLog($config, 'info', $user['username'] . ' bewerkte beveiligingsinstellingen');
            $message = 'Beveiligingsinstellingen opgeslagen.';
            $messageType = 'success';
        }
    }

    $adminConfig = file_exists($adminConfigFile) ? json_decode(file_get_contents($adminConfigFile), true) : [];
    $route = 'security';

    echo $twig->render('pages/security.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'config' => $adminConfig,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleTheme($auth, $config, $twig, $user, $csrf, $siteConfig): void
{
    $themesDir = __DIR__ . '/../themes';
    $themes = [];
    
    foreach (glob($themesDir . '/*', GLOB_ONLYDIR) as $themeDir) {
        $themeName = basename($themeDir);
        $themeJson = $themeDir . '/theme.json';
        $themeData = [
            'name' => $themeName,
            'title' => $themeName,
            'active' => ($siteConfig['active_theme'] ?? 'default') === $themeName,
        ];
        
        if (file_exists($themeJson)) {
            $data = json_decode(file_get_contents($themeJson), true);
            $themeData = array_merge($themeData, $data);
        }
        
        $themes[] = $themeData;
    }
    
    $message = '';
    $messageType = '';
    
    // Handle theme compilation
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['compile_scss'])) {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $themeToCompile = $_POST['theme'] ?? 'default';
            // SCSS compile logic here
            $message = 'SCSS gecompileerd voor ' . $themeToCompile;
            $messageType = 'success';
            adminLog($config, 'info', $user['username'] . ' compileerde SCSS voor ' . $themeToCompile);
        }
    }
    
    $route = 'theme';

    echo $twig->render('pages/theme.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'themes' => $themes,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleThemeNew($auth, $config, $twig, $user, $csrf): void
{
    $message = '';
    $messageType = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $themeName = trim($_POST['name'] ?? '');
            if (!empty($themeName)) {
                $themeName = preg_replace('/[^a-zA-Z0-9_-]/', '-', $themeName);
                $themesDir = __DIR__ . '/../themes';
                $newThemeDir = $themesDir . '/' . $themeName;
                
                if (!file_exists($newThemeDir)) {
                    @mkdir($newThemeDir, 0755, true);
                    @mkdir($newThemeDir . '/partials', 0755, true);
                    @mkdir($newThemeDir . '/css', 0755, true);
                    @mkdir($newThemeDir . '/js', 0755, true);
                    
                    $themeJson = [
                        'title' => ucfirst($themeName),
                        'default_layout' => 'full_content',
                        'header_color' => '#0a369d',
                    ];
                    file_put_contents($newThemeDir . '/theme.json', json_encode($themeJson, JSON_PRETTY_PRINT));
                    
                    adminLog($config, 'info', $user['username'] . ' creëerde thema ' . $themeName);
                    header('Location: /admin/theme');
                    exit;
                } else {
                    $message = 'Thema bestaat al.';
                    $messageType = 'danger';
                }
            }
        }
    }

    $route = 'theme-new';

    echo $twig->render('pages/theme-new.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handlePlugins($auth, $config, $twig, $user, $csrf): void
{
    $pluginsDir = $config['plugins_dir'];
    $configFile = $config['config_json'];
    $siteConfig = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : [];
    $enabledPlugins = $siteConfig['enabled_plugins'] ?? [];
    
    $plugins = [];
    foreach (glob($pluginsDir . '*', GLOB_ONLYDIR) as $pluginDir) {
        $pluginName = basename($pluginDir);
        $pluginJson = $pluginDir . '/plugin.json';
        
        $pluginData = [
            'name' => $pluginName,
            'enabled' => in_array($pluginName, $enabledPlugins),
            'protected' => isProtectedPlugin($pluginName),
            'type' => 'content',
        ];

        if (file_exists($pluginJson)) {
            $data = json_decode(file_get_contents($pluginJson), true);
            $pluginData = array_merge($pluginData, $data);
        }

        $pluginFile = $pluginDir . '/' . $pluginName . '.php';
        if (file_exists($pluginFile) && !isset($pluginData['type'])) {
            $source = file_get_contents($pluginFile);
            if (preg_match("/'type'\s*=>\s*'([^']+)'/", $source, $m)) {
                $pluginData['type'] = $m[1];
            }
        }
        
        $plugins[] = $pluginData;
    }
    
    $route = 'plugins';

    echo $twig->render('pages/plugins.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'plugins' => $plugins,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
}

function handlePluginsNew($auth, $config, $twig, $user, $csrf): void
{
    $message = '';
    $messageType = '';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $pluginName = trim($_POST['name'] ?? '');
            if (!empty($pluginName)) {
                $pluginName = preg_replace('/[^a-zA-Z0-9_-]/', '-', $pluginName);
                $pluginsDir = $config['plugins_dir'];
                $newPluginDir = $pluginsDir . $pluginName;
                
                if (!file_exists($newPluginDir)) {
                    @mkdir($newPluginDir, 0755, true);
                    
                    $pluginJson = [
                        'name' => ucfirst($pluginName),
                        'version' => '1.0.0',
                        'author' => $user['username'],
                    ];
                    file_put_contents($newPluginDir . '/plugin.json', json_encode($pluginJson, JSON_PRETTY_PRINT));
                    file_put_contents($newPluginDir . '/' . $pluginName . '.php', "<?php\n// Plugin: " . ucfirst($pluginName) . "\n\necho 'Hello from " . $pluginName . "';\n");
                    
                    adminLog($config, 'info', $user['username'] . ' creëerde plugin ' . $pluginName);
                    header('Location: /admin/plugins-edit?plugin=' . urlencode($pluginName));
                    exit;
                } else {
                    $message = 'Plugin bestaat al.';
                    $messageType = 'danger';
                }
            }
        }
    }

    $route = 'plugins-new';

    echo $twig->render('pages/plugins-new.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handlePluginsEdit($auth, $config, $twig, $user, $csrf): void
{
    $pluginsDir = $config['plugins_dir'];
    $plugin = $_GET['plugin'] ?? '';
    $plugin = preg_replace('/[^a-zA-Z0-9_-]/', '', $plugin);
    
    // Block editing protected plugins
    if (isProtectedPlugin($plugin)) {
        adminLog($config, 'warning', $user['username'] . ' probeerde essentiële plugin ' . $plugin . ' te bewerken');
        header('Location: /admin/plugins');
        exit;
    }
    
    $pluginDir = $pluginsDir . $plugin;
    $pluginFile = $pluginDir . '/' . $plugin . '.php';
    
    $message = '';
    $messageType = '';
    
    if (!file_exists($pluginFile)) {
        header('Location: /admin/plugins');
        exit;
    }
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $content = $_POST['content'] ?? '';
            file_put_contents($pluginFile, $content);
            adminLog($config, 'info', $user['username'] . ' bewerkte plugin ' . $plugin);
            $message = 'Plugin opgeslagen.';
            $messageType = 'success';
        }
    }
    
    $pluginContent = file_get_contents($pluginFile);
    $route = 'plugins-edit';

    echo $twig->render('pages/plugins-edit.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'pluginName' => $plugin,
        'pluginContent' => $pluginContent,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => true,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handlePluginsConfig($auth, $config, $twig, $user, $csrf): void
{
    $pluginsDir = $config['plugins_dir'];
    $plugin = $_GET['plugin'] ?? '';
    $plugin = preg_replace('/[^a-zA-Z0-9_-]/', '', $plugin);
    $pluginDir = $pluginsDir . $plugin;
    $configFile = $pluginDir . '/config.json';
    
    $message = '';
    $messageType = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $newConfig = $_POST['config'] ?? '';
            file_put_contents($configFile, $newConfig);
            adminLog($config, 'info', $user['username'] . ' bewerkte plugin config ' . $plugin);
            $message = 'Plugin configuratie opgeslagen.';
            $messageType = 'success';
        }
    }
    
    $pluginConfig = file_exists($configFile) ? file_get_contents($configFile) : '{}';
    $route = 'plugins-config';

    echo $twig->render('pages/plugin-config.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'pluginName' => $plugin,
        'pluginConfig' => $pluginConfig,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handlePluginsToggle($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/plugins');
        exit;
    }
    
    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/plugins');
        exit;
    }
    
    $plugin = $_POST['plugin'] ?? '';
    $plugin = preg_replace('/[^a-zA-Z0-9_-]/', '', $plugin);
    
    // Block toggling protected plugins
    if (isProtectedPlugin($plugin)) {
        adminLog($config, 'warning', $user['username'] . ' probeerde essentiële plugin ' . $plugin . ' te deactiveren');
        header('Location: /admin/plugins');
        exit;
    }
    
    $configFile = $config['config_json'];
    $siteConfig = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : [];
    $enabledPlugins = $siteConfig['enabled_plugins'] ?? [];
    
    if (in_array($plugin, $enabledPlugins)) {
        $enabledPlugins = array_diff($enabledPlugins, [$plugin]);
        adminLog($config, 'info', $user['username'] . ' deactiveerde plugin ' . $plugin);
    } else {
        $enabledPlugins[] = $plugin;
        adminLog($config, 'info', $user['username'] . ' activeerde plugin ' . $plugin);
    }
    
    $siteConfig['enabled_plugins'] = array_values($enabledPlugins);
    file_put_contents($configFile, json_encode($siteConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    
    header('Location: /admin/plugins');
    exit;
}

function handlePluginsDelete($auth, $config): void
{
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        header('Location: /admin/plugins');
        exit;
    }
    
    $user = $auth->getCurrentUser();
    if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
        header('Location: /admin/plugins');
        exit;
    }
    
    $plugin = $_POST['plugin'] ?? '';
    $plugin = preg_replace('/[^a-zA-Z0-9_-]/', '', $plugin);
    
    // Block deleting protected plugins
    if (isProtectedPlugin($plugin)) {
        adminLog($config, 'warning', $user['username'] . ' probeerde essentiële plugin ' . $plugin . ' te verwijderen');
        header('Location: /admin/plugins');
        exit;
    }
    
    $pluginsDir = $config['plugins_dir'];
    $pluginDir = $pluginsDir . $plugin;
    
    if (is_dir($pluginDir)) {
        // Remove plugin directory recursively
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($pluginDir, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($iterator as $file) {
            if ($file->isDir()) {
                rmdir($file->getPathname());
            } else {
                unlink($file->getPathname());
            }
        }
        rmdir($pluginDir);
        
        adminLog($config, 'info', $user['username'] . ' verwijderde plugin ' . $plugin);
    }
    
    header('Location: /admin/plugins');
    exit;
}

function handleUsers($auth, $config, $twig, $user, $csrf): void
{
    $message = '';
    $messageType = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!$auth->verifyCsrf($_POST['csrf_token'] ?? '')) {
            $message = 'Ongeldige CSRF token.';
            $messageType = 'danger';
        } else {
            $action = $_POST['action'] ?? '';
            
            if ($action === 'add') {
                $newUsername = trim($_POST['new_username'] ?? '');
                $newPassword = $_POST['new_password'] ?? '';
                $newRole = $_POST['new_role'] ?? 'content-manager';
                $newEmail = trim($_POST['new_email'] ?? '');
                $newAuthorName = trim($_POST['new_author_name'] ?? '');
                $newAuthorEmail = trim($_POST['new_author_email'] ?? '');
                
                $result = $auth->addUser($newUsername, $newPassword, $newRole, $newEmail, $newAuthorName, $newAuthorEmail);
                $message = $result['message'];
                $messageType = $result['success'] ? 'success' : 'danger';
            } elseif ($action === 'profile') {
                $profileUser = $_POST['profile_username'] ?? '';
                $profileEmail = trim($_POST['profile_email'] ?? '');
                $profileAuthorName = trim($_POST['profile_author_name'] ?? '');
                $profileAuthorEmail = trim($_POST['profile_author_email'] ?? '');
                
                $result = $auth->updateUserProfile($profileUser, $profileEmail, $profileAuthorName, $profileAuthorEmail);
                $message = $result['message'];
                $messageType = $result['success'] ? 'success' : 'danger';
            } elseif ($action === 'delete') {
                $deleteUser = $_POST['delete_username'] ?? '';
                if ($deleteUser !== $user['username']) {
                    $result = $auth->deleteUser($deleteUser);
                    $message = $result['message'];
                    $messageType = $result['success'] ? 'success' : 'danger';
                } else {
                    $message = 'Je kunt jezelf niet verwijderen.';
                    $messageType = 'danger';
                }
            } elseif ($action === 'change_role') {
                $roleUser = $_POST['role_username'] ?? '';
                $newRole = $_POST['new_role'] ?? '';
                $result = $auth->changeRole($roleUser, $newRole);
                $message = $result['message'];
                $messageType = $result['success'] ? 'success' : 'danger';
            }
        }
    }
    
    $route = 'users';
    $users = $auth->getUsers();
    $roles = AdminAuth::getRoles();

    echo $twig->render('pages/users.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'users' => $users,
        'roles' => $roles,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => $message,
        'message_type' => $messageType,
    ]);
}

function handleGuide($auth, $config, $twig, $user, $csrf): void
{
    $lang = $_GET['lang'] ?? 'nl';
    $page = $_GET['page'] ?? '';
    $rootDir = realpath(__DIR__ . '/..');
    
    // Get guide file
    if ($page) {
        $guideFile = $rootDir . '/guide/' . $lang . '/' . $page . '.md';
    } else {
        $guideFile = $rootDir . '/guide/' . $lang . '/index.md';
    }
    
    // Fallback to English
    if (!file_exists($guideFile) && $lang !== 'en') {
        $guideFile = $rootDir . '/guide/en/' . ($page ? $page . '.md' : 'index.md');
    }
    
    // Load content
    if (!file_exists($guideFile)) {
        $content = '<p>Handleiding niet gevonden.</p>';
    } else {
        $content = file_get_contents($guideFile);
        
        // Parse Markdown with Table support
        if (class_exists('League\CommonMark\Environment\Environment')) {
            $environment = new \League\CommonMark\Environment\Environment([
                'html_input' => 'strip',
                'heading_permalink' => [
                    'symbol' => '',
                    'aria_hidden' => true,
                    'html_class' => 'heading-permalink',
                    'id_prefix' => '',
                    'fragment_prefix' => '',
                    'apply_id_to_heading' => true,
                    'insert' => 'after',
                    'min_heading_level' => 1,
                    'max_heading_level' => 6,
                    'title' => 'Permalink',
                ],
            ]);
            $environment->addExtension(new \League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension());
            $environment->addExtension(new \League\CommonMark\Extension\Table\TableExtension());
            $environment->addExtension(new \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension());
            $converter = new \League\CommonMark\MarkdownConverter($environment);
            $content = $converter->convert($content)->getContent();
        } else {
            $content = nl2br(htmlspecialchars($content));
        }
    }
    
    // Build guide navigation sidebar using Navigation plugin
    $guideNav = '';
    $navPluginFile = $rootDir . '/plugins/Navigation/Navigation.php';
    if (file_exists($navPluginFile)) {
        require_once $navPluginFile;
        if (class_exists('Navigation')) {
            $navPlugin = new Navigation();
            $guideNav = $navPlugin->getSidebarContent();
        }
    }
    
    // Build breadcrumbs
    $breadcrumbs = [];
    if ($page) {
        $parts = explode('/', $page);
        $path = '';
        foreach ($parts as $part) {
            $path .= ($path ? '/' : '') . $part;
            $breadcrumbs[] = [
                'title' => str_replace('-', ' ', ucfirst($part)),
                'url' => $path,
            ];
        }
    }
    
    echo $twig->render('pages/guide.twig', [
        'user' => $user,
        'route' => 'guide',
        'csrf_token' => $csrf,
        'lang' => $lang,
        'page' => $page,
        'guide_breadcrumbs' => $breadcrumbs,
        'guide_lang' => $lang,
        'guide_page' => $page,
        'content' => $content,
        'guide_nav' => $guideNav,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
}

function handleUpdate($auth, $config, $twig, $user, $csrf): void
{
    $versionFile = __DIR__ . '/../version.php';
    $versionInfo = file_exists($versionFile) ? include $versionFile : [];
    $versionInfo['version'] = $versionInfo['version'] ?? '0.0.0';
    
    $isGitWritable = is_writable(__DIR__ . '/../.git');
    $route = 'update';

    echo $twig->render('pages/update.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'version' => $versionInfo['version'],
        'isGitWritable' => $isGitWritable,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
}

function handleMedia($auth, $config, $twig, $user, $csrf): void
{
    $contentDir = $config['content_dir'];
    $subdir = $_GET['dir'] ?? '';
    $subdir = str_replace(['../', '..\\'], '', $subdir);
    $fullPath = rtrim($contentDir, '/') . '/' . $subdir;
    
    $items = scanContentDir($fullPath, $subdir);
    $route = 'media';

    echo $twig->render('pages/media.twig', [
        'user' => $user,
        'route' => $route,
        'csrf_token' => $csrf,
        'subdir' => $subdir,
        'items' => $items,
        'sidebar_color' => getSidebarColor($config),
        'needs_editor' => false,
        'message' => '',
        'message_type' => 'info',
    ]);
}

// ============================================================================
// HELPER FUNCTIONS (from original admin.php)
// ============================================================================

function adminLog(array $config, string $level, string $message): void
{
    $logFile = $config['log_file'];
    $dir = dirname($logFile);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    $timestamp = date('Y-m-d H:i:s');
    $ip = RequestLogger::getClientIp();
    @file_put_contents($logFile, "[{$timestamp}] [{$level}] [{$ip}] {$message}\n", FILE_APPEND);

    if (class_exists('LogManager')) {
        LogManager::log(LogManager::EVENT_ADMIN, $level, $message, ['ip' => $ip]);
    }
}

function countFiles(string $dir, array $extensions = []): int
{
    $count = 0;
    if (!is_dir($dir)) return 0;
    
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
    foreach ($iterator as $file) {
        if ($file->isFile()) {
            if (empty($extensions) || in_array($file->getExtension(), $extensions)) {
                $count++;
            }
        }
    }
    return $count;
}

function countDirs(string $dir): int
{
    $count = 0;
    if (!is_dir($dir)) return 0;
    
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
    foreach ($iterator as $file) {
        if ($file->isDir() && !str_starts_with($file->getFilename(), '.')) {
            $count++;
        }
    }
    return $count;
}

function countEnabledPlugins(string $pluginsDir, string $configJson): int
{
    $config = file_exists($configJson) ? json_decode(file_get_contents($configJson), true) : [];
    $enabled = $config['enabled_plugins'] ?? [];
    return count($enabled);
}

function dirSize(string $dir): int
{
    $size = 0;
    if (!is_dir($dir)) return 0;
    
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
    foreach ($iterator as $file) {
        if ($file->isFile()) {
            $size += $file->getSize();
        }
    }
    return $size;
}

function formatSize(int $bytes): string
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    while ($bytes >= 1024 && $i < count($units) - 1) {
        $bytes /= 1024;
        $i++;
    }
    return round($bytes, 2) . ' ' . $units[$i];
}

function scanContentDir(string $fullPath, string $subdir): array
{
    $items = [];
    if (!is_dir($fullPath)) return $items;
    
    $files = scandir($fullPath);
    foreach ($files as $file) {
        if ($file === '.' || $file === '..' || str_starts_with($file, '.')) {
            continue;
        }
        
        $path = $fullPath . '/' . $file;
        $isDir = is_dir($path);
        
        $items[] = [
            'name' => $file,
            'path' => $subdir ? $subdir . '/' . $file : $file,
            'is_dir' => $isDir,
            'extension' => $isDir ? '' : strtolower(pathinfo($file, PATHINFO_EXTENSION)),
            'size' => $isDir ? '-' : formatSize(filesize($path)),
            'modified' => date('Y-m-d H:i', filemtime($path)),
        ];
    }
    
    // Sort: directories first, then files, alphabetically
    usort($items, function($a, $b) {
        if ($a['is_dir'] && !$b['is_dir']) return -1;
        if (!$a['is_dir'] && $b['is_dir']) return 1;
        return strcasecmp($a['name'], $b['name']);
    });
    
    return $items;
}

/**
 * Recursively collect all content pages (.md/.php/.html) as pageKey => label pairs.
 * Language-prefixed files (nl./en.) are stripped of their prefix for the key.
 * Directories are included if they contain content (represented by their path).
 *
 * @param string $contentDir Absolute path to the content directory
 * @return array<string,string> Sorted list of [pageKey => displayLabel]
 */
function collectContentPages(string $contentDir): array
{
    $pages = [];
    $realBase = realpath($contentDir);
    if (!$realBase || !is_dir($realBase)) {
        return $pages;
    }

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($realBase, RecursiveDirectoryIterator::SKIP_DOTS)
    );

    $langRegex = '/^(nl|en|de|fr)\./';

    foreach ($iterator as $fileInfo) {
        if (!$fileInfo->isFile()) {
            continue;
        }
        $ext = strtolower($fileInfo->getExtension());
        if (!in_array($ext, ['md', 'php', 'html'], true)) {
            continue;
        }

        $relative = substr($fileInfo->getRealPath(), strlen($realBase) + 1);
        $relative = str_replace('\\', '/', $relative);

        // Skip hidden / dash-prefixed segments (private assets etc.)
        $skip = false;
        foreach (explode('/', $relative) as $segment) {
            if ($segment !== '' && ($segment[0] === '.' || $segment[0] === '-')) {
                $skip = true;
                break;
            }
        }
        if ($skip) {
            continue;
        }

        // Strip extension
        $key = preg_replace('/\.(md|php|html)$/i', '', $relative);

        // Strip language prefix from the filename component
        $dirPart = dirname($key);
        $dirPart = ($dirPart === '.' || $dirPart === '') ? '' : $dirPart . '/';
        $filePart = basename($key);
        if (preg_match($langRegex, $filePart, $m)) {
            $filePart = substr($filePart, strlen($m[1]) + 1);
        }
        $key = $dirPart . $filePart;

        // folder/index -> folder
        if (str_ends_with($key, '/index')) {
            $key = substr($key, 0, -6);
        }

        if ($key === '') {
            $key = 'index';
        }

        $label = ucfirst(str_replace(['-', '/'], [' ', ' / '], $key));
        $pages[$key] = $label;
    }

    ksort($pages);
    return $pages;
}

function updateContentFrontmatter(string $content, string $key, string $value): string
{
    if (preg_match('/^---\s*\n(.+?)\n---\s*\n(.*)$/s', $content, $m)) {
        $frontmatter = $m[1];
        $body = $m[2];
        
        $lines = explode("\n", $frontmatter);
        $found = false;
        foreach ($lines as $i => $line) {
            if (str_starts_with($line, $key . ':')) {
                $lines[$i] = $key . ': ' . $value;
                $found = true;
                break;
            }
        }
        
        if (!$found) {
            $lines[] = $key . ': ' . $value;
        }
        
        return "---\n" . implode("\n", $lines) . "\n---\n" . $body;
    }
    
    return "---\n" . $key . ': ' . $value . "\n---\n\n" . $content;
}

function extractFrontmatterValue(string $content, string $key): string
{
    if (preg_match('/^---\s*\n(.+?)\n---/s', $content, $m)) {
        $frontmatter = $m[1];
        if (preg_match('/^' . preg_quote($key, '/') . ': (.+?)$/m', $frontmatter, $mm)) {
            return trim($mm[1]);
        }
    }
    return '';
}

function extractLanguagePrefix(string $file): string
{
    $parts = explode('/', $file);
    $filename = end($parts);
    if (preg_match('/^(nl|en|de|fr|es)\./', $filename, $m)) {
        return $m[1];
    }
    return 'nl';
}

function backupContentFile(string $filePath): void
{
    $backupDir = dirname($filePath) . '/.bak';
    if (!is_dir($backupDir)) {
        @mkdir($backupDir, 0755, true);
    }
    $backupFile = $backupDir . '/' . basename($filePath) . '.' . date('YmdHis');
    @copy($filePath, $backupFile);
}

