<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

use InvalidArgumentException;

/**
 * Immutable class name value object with namespace support
 */
final readonly class ClassName
{
    /**
     * @var class-string
     */
    private string $fullyQualified;

    private PhpNamespace $namespace;

    private string $shortName;

    private function __construct(string $className)
    {
        if (empty($className)) {
            throw new InvalidArgumentException('Class name cannot be empty');
        }

        // Remove the leading backslash
        $this->fullyQualified = ltrim($className, '\\');

        if (! $this->isValidClassName($this->fullyQualified)) {
            throw new InvalidArgumentException("Invalid class name: {$className}");
        }

        // Parse class name components directly in constructor
        $lastBackslash = strrpos($this->fullyQualified, '\\');

        if ($lastBackslash === false) {
            $this->namespace = PhpNamespace::global();
            $this->shortName = $this->fullyQualified;
        } else {
            $namespaceStr = substr($this->fullyQualified, 0, $lastBackslash);
            $this->namespace = PhpNamespace::fromString($namespaceStr);
            $this->shortName = substr($this->fullyQualified, $lastBackslash + 1);
        }
    }

    /**
     * Create from string
     */
    public static function create(string $className): self
    {
        return new self($className);
    }

    /**
     * Create from object
     */
    public static function fromObject(object $object): self
    {
        return new self($object::class);
    }

    /**
     * Create from namespace and short name
     */
    public static function fromNamespace(PhpNamespace $namespace, string $shortName): self
    {
        if ($namespace->isGlobal()) {
            return new self($shortName);
        }

        return new self($namespace->toString() . '\\' . $shortName);
    }

    /**
     * Get a fully qualified class name
     * @return class-string
     */
    public function getFullyQualified(): string
    {
        return $this->fullyQualified;
    }

    /**
     * Get namespace as string
     */
    public function getNamespace(): string
    {
        return $this->namespace->toString();
    }

    /**
     * Get namespace as value object
     */
    public function getNamespaceObject(): PhpNamespace
    {
        return $this->namespace;
    }

    /**
     * Get a short class name (without a namespace)
     */
    public function getShortName(): string
    {
        return $this->shortName;
    }

    /**
     * Get class name with leading backslash
     */
    public function getWithLeadingSlash(): string
    {
        return '\\' . $this->fullyQualified;
    }

    /**
     * Check if class exists
     *
     * @param bool $autoload Whether to trigger autoloading (default: true)
     *                       Set to false during discovery to prevent constructor execution
     */
    public function exists(bool $autoload = true): bool
    {
        if (empty($this->fullyQualified)) {
            return false;
        }

        return class_exists($this->fullyQualified, $autoload)
            || interface_exists($this->fullyQualified, $autoload)
            || trait_exists($this->fullyQualified, $autoload);
    }

    /**
     * Check if class implements interface
     */
    public function implementsInterface(string $interface): bool
    {
        if (! $this->exists()) {
            return false;
        }

        return is_subclass_of($this->fullyQualified, $interface);
    }

    /**
     * Check if class extends another class
     */
    public function extends(string $parentClass): bool
    {
        if (! $this->exists()) {
            return false;
        }

        return is_subclass_of($this->fullyQualified, $parentClass);
    }

    /**
     * Check if in same namespace
     */
    public function inSameNamespace(self $other): bool
    {
        return $this->namespace->equals($other->namespace);
    }

    /**
     * Check if in namespace (or sub-namespace)
     */
    public function inNamespace(string|PhpNamespace $namespace): bool
    {
        $namespaceObj = $namespace instanceof PhpNamespace ? $namespace : PhpNamespace::fromString($namespace);

        return $this->namespace->startsWith($namespaceObj);
    }

    /**
     * Get parent namespace as string (deprecated - use getParentNamespaceObject)
     */
    public function getParentNamespace(): ?string
    {
        $parent = $this->namespace->parent();

        return $parent?->toString();
    }

    /**
     * Get parent namespace as value object
     */
    public function getParentNamespaceObject(): ?PhpNamespace
    {
        return $this->namespace->parent();
    }

    /**
     * Get all namespace parts
     * @return array<string>
     */
    public function getNamespaceParts(): array
    {
        return $this->namespace->parts();
    }

    /**
     * Check if class name matches pattern
     */
    public function matches(string $pattern): bool
    {
        return fnmatch($pattern, $this->fullyQualified, FNM_NOESCAPE);
    }

    /**
     * String representation (short name for readability)
     */
    public function __toString(): string
    {
        return $this->shortName;
    }

    /**
     * Explicit toString method
     */
    public function toString(): string
    {
        return $this->fullyQualified;
    }

    /**
     * Get string representation for debugging
     */
    public function toDebugString(): string
    {
        if ($this->namespace->isGlobal()) {
            return $this->shortName;
        }

        return $this->namespace->toString() . '\\' . $this->shortName;
    }

    /**
     * Compare for equality
     */
    public function equals(self $other): bool
    {
        return $this->fullyQualified === $other->fullyQualified;
    }

    /**
     * Change namespace (returns new instance with same short name but different namespace)
     */
    public function withNamespace(PhpNamespace $namespace): self
    {
        return self::fromNamespace($namespace, $this->shortName);
    }

    /**
     * Move to parent namespace (returns new instance in parent namespace)
     */
    public function moveToParent(): ?self
    {
        $parent = $this->getParentNamespaceObject();

        if ($parent === null) {
            return null;
        }

        return self::fromNamespace($parent, $this->shortName);
    }

    /**
     * Move to child namespace (returns new instance in child namespace)
     */
    public function moveToChild(string $childNamespace): self
    {
        $newNamespace = $this->namespace->append($childNamespace);

        return self::fromNamespace($newNamespace, $this->shortName);
    }

    /**
     * Validate class name format
     */
    private function isValidClassName(string $className): bool
    {
        // Basic validation: should contain only alphanumeric, underscore, and backslash
        return preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\\\\]*$/', $className) === 1;
    }
}
