<?php

/**
 * JCOGS Image Pro - Cache AJAX Endpoints Action Handler
 * =====================================================
 * Handles AJAX requests for cache statistics and locations
 * 
 * @category   ExpressionEngine Add-on
 * @package    JCOGS Image Pro
 * @author     JCOGS Design <contact@jcogs.net>
 * @copyright  Copyright (c) 2021 - 2025 JCOGS Design
 * @license    https://jcogs.net/add-ons/license/jcogs_img_pro
 * @version    2.0.6
 * @link       https://JCOGS.net/
 * @since      Issue #71 - Async Cache Loading
 */

namespace JCOGSDesign\JCOGSImagePro\Actions;

use Exception;
use JCOGSDesign\JCOGSImagePro\Service\ServiceCache;

/**
 * Cache AJAX Endpoints Action Handler
 * 
 * Provides AJAX endpoints for asynchronous loading of cache data.
 * Used to improve page load performance by deferring expensive
 * cache statistics and location calculations.
 * 
 * Methods:
 * - cache_statistics: Returns cache stats as JSON
 * - cache_locations: Returns cache locations table HTML as JSON
 */
class CacheAjaxEndpoints extends ImageAbstractAction
{
    /**
     * Cache key for statistics (Issue #95 - shared for invalidation)
     */
    private const CACHE_STATS_KEY = '/jcogs_img_pro/cache_stats_v2';
    /**
     * Main action entry point (required by ImageAbstractAction)
     * Routes to specific methods based on 'method' parameter.
     * 
     * @return string Empty string (JSON sent via headers)
     */
    public function process(): string
    {
        // Get the method parameter from the request
        $method = ee()->input->get('method') ?: ee()->input->post('method');
        
        // Route to the appropriate method
        switch ($method) {
            case 'cache_statistics':
                $this->cache_statistics();
                break;
            case 'cache_locations':
                $this->cache_locations();
                break;
            case 'connection_management':
                $this->connection_management();
                break;
            case 'benchmark_threshold':
                $this->benchmark_threshold();
                break;
            default:
                $this->_sendJsonResponse([
                    'success' => false,
                    'error' => 'Invalid method specified'
                ]);
        }
        
        return '';
    }

