<?php

declare(strict_types=1);

namespace App\Framework\Mcp;

use App\Framework\Core\AttributeMapper;
use App\Framework\Mcp\Core\ValueObjects\OutputFormat;
use App\Framework\Reflection\WrappedReflectionClass;
use App\Framework\Reflection\WrappedReflectionMethod;
use ReflectionEnum;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionUnionType;

final readonly class McpToolMapper implements AttributeMapper
{
    public function getAttributeClass(): string
    {
        return McpTool::class;
    }

    public function map(WrappedReflectionClass|WrappedReflectionMethod $reflectionTarget, object $attributeInstance): ?array
    {
        if (! $reflectionTarget instanceof WrappedReflectionMethod || ! $attributeInstance instanceof McpTool) {
            return null;
        }

        $class = $reflectionTarget->getDeclaringClass();

        return [
            'name' => $attributeInstance->name,
            'description' => $attributeInstance->description,
            'inputSchema' => $this->generateInputSchema($reflectionTarget, $attributeInstance),
            'class' => $class->getFullyQualified(),
            'method' => $reflectionTarget->getName(),
            'parameters' => $this->extractParameters($reflectionTarget),
        ];
    }

    private function generateInputSchema(WrappedReflectionMethod $method, McpTool $tool): array
    {
        $schema = [
            'type' => 'object',
            'properties' => [],
            'required' => [],
            'title' => $tool->name,
            'description' => $tool->description,
        ];

        if (! empty($tool->category)) {
            $schema['category'] = $tool->category;
        }

        if (! empty($tool->tags)) {
            $schema['tags'] = $tool->tags;
        }

        foreach ($method->getParameters() as $param) {
            $paramName = $param->getName();
            $paramSchema = $this->generateParameterSchema($param);

            $schema['properties'][$paramName] = $paramSchema;

            if (! $param->isOptional()) {
                $schema['required'][] = $paramName;
            }
        }

        return $schema;
    }

    private function generateParameterSchema(\ReflectionParameter $param): array
    {
        $type = $param->getType();
        $schema = $this->mapPhpTypeToJsonSchema($type);

        // Add description based on parameter name
        $schema['description'] = $this->generateParameterDescription($param);

        // Add default value if parameter is optional
        if ($param->isOptional()) {
            try {
                $defaultValue = $param->getDefaultValue();
                $schema['default'] = $defaultValue;
            } catch (\ReflectionException) {
                // Some defaults (like class constants) can't be retrieved
                $schema['default'] = null;
            }
        }

        return $schema;
    }

    private function mapPhpTypeToJsonSchema(?\ReflectionType $type): array
    {
        if ($type === null) {
            return ['type' => 'string', 'description' => 'Mixed type parameter'];
        }

        if ($type instanceof ReflectionNamedType) {
            return $this->mapNamedTypeToSchema($type);
        }

        if ($type instanceof ReflectionUnionType) {
            return $this->mapUnionTypeToSchema($type);
        }

        if ($type instanceof ReflectionIntersectionType) {
            return ['type' => 'object', 'description' => 'Intersection type'];
        }

        return ['type' => 'string', 'description' => 'Unknown type'];
    }

    private function mapNamedTypeToSchema(ReflectionNamedType $type): array
    {
        $typeName = $type->getName();

        return match ($typeName) {
            'string' => ['type' => 'string'],
            'int' => ['type' => 'integer'],
            'float' => ['type' => 'number'],
            'bool' => ['type' => 'boolean'],
            'array' => ['type' => 'array', 'items' => ['type' => 'string']],
            'null' => ['type' => 'null'],
            'mixed' => ['description' => 'Mixed type - can be any value'],
            default => $this->mapComplexTypeToSchema($typeName)
        };
    }

    private function mapComplexTypeToSchema(string $typeName): array
    {
        // Handle enums
        if (class_exists($typeName) && enum_exists($typeName)) {
            return $this->mapEnumToSchema($typeName);
        }

        // Handle OutputFormat specifically
        if ($typeName === OutputFormat::class || str_ends_with($typeName, 'OutputFormat')) {
            return [
                'type' => 'string',
                'enum' => ['array', 'json', 'table', 'tree', 'text', 'mermaid', 'plantuml'],
                'default' => 'array',
                'description' => 'Output format for the response',
            ];
        }

        // Handle other classes
        if (class_exists($typeName)) {
            return [
                'type' => 'object',
                'description' => "Instance of {$typeName}",
            ];
        }

        // Fallback for unknown types
        return [
            'type' => 'string',
            'description' => "Parameter of type {$typeName}",
        ];
    }

    private function mapEnumToSchema(string $enumClass): array
    {
        try {
            $reflection = new ReflectionEnum($enumClass);
            $cases = [];

            foreach ($reflection->getCases() as $case) {
                $cases[] = $case->getValue();
            }

            return [
                'type' => 'string',
                'enum' => $cases,
                'description' => "Enum values from {$enumClass}",
            ];
        } catch (\ReflectionException) {
            return [
                'type' => 'string',
                'description' => "Enum type {$enumClass}",
            ];
        }
    }

    private function mapUnionTypeToSchema(ReflectionUnionType $type): array
    {
        $types = [];
        $hasNull = false;

        foreach ($type->getTypes() as $unionType) {
            if ($unionType instanceof ReflectionNamedType && $unionType->getName() === 'null') {
                $hasNull = true;

                continue;
            }

            $schema = $this->mapPhpTypeToJsonSchema($unionType);
            $types[] = $schema;
        }

        if (count($types) === 1) {
            $schema = $types[0];
            if ($hasNull) {
                $schema['nullable'] = true;
            }

            return $schema;
        }

        return [
            'anyOf' => $types,
            'nullable' => $hasNull,
            'description' => 'Union type parameter',
        ];
    }

    private function generateParameterDescription(\ReflectionParameter $param): string
    {
        $name = $param->getName();
        $type = $param->getType();

        // Generate human-readable descriptions based on parameter names
        $descriptions = [
            'path' => 'File or directory path',
            'file' => 'File path',
            'directory' => 'Directory path',
            'format' => 'Output format for the response',
            'includeHidden' => 'Whether to include hidden files',
            'includeAnalysis' => 'Whether to include detailed analysis',
            'includeSecurity' => 'Whether to include security assessment',
            'includeMetrics' => 'Whether to include performance metrics',
            'includeHealthCheck' => 'Whether to include health check information',
            'controller' => 'Controller class name to filter by',
            'method' => 'HTTP method to filter by',
            'focus' => 'Area of focus for analysis',
            'task' => 'Task description for the agent',
            'sortBy' => 'Field to sort results by',
            'sortOrder' => 'Sort order (asc or desc)',
            'pattern' => 'Search pattern or regex',
            'limit' => 'Maximum number of results to return',
            'offset' => 'Number of results to skip',
            'recursive' => 'Whether to search recursively',
            'caseSensitive' => 'Whether search should be case sensitive',
        ];

        if (isset($descriptions[$name])) {
            return $descriptions[$name];
        }

        $typeName = $type instanceof ReflectionNamedType ? $type->getName() : 'mixed';

        return "Parameter of type {$typeName}";
    }

    private function extractParameters(WrappedReflectionMethod $method): array
    {
        $parameters = [];

        foreach ($method->getParameters() as $param) {
            $type = $param->getType();
            $parameters[] = [
                'name' => $param->getName(),
                'type' => $type ? $type->getName() : 'mixed',
                'required' => ! $param->isOptional(),
                'default' => $param->isOptional() ? $param->getDefaultValue() : null,
            ];
        }

        return $parameters;
    }
}
