<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Processing;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\FileSystemService;
use Throwable;

/**
 * Extracts class information from PHP files efficiently
 */
final readonly class ClassExtractor
{
    private const PATTERNS = [
        'namespace' => '/^\s*namespace\s+([^;]+);/m',
        'class' => '/^\s*(?:final\s+)?(?:abstract\s+)?(?:readonly\s+)?class\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/mi',
        'interface' => '/^\s*interface\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/mi',
        'trait' => '/^\s*trait\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/mi',
        'enum' => '/^\s*enum\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/mi',
    ];

    public function __construct(
        private FileSystemService $fileSystemService
    ) {
    }

    /**
     * Extract class names from a file
     * @return array<ClassName>
     */
    public function extractFromFile(File $file): array
    {
        try {
            // Read file content once
            $content = $this->fileSystemService->readFile($file);

            // Extract namespace
            $namespace = $this->extractNamespace($content);

            // Extract all class-like declarations
            $classNames = [];

            foreach (['class', 'interface', 'trait', 'enum'] as $type) {
                $names = $this->extractDeclarations($content, $type, $namespace);
                foreach ($names as $name) {
                    $classNames[] = $name;
                }
            }

            return $this->deduplicateClassNames($classNames);

        } catch (Throwable) {
            // Silently fail for files that can't be processed
            return [];
        }
    }

    /**
     * Extract namespace from content
     */
    private function extractNamespace(string $content): string
    {
        if (preg_match(self::PATTERNS['namespace'], $content, $matches)) {
            return trim($matches[1]);
        }

        return '';
    }

    /**
     * Extract declarations of a specific type
     * @return array<ClassName>
     */
    private function extractDeclarations(string $content, string $type, string $namespace): array
    {
        $pattern = self::PATTERNS[$type] ?? null;
        if (! $pattern) {
            return [];
        }

        $classNames = [];
        if (preg_match_all($pattern, $content, $matches)) {
            foreach ($matches[1] as $name) {
                $fullName = $namespace ? $namespace . '\\' . $name : $name;
                $classNames[] = ClassName::create($fullName);
            }
        }

        return $classNames;
    }

    /**
     * Remove duplicate class names
     * @param array<ClassName> $classNames
     * @return array<ClassName>
     */
    private function deduplicateClassNames(array $classNames): array
    {
        $seen = [];
        $unique = [];

        foreach ($classNames as $className) {
            $fqn = $className->getFullyQualified();
            if (! isset($seen[$fqn])) {
                $seen[$fqn] = true;
                $unique[] = $className;
            }
        }

        return $unique;
    }

    /**
     * Quick check if content likely contains PHP classes
     */
    public function likelyContainsClasses(string $content): bool
    {
        // Quick checks to avoid regex on files that definitely don't have classes
        if (strlen($content) < 10) {
            return false;
        }

        // Must have PHP opening tag
        if (! str_contains($content, '<?php')) {
            return false;
        }

        // Check for class-like keywords
        $keywords = ['class ', 'interface ', 'trait ', 'enum '];
        foreach ($keywords as $keyword) {
            if (str_contains($content, $keyword)) {
                return true;
            }
        }

        return false;
    }
}
