<?php

declare(strict_types=1);

namespace App\Framework\Mcp\Shared\Formatters;

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

/**
 * Tree Output Formatter
 *
 * Formatiert Daten als hierarchische Baumstruktur mit eingerückten Elementen
 */
final readonly class TreeFormatter implements OutputFormatter
{
    public function format(mixed $data): array
    {
        $treeData = $this->normalizeToArray($data);

        if (empty($treeData)) {
            return [
                'content' => "No data to display\n",
                'format' => $this->getFormat()->value,
                'tree_depth' => 0,
                'total_nodes' => 0,
            ];
        }

        $treeOutput = $this->buildTree($treeData);
        $stats = $this->calculateTreeStats($treeData);

        return [
            'content' => $treeOutput,
            'format' => $this->getFormat()->value,
            'tree_depth' => $stats['max_depth'],
            'total_nodes' => $stats['node_count'],
            'branches' => $stats['branches'],
        ];
    }

    public function supports(OutputFormat $format): bool
    {
        return $format === OutputFormat::TREE;
    }

    public function getFormat(): OutputFormat
    {
        return OutputFormat::TREE;
    }

    public function getDescription(): string
    {
        return $this->getFormat()->getDescription();
    }

    private function normalizeToArray(mixed $data): array
    {
        if (is_array($data)) {
            return $data;
        }

        if (is_object($data)) {
            return (array) $data;
        }

        return ['root' => $data];
    }

    private function buildTree(array $data, int $depth = 0, string $prefix = ''): string
    {
        $output = '';
        $keys = array_keys($data);
        $keyCount = count($keys);

        foreach ($keys as $index => $key) {
            $value = $data[$key];
            $isLast = ($index === $keyCount - 1);

            // Tree symbols
            $connector = $isLast ? '└── ' : '├── ';
            $childPrefix = $prefix . ($isLast ? '    ' : '│   ');

            // Format the key-value pair
            $line = $prefix . $connector . $this->formatNode($key, $value);
            $output .= $line . "\n";

            // Recursively handle nested arrays/objects
            if (is_array($value) && ! empty($value) && $depth < 10) {
                $output .= $this->buildTree($value, $depth + 1, $childPrefix);
            } elseif (is_object($value) && $depth < 10) {
                $objectArray = (array) $value;
                if (! empty($objectArray)) {
                    $output .= $this->buildTree($objectArray, $depth + 1, $childPrefix);
                }
            }
        }

        return $output;
    }

    private function formatNode(string|int $key, mixed $value): string
    {
        $formattedValue = $this->formatValue($value);

        if (is_array($value) || is_object($value)) {
            $childCount = is_array($value) ? count($value) : count((array) $value);

            return "{$key} ({$childCount} items)";
        }

        return "{$key}: {$formattedValue}";
    }

    private function formatValue(mixed $value): string
    {
        return match (true) {
            is_bool($value) => $value ? 'true' : 'false',
            is_null($value) => 'null',
            is_string($value) => '"' . (strlen($value) > 50 ? substr($value, 0, 47) . '...' : $value) . '"',
            is_numeric($value) => (string) $value,
            is_array($value) => '[array:' . count($value) . ']',
            is_object($value) => '[object:' . get_class($value) . ']',
            default => '[' . gettype($value) . ']'
        };
    }

    private function calculateTreeStats(array $data, int $currentDepth = 0): array
    {
        $stats = [
            'max_depth' => $currentDepth,
            'node_count' => count($data),
            'branches' => 0,
        ];

        foreach ($data as $value) {
            if (is_array($value) && ! empty($value)) {
                $stats['branches']++;
                $childStats = $this->calculateTreeStats($value, $currentDepth + 1);
                $stats['max_depth'] = max($stats['max_depth'], $childStats['max_depth']);
                $stats['node_count'] += $childStats['node_count'];
                $stats['branches'] += $childStats['branches'];
            } elseif (is_object($value)) {
                $objectArray = (array) $value;
                if (! empty($objectArray)) {
                    $stats['branches']++;
                    $childStats = $this->calculateTreeStats($objectArray, $currentDepth + 1);
                    $stats['max_depth'] = max($stats['max_depth'], $childStats['max_depth']);
                    $stats['node_count'] += $childStats['node_count'];
                    $stats['branches'] += $childStats['branches'];
                }
            }
        }

        return $stats;
    }
}
