<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\Modifier\PhpSignature;

use Butschster\ContextGenerator\Modifier\SourceModifierInterface;
use Nette\PhpGenerator\ClassLike;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\EnumType;
use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\TraitType;

/**
 * Parser for PHP class files to extract signatures without implementation details.
 */
final class PhpSignature implements SourceModifierInterface
{
    private const array MAGIC_METHODS = [
        '__construct',
    ];

    /**
     *
     * @psalm-return 'php-signature'
     */
    public function getIdentifier(): string
    {
        return 'php-signature';
    }

    public function supports(string $contentType): bool
    {
        return \str_ends_with($contentType, '.php');
    }

    public function modify(string $content, array $context = []): string
    {
        try {
            $file = PhpFile::fromCode($content);

            $output = '';
            foreach ($file->getNamespaces() as $namespace) {
                $output .= "namespace {$namespace->getName()};\n\n";

                foreach ($namespace->getUses() as $use) {
                    $output .= "use $use;\n";
                }

                foreach ($namespace->getClasses() as $class) {
                    $output .= $this->processClass($class);
                }
            }

            return $output;
        } catch (\Throwable $e) {
            return "// Error parsing file: {$e->getMessage()}\n";
        }
    }

    /**
     * Process a class and generate its signature
     */
    private function processClass(ClassLike $class): string
    {
        $output = '';

        // Determine class type and generate signature
        if ($class->isInterface()) {
            $output .= $this->generateInterfaceSignature($class);
        } elseif ($class->isTrait()) {
            $output .= $this->generateTraitSignature($class);
        } elseif (PHP_VERSION_ID >= 80100 && \method_exists($class, 'isEnum') && $class->isEnum()) {
            $output .= $this->generateEnumSignature($class);
        } else {
            $output .= $this->generateClassSignature($class);
        }

        return $output . "\n";
    }

    /**
     * Generate class signature
     */
    private function generateClassSignature(ClassType $class): string
    {
        // Remove not public properties
        foreach ($class->getProperties() as $property) {
            if (!$property->isPublic()) {
                $class->removeProperty($property->getName());
            }
        }

        // Remove not public constants
        foreach ($class->getConstants() as $constant) {
            if (!$constant->isPublic()) {
                $class->removeConstant($constant->getName());
            }
        }


        // Remove method bodies
        foreach ($class->getMethods() as $method) {
            // if is magic method, skip
            if (\in_array($method->getName(), self::MAGIC_METHODS, true)) {
                continue;
            }

            if (!$method->isPublic()) {
                $class->removeMethod($method->getName());
            }

            $method->setBody($method->isAbstract() ? '' : '/* ... */');
        }

        // Generate the class code
        return (string) $class;
    }

    /**
     * Generate interface signature
     */
    private function generateInterfaceSignature(InterfaceType $interface): string
    {
        // Generate the interface code
        return (string) $interface;
    }

    /**
     * Generate trait signature
     */
    private function generateTraitSignature(TraitType $trait): string
    {
        // Remove not public properties
        foreach ($trait->getProperties() as $property) {
            if (!$property->isPublic()) {
                $trait->removeProperty($property->getName());
            }
        }

        // Remove not public constants
        foreach ($trait->getConstants() as $constant) {
            if (!$constant->isPublic()) {
                $trait->removeConstant($constant->getName());
            }
        }

        foreach ($trait->getMethods() as $method) {
            if (!$method->isPublic()) {
                $trait->removeMethod($method->getName());
            }
            $method->setBody('/* ... */');
        }

        // Generate the trait code
        return (string) $trait;
    }

    /**
     * Generate enum signature (PHP 8.1+)
     */
    private function generateEnumSignature(EnumType $enum): string
    {
        // Remove method bodies
        foreach ($enum->getMethods() as $method) {
            if (!$method->isPublic()) {
                $enum->removeMethod($method->getName());
            }
            $method->setBody('/* ... */');
        }

        // Generate the enum code
        return (string) $enum;
    }
}
