<?php

declare(strict_types=1);

namespace App\Framework\Mcp\Core\Services;

use App\Framework\Mcp\Core\ValueObjects\OptimizationResult;
use App\Framework\Mcp\Core\ValueObjects\OptimizationStrategy;
use App\Framework\Mcp\Core\ValueObjects\OutputFormat;

/**
 * Result Optimizer
 *
 * Advanced result optimization system for MCP tools including
 * compression, format optimization, size reduction, and
 * intelligent content filtering based on relevance.
 */
final readonly class ResultOptimizer
{
    private const MAX_ARRAY_SIZE = 1000;
    private const MAX_STRING_LENGTH = 10000;
    private const MAX_DEPTH = 10;
    private const COMPRESSION_THRESHOLD = 1024; // 1KB

    public function __construct(
        private array $optimizationConfig = []
    ) {
    }

    /**
     * Optimize result based on strategy and format
     */
    public function optimize(
        mixed $result,
        OutputFormat $format,
        OptimizationStrategy $strategy = OptimizationStrategy::BALANCED
    ): OptimizationResult {
        $originalSize = $this->calculateSize($result);
        $optimized = $result;

        // Apply optimization strategies
        $optimized = match ($strategy) {
            OptimizationStrategy::NONE => $result,
            OptimizationStrategy::SIZE => $this->optimizeForSize($optimized),
            OptimizationStrategy::SPEED => $this->optimizeForSpeed($optimized),
            OptimizationStrategy::BALANCED => $this->optimizeBalanced($optimized),
            OptimizationStrategy::AGGRESSIVE => $this->optimizeAggressive($optimized)
        };

        // Apply format-specific optimizations
        $optimized = $this->optimizeForFormat($optimized, $format);

        $optimizedSize = $this->calculateSize($optimized);
        $compressionRatio = $originalSize > 0 ? $optimizedSize / $originalSize : 1.0;

        return new OptimizationResult(
            originalData: $result,
            optimizedData: $optimized,
            originalSize: $originalSize,
            optimizedSize: $optimizedSize,
            compressionRatio: $compressionRatio,
            strategy: $strategy,
            format: $format,
            optimizations: $this->getAppliedOptimizations($result, $optimized),
            metadata: $this->generateMetadata($result, $optimized, $strategy)
        );
    }

    /**
     * Compress data if it exceeds threshold
     */
    public function compress(mixed $data, int $level = 6): array|string
    {
        $serialized = serialize($data);

        if (strlen($serialized) < self::COMPRESSION_THRESHOLD) {
            return $data;
        }

        $compressed = gzcompress($serialized, $level);

        if ($compressed !== false && strlen($compressed) < strlen($serialized)) {
            return [
                '_compressed' => true,
                '_algorithm' => 'gzip',
                '_level' => $level,
                '_data' => base64_encode($compressed),
                '_original_size' => strlen($serialized),
                '_compressed_size' => strlen($compressed),
                '_ratio' => strlen($compressed) / strlen($serialized),
            ];
        }

        return $data;
    }

    /**
     * Decompress compressed data
     */
    public function decompress(mixed $data): mixed
    {
        if (! is_array($data) || ! isset($data['_compressed']) || $data['_compressed'] !== true) {
            return $data;
        }

        $compressed = base64_decode($data['_data']);
        $decompressed = gzuncompress($compressed);

        if ($decompressed !== false) {
            return unserialize($decompressed);
        }

        return $data;
    }

    /**
     * Remove redundant or less important data
     */
    public function filterRelevantData(array $data, array $priorities = []): array
    {
        if (empty($priorities)) {
            $priorities = $this->getDefaultPriorities();
        }

        $filtered = [];

        foreach ($data as $key => $value) {
            $priority = $priorities[$key] ?? $this->calculateKeyPriority($key, $value);

            if ($priority >= 0.5) { // Keep high priority items
                if (is_array($value)) {
                    $filtered[$key] = $this->filterArrayByRelevance($value, $priority);
                } else {
                    $filtered[$key] = $this->filterValueByRelevance($value, $priority);
                }
            }
        }

        return $filtered;
    }

    /**
     * Truncate large data structures
     */
    public function truncateData(mixed $data, array $limits = []): mixed
    {
        $defaultLimits = [
            'max_array_size' => self::MAX_ARRAY_SIZE,
            'max_string_length' => self::MAX_STRING_LENGTH,
            'max_depth' => self::MAX_DEPTH,
        ];

        $limits = array_merge($defaultLimits, $limits);

        return $this->truncateRecursive($data, $limits, 0);
    }

    /**
     * Optimize for specific output format
     */
    private function optimizeForFormat(mixed $data, OutputFormat $format): mixed
    {
        return match ($format) {
            OutputFormat::JSON => $this->optimizeForJson($data),
            OutputFormat::TABLE => $this->optimizeForTable($data),
            OutputFormat::TREE => $this->optimizeForTree($data),
            OutputFormat::TEXT => $this->optimizeForText($data),
            OutputFormat::MERMAID => $this->optimizeForMermaid($data),
            OutputFormat::PLANTUML => $this->optimizeForPlantUML($data),
            default => $data
        };
    }

    private function optimizeForSize(mixed $data): mixed
    {
        if (is_array($data)) {
            // Remove null values and empty arrays
            $data = array_filter($data, fn ($value) => $value !== null && $value !== []);

            // Limit array size
            if (count($data) > self::MAX_ARRAY_SIZE) {
                $data = array_slice($data, 0, self::MAX_ARRAY_SIZE, true);
                $data['_truncated'] = true;
                $data['_original_count'] = count($data) + 1;
            }

            // Recursively optimize nested arrays
            return array_map([$this, 'optimizeForSize'], $data);
        }

        if (is_string($data) && strlen($data) > self::MAX_STRING_LENGTH) {
            return substr($data, 0, self::MAX_STRING_LENGTH) . '... [truncated]';
        }

        return $data;
    }

    private function optimizeForSpeed(mixed $data): mixed
    {
        // For speed optimization, we primarily focus on reducing processing overhead
        if (is_array($data)) {
            // Remove complex nested structures that take time to process
            $simplified = [];

            foreach ($data as $key => $value) {
                if (is_scalar($value) || (is_array($value) && count($value) < 10)) {
                    $simplified[$key] = $value;
                } else {
                    $simplified[$key] = '[Complex data - ' . gettype($value) . ']';
                }
            }

            return $simplified;
        }

        return $data;
    }

    private function optimizeBalanced(mixed $data): mixed
    {
        // Apply both size and speed optimizations with moderate limits
        $sizeOptimized = $this->optimizeForSize($data);

        return $this->truncateData($sizeOptimized, [
            'max_array_size' => (int) (self::MAX_ARRAY_SIZE * 0.8),
            'max_string_length' => (int) (self::MAX_STRING_LENGTH * 0.8),
            'max_depth' => self::MAX_DEPTH - 2,
        ]);
    }

    private function optimizeAggressive(mixed $data): mixed
    {
        // Aggressive optimization - significant data reduction
        $optimized = $this->optimizeForSize($data);
        $optimized = $this->optimizeForSpeed($optimized);

        return $this->truncateData($optimized, [
            'max_array_size' => (int) (self::MAX_ARRAY_SIZE * 0.5),
            'max_string_length' => (int) (self::MAX_STRING_LENGTH * 0.5),
            'max_depth' => (int) (self::MAX_DEPTH * 0.6),
        ]);
    }

    private function optimizeForJson(mixed $data): mixed
    {
        // JSON-specific optimizations
        if (is_array($data)) {
            // Ensure JSON compatibility
            return array_map(function ($value) {
                if (is_resource($value)) {
                    return '[Resource]';
                }
                if (is_object($value) && ! method_exists($value, '__toString')) {
                    return '[Object: ' . get_class($value) . ']';
                }

                return $value;
            }, $data);
        }

        return $data;
    }

    private function optimizeForTable(mixed $data): mixed
    {
        // Table format works best with flat arrays
        if (is_array($data)) {
            $flattened = [];

            foreach ($data as $key => $value) {
                if (is_array($value)) {
                    // Flatten nested arrays for table display
                    foreach ($value as $subKey => $subValue) {
                        $flattened[$key . '.' . $subKey] = is_scalar($subValue)
                            ? $subValue
                            : json_encode($subValue);
                    }
                } else {
                    $flattened[$key] = $value;
                }
            }

            return $flattened;
        }

        return $data;
    }

    private function optimizeForTree(mixed $data): mixed
    {
        // Tree format benefits from hierarchical structure
        if (is_array($data)) {
            // Limit depth for tree display
            return $this->truncateData($data, ['max_depth' => 5]);
        }

        return $data;
    }

    private function optimizeForText(mixed $data): mixed
    {
        // Text format should be human-readable
        if (is_array($data)) {
            $text = [];

            foreach ($data as $key => $value) {
                $textValue = is_scalar($value) ? (string) $value : json_encode($value);
                $text[] = $key . ': ' . $textValue;
            }

            return implode("\n", $text);
        }

        return (string) $data;
    }

    private function optimizeForMermaid(mixed $data): mixed
    {
        // Mermaid diagrams need specific structure
        if (is_array($data)) {
            // Simplify for diagram generation
            return array_map(function ($value) {
                if (is_string($value)) {
                    // Remove special characters that break Mermaid syntax
                    return preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $value);
                }

                return $value;
            }, $data);
        }

        return $data;
    }

    private function optimizeForPlantUML(mixed $data): mixed
    {
        // PlantUML-specific optimizations
        return $this->optimizeForMermaid($data); // Similar requirements
    }

    private function truncateRecursive(mixed $data, array $limits, int $depth): mixed
    {
        if ($depth >= $limits['max_depth']) {
            return '[Max depth reached]';
        }

        if (is_array($data)) {
            if (count($data) > $limits['max_array_size']) {
                $data = array_slice($data, 0, $limits['max_array_size'], true);
                $data['_truncated'] = true;
            }

            return array_map(
                fn ($value) => $this->truncateRecursive($value, $limits, $depth + 1),
                $data
            );
        }

        if (is_string($data) && strlen($data) > $limits['max_string_length']) {
            return substr($data, 0, $limits['max_string_length']) . '... [truncated]';
        }

        return $data;
    }

    private function filterArrayByRelevance(array $data, float $priority): array
    {
        $itemsToKeep = max(1, (int) (count($data) * $priority));

        // Sort by importance (implementation would need domain knowledge)
        // For now, keep first N items
        return array_slice($data, 0, $itemsToKeep, true);
    }

    private function filterValueByRelevance(mixed $value, float $priority): mixed
    {
        if (is_string($value) && $priority < 0.8) {
            // Truncate less important strings
            $maxLength = (int) (strlen($value) * $priority);

            return strlen($value) > $maxLength
                ? substr($value, 0, $maxLength) . '...'
                : $value;
        }

        return $value;
    }

    private function calculateKeyPriority(string $key, mixed $value): float
    {
        // Important keys get higher priority
        $highPriorityKeys = ['id', 'name', 'title', 'type', 'status', 'error', 'message'];
        $mediumPriorityKeys = ['description', 'path', 'url', 'class', 'method'];
        $lowPriorityKeys = ['debug', 'trace', 'metadata', 'internal'];

        $lowerKey = strtolower($key);

        if (in_array($lowerKey, $highPriorityKeys)) {
            return 1.0;
        }

        if (in_array($lowerKey, $mediumPriorityKeys)) {
            return 0.7;
        }

        if (in_array($lowerKey, $lowPriorityKeys)) {
            return 0.3;
        }

        // Default priority based on value type
        if (is_scalar($value)) {
            return 0.6;
        }

        if (is_array($value) && count($value) < 10) {
            return 0.5;
        }

        return 0.4;
    }

    private function getDefaultPriorities(): array
    {
        return [
            'id' => 1.0,
            'name' => 1.0,
            'title' => 1.0,
            'type' => 0.9,
            'status' => 0.9,
            'message' => 0.8,
            'description' => 0.7,
            'path' => 0.6,
            'debug' => 0.3,
            'trace' => 0.2,
            'metadata' => 0.4,
        ];
    }

    private function calculateSize(mixed $data): int
    {
        return strlen(serialize($data));
    }

    private function getAppliedOptimizations(mixed $original, mixed $optimized): array
    {
        $optimizations = [];

        $originalSize = $this->calculateSize($original);
        $optimizedSize = $this->calculateSize($optimized);

        if ($optimizedSize < $originalSize) {
            $optimizations[] = 'size_reduction';
        }

        if (is_array($original) && is_array($optimized)) {
            if (count($optimized) < count($original)) {
                $optimizations[] = 'array_truncation';
            }

            if (isset($optimized['_truncated'])) {
                $optimizations[] = 'data_truncation';
            }
        }

        return $optimizations;
    }

    private function generateMetadata(mixed $original, mixed $optimized, OptimizationStrategy $strategy): array
    {
        return [
            'strategy_applied' => $strategy->value,
            'optimization_timestamp' => time(),
            'original_type' => gettype($original),
            'optimized_type' => gettype($optimized),
            'size_reduction_bytes' => $this->calculateSize($original) - $this->calculateSize($optimized),
            'processing_time' => microtime(true), // This would need actual timing
        ];
    }
}
