<?php

declare(strict_types=1);

namespace App\Framework\Tokenizer\ValueObjects;

/**
 * Context information for a token
 */
final readonly class TokenContext
{
    public function __construct(
        public bool    $isInClass = false,
        public bool    $isInFunction = false,
        public bool    $isInNamespace = false,
        public bool    $isInAttribute = false,
        public bool    $isInDocComment = false,
        public ?string $currentClass = null,
        public ?string $currentFunction = null,
        public ?string $currentNamespace = null,
        public array   $scopeStack = [],
        public int     $nestingLevel = 0
    ) {
    }

    /**
     * Create a new context with updated values
     */
    public function with(array $updates): self
    {
        return new self(
            isInClass: $updates['isInClass'] ?? $this->isInClass,
            isInFunction: $updates['isInFunction'] ?? $this->isInFunction,
            isInNamespace: $updates['isInNamespace'] ?? $this->isInNamespace,
            isInAttribute: $updates['isInAttribute'] ?? $this->isInAttribute,
            isInDocComment: $updates['isInDocComment'] ?? $this->isInDocComment,
            currentClass: $updates['currentClass'] ?? $this->currentClass,
            currentFunction: $updates['currentFunction'] ?? $this->currentFunction,
            currentNamespace: $updates['currentNamespace'] ?? $this->currentNamespace,
            scopeStack: $updates['scopeStack'] ?? $this->scopeStack,
            nestingLevel: $updates['nestingLevel'] ?? $this->nestingLevel
        );
    }

    /**
     * Enter a new scope
     */
    public function enterScope(string $type, ?string $name = null): self
    {
        $newStack = $this->scopeStack;
        $newStack[] = ['type' => $type, 'name' => $name];

        $updates = [
            'scopeStack' => $newStack,
            'nestingLevel' => $this->nestingLevel + 1,
        ];

        // Use match for cleaner, type-safe scope handling
        $scopeUpdates = match($type) {
            'class', 'interface', 'trait', 'enum' => [
                'isInClass' => true,
                'currentClass' => $name,
            ],
            'function', 'method' => [
                'isInFunction' => true,
                'currentFunction' => $name,
            ],
            'namespace' => [
                'isInNamespace' => true,
                'currentNamespace' => $name,
            ],
            'attribute' => [
                'isInAttribute' => true,
            ],
            'doccomment' => [
                'isInDocComment' => true,
            ],
            default => []
        };

        return $this->with([...$updates, ...$scopeUpdates]);
    }

    /**
     * Exit current scope
     */
    public function exitScope(): self
    {
        if (empty($this->scopeStack)) {
            return $this;
        }

        $newStack = $this->scopeStack;
        $exitedScope = array_pop($newStack);

        $updates = [
            'scopeStack' => $newStack,
            'nestingLevel' => max(0, $this->nestingLevel - 1),
        ];

        // Update context based on remaining stack
        if (empty($newStack)) {
            $updates = [
                ...$updates,
                'isInClass' => false,
                'isInFunction' => false,
                'isInAttribute' => false,
                'isInDocComment' => false,
                'currentClass' => null,
                'currentFunction' => null,
            ];
        } else {
            // Check what scopes we're still in
            $stillInClass = false;
            $stillInFunction = false;
            $stillInAttribute = false;
            $stillInDocComment = false;
            $currentClass = null;
            $currentFunction = null;

            foreach ($newStack as $scope) {
                $scopeType = $scope['type'];

                match($scopeType) {
                    'class', 'interface', 'trait', 'enum' => ($stillInClass = true) && ($currentClass = $scope['name']),
                    'function', 'method' => ($stillInFunction = true) && ($currentFunction = $scope['name']),
                    'attribute' => $stillInAttribute = true,
                    'doccomment' => $stillInDocComment = true,
                    default => null
                };
            }

            $updates = [
                ...$updates,
                'isInClass' => $stillInClass,
                'isInFunction' => $stillInFunction,
                'isInAttribute' => $stillInAttribute,
                'isInDocComment' => $stillInDocComment,
                'currentClass' => $currentClass,
                'currentFunction' => $currentFunction,
            ];
        }

        return $this->with($updates);
    }

    /**
     * Get the current scope type
     */
    public function getCurrentScopeType(): ?string
    {
        if (empty($this->scopeStack)) {
            return null;
        }

        $currentScope = $this->scopeStack[array_key_last($this->scopeStack)];

        return $currentScope['type'] ?? null;
    }

    /**
     * Check if we're in a specific scope type
     */
    public function isInScopeType(string $type): bool
    {
        return match($type) {
            'class' => $this->isInClass,
            'function' => $this->isInFunction,
            'namespace' => $this->isInNamespace,
            'attribute' => $this->isInAttribute,
            'doccomment' => $this->isInDocComment,
            default => false
        };
    }

    /**
     * Get fully qualified name for current context
     */
    public function getFullyQualifiedName(?string $name = null): string
    {
        $parts = array_filter([
            $this->currentNamespace,
            $this->currentClass,
            $name,
        ]);

        return implode('\\', $parts);
    }

    /**
     * Clone the context
     */
    public function clone(): self
    {
        return new self(
            isInClass: $this->isInClass,
            isInFunction: $this->isInFunction,
            isInNamespace: $this->isInNamespace,
            isInAttribute: $this->isInAttribute,
            isInDocComment: $this->isInDocComment,
            currentClass: $this->currentClass,
            currentFunction: $this->currentFunction,
            currentNamespace: $this->currentNamespace,
            scopeStack: $this->scopeStack,
            nestingLevel: $this->nestingLevel
        );
    }
}
