<?php

declare(strict_types=1);

namespace App\Framework\Quality\PHPStan\Rules\Naming;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
 * Enforces Value Object Naming Convention:
 * - NO "VO" or "ValueObject" suffix
 * - Descriptive names (Email, Price, Duration, not EmailVO or EmailValueObject)
 *
 * @implements Rule<InClassNode>
 */
final class ValueObjectNamingRule implements Rule
{
    public function getNodeType(): string
    {
        return InClassNode::class;
    }

    /**
     * @param InClassNode $node
     */
    public function processNode(Node $node, Scope $scope): array
    {
        $classReflection = $node->getClassReflection();
        $className = $classReflection->getName();
        $shortName = $classReflection->getNativeReflection()->getShortName();

        // Check if it's a Value Object
        if (! $this->isValueObject($classReflection)) {
            return [];
        }

        $errors = [];

        // Rule 1: NO "VO" suffix
        if (str_ends_with($shortName, 'VO')) {
            $suggestedName = preg_replace('/VO$/', '', $shortName);
            $errors[] = RuleErrorBuilder::message(sprintf(
                'Value Object "%s" should not have "VO" suffix. Rename to "%s".',
                $shortName,
                $suggestedName
            ))
            ->identifier('naming.valueObject.noVoSuffix')
            ->build();
        }

        // Rule 2: NO "ValueObject" suffix
        if (str_ends_with($shortName, 'ValueObject')) {
            $suggestedName = preg_replace('/ValueObject$/', '', $shortName);
            $errors[] = RuleErrorBuilder::message(sprintf(
                'Value Object "%s" should not have "ValueObject" suffix. Rename to "%s".',
                $shortName,
                $suggestedName
            ))
            ->identifier('naming.valueObject.noValueObjectSuffix')
            ->build();
        }

        return $errors;
    }

    private function isValueObject(\PHPStan\Reflection\ClassReflection $classReflection): bool
    {
        $nativeReflection = $classReflection->getNativeReflection();

        // Value Objects are typically:
        // 1. Readonly classes
        if (! $nativeReflection->isReadOnly()) {
            return false;
        }

        // 2. Final classes
        if (! $nativeReflection->isFinal()) {
            return false;
        }

        // 3. In specific namespaces (ValueObjects, Core, Domain)
        $className = $classReflection->getName();
        $valueObjectNamespaces = [
            '\\ValueObjects\\',
            '\\Core\\ValueObjects\\',
            '\\Domain\\',
        ];

        foreach ($valueObjectNamespaces as $namespace) {
            if (str_contains($className, $namespace)) {
                return true;
            }
        }

        // 4. Has only readonly public properties and no methods with business logic
        $publicMethods = array_filter(
            $nativeReflection->getMethods(),
            fn ($m) => $m->isPublic() && ! $m->isConstructor() && ! $m->isStatic()
        );

        // Value Objects typically have few public methods (mostly transformations/getters)
        if (count($publicMethods) <= 5) {
            // Check for common Value Object methods
            $valueObjectMethods = ['equals', 'toString', 'toArray', 'toJson', 'toDecimal', 'format'];
            foreach ($publicMethods as $method) {
                if (in_array($method->getName(), $valueObjectMethods, true)) {
                    return true;
                }
            }
        }

        return false;
    }
}