    /**
     * Get cache statistics as JSON
     * 
     * Returns cache statistics data for async loading. Uses caching to prevent
     * repeated expensive operations on remote storage.
     * 
     * This method is called via EE Action URL:
     * /?ACT=xxx&method=cache_statistics
     * 
     * Query Parameters:
     * - force_refresh: If '1' or 'true', bypass cache and recalculate (Issue #95)
     * 
     * @return void Outputs JSON and exits
     */
    public function cache_statistics(): void
    {
        try {
            // Check if force refresh requested (Issue #95)
            $force_refresh = ee()->input->get('force_refresh') === '1' || 
                           ee()->input->get('force_refresh') === 'true';
            
            // Use the Action's own method to get cached statistics
            $cache_stats = $this->_getCachedStatistics($force_refresh);
            
            $this->_sendJsonResponse([
                'success' => true,
                'data' => $cache_stats
            ]);
            
        } catch (Exception $e) {
            $this->_sendJsonResponse([
                'success' => false,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Get cache locations table HTML as JSON
     * 
     * Returns the cache locations table HTML for async loading.
     * 
     * This method is called via EE Action URL:
     * /?ACT=xxx&method=cache_locations
     * 
     * @return void Outputs JSON and exits
     */
    public function cache_locations(): void
    {
        try {
            // Use the Action's own method to generate table HTML
            $html = $this->_getCacheLocationsTableHtml();
            
            $this->_sendJsonResponse([
                'success' => true,
                'html' => $html
            ]);
            
        } catch (Exception $e) {
            $this->_sendJsonResponse([
                'success' => false,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Get connection management table HTML as JSON
     * 
     * Returns the connection management table HTML for async loading.
     * 
     * This method is called via EE Action URL:
     * /?ACT=xxx&method=connection_management
     * 
     * @return void Outputs JSON and exits
     */
    public function connection_management(): void
    {
        try {
            // Generate the connection management table HTML
            $html = $this->_getConnectionManagementTableHtml();
            
            $this->_sendJsonResponse([
                'success' => true,
                'html' => $html
            ]);
            
        } catch (Exception $e) {
            $this->_sendJsonResponse([
                'success' => false,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Run cache threshold benchmark and return results
     * 
     * Benchmarks cache performance to determine optimal preload threshold
     * setting. Returns timing data, analysis, and recommendations.
     * 
     * This method is called via EE Action URL:
     * /?ACT=xxx&method=benchmark_threshold
     * 
     * @return void Outputs JSON and exits
     */
    public function benchmark_threshold(): void
    {
        try {
            // Load language file for translations
            ee()->lang->loadfile('jcogs_img_pro');
            
            $cache_service = ServiceCache::cache();
            
            // Get optional parameters
            $connection_name = ee()->input->get('connection') ?: null;
            $sample_size = (int)(ee()->input->get('sample_size') ?: 100);
            $test_runs = (int)(ee()->input->get('test_runs') ?: 3);
            
            // Validate parameters
            $sample_size = max(10, min(500, $sample_size)); // Clamp 10-500
            $test_runs = max(1, min(5, $test_runs)); // Clamp 1-5
            
            // Run the benchmark
            $results = $cache_service->benchmark_cache_threshold(
                $connection_name,
                $sample_size,
                $test_runs
            );
            
            if (!$results['success']) {
                $this->_sendJsonResponse($results);
                return;
            }
            
            // Generate HTML results for display
            $html = $this->_generateBenchmarkResultsHtml($results);
            
            $this->_sendJsonResponse([
                'success' => true,
                'results' => $results,
                'html' => $html
            ]);
            
        } catch (Exception $e) {
            $this->_sendJsonResponse([
                'success' => false,
                'error' => 'Benchmark failed: ' . $e->getMessage()
            ]);
        }
    }

    /**
     * Calculate cache statistics across all connections
     * 
     * @return array Cache statistics data
     */
    private function _calculateCacheStatistics(): array
    {
        try {
            $named_connections = $this->settings_service->getNamedFilesystemAdapters()['connections'] ?? [];
            $total_files = 0;
            $total_size = 0;
            $location_stats = [];
            $errors = [];

            // If no connections are configured, return minimal stats
            if (empty($named_connections)) {
                return [
                    'total_files' => 0,
                    'total_size' => 0,
                    'total_size_formatted' => '0 B',
                    'connection_count' => 0,
                    'connections' => [],
                    'errors' => ['No named connections configured.']
                ];
            }

            foreach ($named_connections as $connection_name => $config) {
                try {
                    $adapter_type = $config['type'] ?? 'local';
                    
                    // Get cache directory for operations
                    $cache_dir_operational = $this->filesystem_service->get_adapter_cache_path($connection_name, true);
                    
                    // Audit cache
                    $adapter_info = $this->filesystem_service->audit_adapter_cache($connection_name, $cache_dir_operational);
                    
                    // Skip cache locations with no files
                    if (($adapter_info['file_count'] ?? 0) == 0) {
                        continue;
                    }
                    
                    // Add to location stats (for counting active connections)
                    $location_stats[] = [
                        'connection_name' => $connection_name,
                        'adapter_type' => $adapter_type,
                        'file_count' => $adapter_info['file_count'] ?? 0,
                        'total_size' => $adapter_info['total_size'] ?? 0
                    ];
                    
                    $total_files += $adapter_info['file_count'] ?? 0;
                    $total_size += $adapter_info['total_size'] ?? 0;

                } catch (Exception $e) {
                    $errors[] = "Connection '{$connection_name}': " . $e->getMessage();
                }
            }

            // Count active connections (connections that actually have cached files)
            $active_connection_count = count($location_stats);
            
            return [
                'total_files' => $total_files,
                'total_size' => $total_size,
                'total_size_formatted' => $this->utilities_service->format_file_size($total_size),
                'connection_count' => $active_connection_count,
                'connections' => $location_stats,
                'errors' => $errors
            ];
            
        } catch (Exception $e) {
            return [
                'total_files' => 0,
                'total_size' => 0,
                'total_size_formatted' => '0 B',
                'connection_count' => 0,
                'connections' => [],
                'errors' => ['System error: ' . $e->getMessage()]
            ];
        }
    }

    /**
     * Format last updated date from adapter info
     * 
     * @param array $adapter_info Adapter information
     * @return string Formatted date or 'Never'
     */
    private function _formatLastUpdated(array $adapter_info): string
    {
        if (!isset($adapter_info['last_modified']) || $adapter_info['last_modified'] === null) {
            return 'Never';
        }
        
        $timestamp = $adapter_info['last_modified'];
        
        if (is_numeric($timestamp)) {
            return date('Y-m-d H:i:s', (int)$timestamp);
        } elseif (is_string($timestamp)) {
            $parsed = strtotime($timestamp);
            if ($parsed !== false) {
                return date('Y-m-d H:i:s', $parsed);
            }
        }
        
        return 'Never';
    }

    /**
     * Generate HTML for benchmark results display
     * 
     * @param array $results Benchmark results from cache service
     * @return string HTML formatted results
     */
    private function _generateBenchmarkResultsHtml(array $results): string
    {
        $current = $results['current_state'];
        $benchmark = $results['benchmark_results'];
        $analysis = $results['analysis'];
        
        $html = '<div class="benchmark-results">';
        
        // Current State Section
        $html .= '<div class="benchmark-section">';
        $html .= '<h3>' . lang('jcogs_img_pro_benchmark_current_state') . '</h3>';
        $html .= '<table class="benchmark-table">';
        $html .= '<tr><th>' . lang('jcogs_img_pro_cache_entries') . ':</th><td>' . number_format($current['cache_entry_count']) . '</td></tr>';
        $html .= '<tr><th>' . lang('jcogs_img_pro_current_threshold') . ':</th><td>' . number_format($current['current_threshold']) . '</td></tr>';
        $html .= '<tr><th>' . lang('jcogs_img_pro_estimated_memory') . ':</th><td>' . $current['estimated_memory_mb'] . ' MB</td></tr>';
        
        // Enhanced preload status with meaningful explanation
        $preload_explanation = $current['would_preload'] 
            ? 'Full cache preload - entire cache log loaded into memory for fastest queries'
            : 'Per-entry database queries - cache entries retrieved individually as needed';
        $html .= '<tr><th>' . lang('jcogs_img_pro_preload_status') . ':</th><td><strong>' . ($current['would_preload'] ? 'Preload Enabled' : 'On-Demand Loading') . '</strong><br><small style="color: #64748b;">' . $preload_explanation . '</small></td></tr>';
        $html .= '</table>';
        $html .= '</div>';
        
        // Benchmark Results Section
        $html .= '<div class="benchmark-section">';
        $html .= '<h3>' . lang('jcogs_img_pro_benchmark_performance') . '</h3>';
        $html .= '<table class="benchmark-table">';
        $html .= '<tr><th>' . lang('jcogs_img_pro_test_runs') . ':</th><td>' . $benchmark['test_runs'] . '</td></tr>';
        $html .= '<tr><th>' . lang('jcogs_img_pro_sample_size') . ':</th><td>' . $benchmark['sample_size'] . ' queries</td></tr>';
        $html .= '<tr><th colspan="2" style="background: #f7f7f7; padding: 8px;">' . lang('jcogs_img_pro_preload_timing') . '</th></tr>';
        $html .= '<tr><td style="padding-left: 20px;">' . lang('jcogs_img_pro_average') . ':</td><td><strong>' . $benchmark['preload']['avg_time_ms'] . ' ms</strong></td></tr>';
        $html .= '<tr><td style="padding-left: 20px;">' . lang('jcogs_img_pro_range') . ':</td><td>' . $benchmark['preload']['min_time_ms'] . ' - ' . $benchmark['preload']['max_time_ms'] . ' ms</td></tr>';
        $html .= '<tr><th colspan="2" style="background: #f7f7f7; padding: 8px;">' . lang('jcogs_img_pro_on_demand_timing') . '</th></tr>';
        $html .= '<tr><td style="padding-left: 20px;">' . lang('jcogs_img_pro_per_query') . ':</td><td><strong>' . $benchmark['on_demand']['avg_per_query_ms'] . ' ms</strong></td></tr>';
        $html .= '<tr><td style="padding-left: 20px;">' . lang('jcogs_img_pro_total_sample') . ':</td><td>' . $benchmark['on_demand']['total_avg_time_ms'] . ' ms</td></tr>';
        $html .= '</table>';
        $html .= '</div>';
        
        // Analysis & Recommendation Section
        $html .= '<div class="benchmark-section recommendation">';
        $html .= '<h3>' . lang('jcogs_img_pro_recommendation') . '</h3>';
        $html .= '<div class="recommendation-box">';
        $html .= '<p><strong>' . lang('jcogs_img_pro_break_even_point') . ':</strong> ' . number_format($analysis['break_even_point']) . ' queries</p>';
        
        // Enhanced recommendation with explanation
        $recommended_explanation = ($analysis['recommended_threshold'] > $current['cache_entry_count']) 
            ? 'Cache preload recommended - your cache size makes full preload more efficient than individual queries'
            : 'Per-entry queries recommended - your cache is large enough that individual database queries will be faster than preloading';
        $html .= '<p><strong>' . lang('jcogs_img_pro_recommended_threshold') . ':</strong> <span class="recommended-value">' . number_format($analysis['recommended_threshold']) . '</span></p>';
        $html .= '<p class="recommendation-explanation"><em>' . $recommended_explanation . '</em></p>';
        $html .= '<p class="recommendation-text">' . htmlspecialchars($analysis['recommendation']) . '</p>';
        $html .= '</div>';
        
        // Apply button
        $html .= '<div class="benchmark-actions" style="margin-top: 15px;">';
        $html .= '<button type="button" class="btn action" onclick="applyRecommendedThreshold(' . $analysis['recommended_threshold'] . ')">';
        $html .= lang('jcogs_img_pro_apply_recommendation');
        $html .= '</button>';
        $html .= '</div>';
        
        $html .= '</div>'; // recommendation
        $html .= '</div>'; // benchmark-results
        
        return $html;
    }

    /**
     * Generate cache locations table HTML
     * 
     * @return string HTML table for cache locations
     */
    private function _getCacheLocationsTableHtml(): string
    {
        try {
            // Load language file for translations
            ee()->lang->loadfile('jcogs_img_pro');
            
            $named_connections = $this->settings_service->getNamedFilesystemAdapters()['connections'] ?? [];
            
            if (empty($named_connections)) {
                return '<div class="no-results"><p>No cache locations found.</p></div>';
            }

            // Build simple HTML table without using CP/Table
            $html = '<style>
                .cache-action-audit { border-color: #3b82f6; border-style: solid; border-width: thin; color: #3b82f6; background-color: #fff; }
                .cache-action-audit:hover { background-color: #3b82f6; color: #fff; }
                .cache-action-clear { border-color: #f97316; border-style: solid; border-width: thin; color: #f97316; background-color: #fff; }
                .cache-action-clear:hover { background-color: #f97316; color: #fff; }
            </style>';
            $html .= '<table class="table table-striped cache-locations-table" style="width:100%;">';
            $html .= '<thead><tr>';
            $html .= '<th>Cache Location</th>';
            $html .= '<th style="text-align:center;">Connection</th>';
            $html .= '<th style="text-align:center;">Files</th>';
            $html .= '<th style="text-align:center;">Size</th>';
            $html .= '<th style="text-align:center;">Status</th>';
            $html .= '<th style="text-align:center;">Last Updated</th>';
            $html .= '<th style="text-align:center;">Actions</th>';
            $html .= '</tr></thead><tbody>';

            foreach ($named_connections as $connection_name => $config) {
                try {
                    $adapter_type = $config['type'] ?? 'local';
                    $is_valid = $config['is_valid'] ?? false;
                    
                    // Get cache directory
                    $cache_dir_operational = $this->filesystem_service->get_adapter_cache_path($connection_name, true);
                    $cache_dir_display = $this->_getConnectionCachePath($config);
                    
                    // Audit cache
                    $adapter_info = $this->filesystem_service->audit_adapter_cache($connection_name, $cache_dir_operational);
                    
                    // Skip if no files
                    if (($adapter_info['file_count'] ?? 0) == 0) {
                        continue;
                    }
                    
                    $file_count = $adapter_info['file_count'] ?? 0;
                    $size_formatted = $this->utilities_service->format_file_size($adapter_info['total_size'] ?? 0);
                    $last_updated = $this->_formatLastUpdated($adapter_info);
                    
                    // Build row
                    $html .= '<tr>';
                    $html .= '<td><strong>' . htmlspecialchars($cache_dir_display, ENT_QUOTES, 'UTF-8') . '</strong></td>';
                    $html .= '<td style="text-align:center;"><span class="st-' . strtolower($adapter_type) . '">' . htmlspecialchars($connection_name, ENT_QUOTES, 'UTF-8') . '</span></td>';
                    $html .= '<td style="text-align:center;">' . number_format($file_count) . '</td>';
                    $html .= '<td style="text-align:center;">' . htmlspecialchars($size_formatted, ENT_QUOTES, 'UTF-8') . '</td>';
                    
                    // Status badge
                    if ($is_valid) {
                        $html .= '<td style="text-align:center;"><span style="background-color: #f0fdf4; color: #16a34a; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600;">Valid</span></td>';
                    } else {
                        $html .= '<td style="text-align:center;"><span style="background-color: #fef2f2; color: #dc2626; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600;">Invalid</span></td>';
                    }
                    
                    $html .= '<td style="text-align:center;">' . htmlspecialchars($last_updated, ENT_QUOTES, 'UTF-8') . '</td>';
                    
                    // Action buttons
                    $html .= '<td style="text-align:center;">';
                    if ($file_count > 0 && $is_valid) {
                        // Use explicit CP URL to ensure correct entry point in AJAX context
                        $cp_url = ee()->config->item('cp_url');
                        $audit_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/audit_cache/' . urlencode($connection_name), [], $cp_url);
                        $clear_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/clear_cache/' . urlencode($connection_name), [], $cp_url);
                        
                        // Get translated labels and tooltips
                        $audit_label = lang('jcogs_img_cp_cache_audit_text');
                        $clear_label = lang('jcogs_img_cp_cache_clear');
                        $audit_tooltip = lang('jcogs_img_cp_cache_location_audit');
                        $clear_tooltip = lang('jcogs_img_cp_cache_location_clear');
                        
                        // Audit button with broom icon, text label and blue styling with hover
                        $html .= '<a href="' . $audit_url . '" class="button button--small cache-action-audit" title="' . htmlspecialchars($audit_tooltip, ENT_QUOTES, 'UTF-8') . '"><i class="fas fa-broom"></i> ' . htmlspecialchars($audit_label, ENT_QUOTES, 'UTF-8') . '</a> ';
                        
                        // Clear cache button with eraser icon, text label and orange styling with hover
                        $html .= '<a href="' . $clear_url . '" class="button button--small cache-action-clear" title="' . htmlspecialchars($clear_tooltip, ENT_QUOTES, 'UTF-8') . '" onclick="return confirm(\'Clear all cache files from &quot;' . htmlspecialchars($connection_name, ENT_QUOTES, 'UTF-8') . '&quot;?\');"><i class="fas fa-eraser"></i> ' . htmlspecialchars($clear_label, ENT_QUOTES, 'UTF-8') . '</a>';
                    }
                    $html .= '</td>';
                    $html .= '</tr>';

                } catch (Exception $e) {
                    // Skip problematic connections silently
                    continue;
                }
            }

            $html .= '</tbody></table>';
            
            return $html;
            
        } catch (Exception $e) {
            return '<div class="cache-error-message"><strong>Error building cache locations table:</strong><br>' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
        }
    }

    /**
     * Get cached statistics or calculate and cache them
     * 
     * Implements short-term caching (60 seconds) to prevent repeated expensive
     * operations when loading the page or widgets.
     * 
     * @param bool $force_refresh If true, bypass cache and recalculate (Issue #95)
     * @return array Cache statistics
     */
    private function _getCachedStatistics(bool $force_refresh = false): array
    {
        $cache_ttl = 60; // seconds
        
        // If force refresh requested, skip cache lookup (Issue #95)
        if (!$force_refresh) {
            // Try to get from EE cache
            $cached = ee()->cache->get(self::CACHE_STATS_KEY);
            if ($cached !== false) {
                return $cached;
            }
        }
        
        // Calculate fresh stats using cache service
        $stats = $this->_calculateCacheStatistics();
        
        // Store in cache
        ee()->cache->save(self::CACHE_STATS_KEY, $stats, $cache_ttl);
        
        return $stats;
    }

    /**
     * Get the cache path for a connection (for display)
     * 
     * @param array $connection Connection configuration
     * @return string Cache path
     */
    private function _getConnectionCachePath(array $connection): string
    {
        $config = $connection['config'] ?? [];
        $type = $connection['type'] ?? 'unknown';
        
        switch ($type) {
            case 'local':
                return $config['cache_directory'] ?? '/images/jcogs_img_pro/cache';
            case 's3':
            case 'r2':
                $bucket = $config['bucket'] ?? 'bucket';
                $path = $config['server_path'] ?? '';
                return $bucket . (!empty($path) ? '/' . trim($path, '/') : '');
            case 'dospaces':
                $space = $config['space'] ?? 'space';
                $path = $config['server_path'] ?? '';
                return $space . (!empty($path) ? '/' . trim($path, '/') : '');
            default:
                return 'Unknown';
        }
    }

    /**
     * Generate connection management table HTML
     * 
     * @return string HTML table for connection management
     */
    private function _getConnectionManagementTableHtml(): string
    {
        try {
            $named_adapters_config = $this->settings_service->getNamedFilesystemAdapters();
            $named_connections = $named_adapters_config['connections'] ?? [];
            
            if (empty($named_connections)) {
                $cp_url = ee()->config->item('cp_url');
                return '<div class="no-results"><p>No connections configured. <a href="' . ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/add_connection', [], $cp_url) . '" class="button button--small button--primary">Add New Connection</a></p></div>';
            }

            // Count local connections (for delete validation)
            $local_connection_count = 0;
            foreach ($named_connections as $config) {
                if (($config['type'] ?? '') === 'local') {
                    $local_connection_count++;
                }
            }

            // Get CP URL once for all URL generation in AJAX context
            $cp_url = ee()->config->item('cp_url');

            // Build HTML table
            $html = '<table class="table table-striped connections-table" style="width:100%;">';
            $html .= '<thead><tr>';
            $html .= '<th>Connection Name</th>';
            $html .= '<th style="text-align:center;">Type</th>';
            $html .= '<th>Cache Path</th>';
            $html .= '<th style="text-align:center;">Status</th>';
            $html .= '<th style="text-align:center;">Actions</th>';
            $html .= '</tr></thead><tbody>';

            foreach ($named_connections as $name => $connection) {
                $config = $connection['config'] ?? [];
                $type = $connection['type'] ?? 'unknown';
                $is_valid = $connection['is_valid'] ?? false;
                $is_local = ($type === 'local');
                
                // Get cache path
                $cache_path = $this->_getConnectionCachePath($connection);
                
                // Build name column
                $name_html = '<strong>' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '</strong>';
                
                // Build type column
                $type_display = match($type) {
                    'local' => 'Local',
                    's3' => 'AWS S3',
                    'r2' => 'Cloudflare R2',
                    'dospaces' => 'DigitalOcean Spaces',
                    default => strtoupper($type)
                };
                $type_html = '<span class="st-' . strtolower($type) . '">' . htmlspecialchars($type_display, ENT_QUOTES, 'UTF-8') . '</span>';
                
                // Build status column
                if ($is_valid) {
                    $status_html = '<span style="background-color: #f0fdf4; color: #16a34a; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600;">Valid</span>';
                } else {
                    $status_html = '<span style="background-color: #fef2f2; color: #dc2626; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600;">Invalid</span>';
                }
                
                // Build actions column
                $edit_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/edit_connection/' . urlencode($name), [], $cp_url);
                $clone_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/clone_connection/' . urlencode($name), [], $cp_url);
                $test_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/test_connection/' . urlencode($name), [], $cp_url);
                $delete_url = ee('CP/URL')->make('addons/settings/jcogs_img_pro/caching/delete_connection/' . urlencode($name), [], $cp_url);
                
                $actions_html = '<div style="display: flex; gap: 0.5rem; justify-content: center; align-items: center;">';
                $actions_html .= '<a href="' . $edit_url . '" title="Edit Connection" class="button button--small"><i class="fas fa-edit"></i> Edit</a>';
                $actions_html .= '<a href="' . $clone_url . '" title="Clone Connection" class="button button--small"><i class="fas fa-clone"></i> Clone</a>';
                $actions_html .= '<a href="' . $test_url . '" title="Test Connection" class="button button--small"><i class="fas fa-plug"></i> Test</a>';
                
                // Only allow delete if not the last local connection
                if (!$is_local || $local_connection_count > 1) {
                    $actions_html .= '<a href="' . $delete_url . '" title="Delete Connection" class="button button--small button--danger" onclick="return confirm(\'Delete connection &quot;' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '&quot;?\')"><i class="fas fa-trash-alt"></i> Delete</a>';
                } else {
                    $actions_html .= '<span title="Cannot delete the last local connection" style="display: inline-block;"><button type="button" class="button button--small button--danger" disabled style="opacity: 0.5; cursor: not-allowed;"><i class="fas fa-trash-alt"></i> Delete</button></span>';
                }
                $actions_html .= '</div>';
                
                // Build row
                $html .= '<tr>';
                $html .= '<td>' . $name_html . '</td>';
                $html .= '<td style="text-align:center;">' . $type_html . '</td>';
                $html .= '<td>' . htmlspecialchars($cache_path, ENT_QUOTES, 'UTF-8') . '</td>';
                $html .= '<td style="text-align:center;">' . $status_html . '</td>';
                $html .= '<td style="text-align:center;">' . $actions_html . '</td>';
                $html .= '</tr>';
            }

            $html .= '</tbody></table>';
            
            return $html;
            
        } catch (Exception $e) {
            return '<div class="cache-error-message"><strong>Error building connection management table:</strong><br>' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '</div>';
        }
    }

    /**
     * Invalidate cached statistics (Issue #95)
     * 
     * Call this after cache operations (clear, delete, audit) to force
     * recalculation on next statistics request.
     * 
     * @return void
     */
    public static function invalidateCacheStatistics(): void
    {
        ee()->cache->delete(self::CACHE_STATS_KEY);
    }

    /**
     * Send JSON response with proper headers and exit
     * 
     * @param array $data Data to encode as JSON
     * @return void Exits after sending response
     */
    private function _sendJsonResponse(array $data): void
    {
        // Set JSON header
        header('Content-Type: application/json; charset=utf-8');
        
        // Encode and output JSON
        $json = json_encode($data);
        header('Content-Length: ' . strlen($json));
        
        echo $json;
        exit();
    }
}
