<?php

declare(strict_types=1);

namespace App\Framework\Tokenizer;

use App\Framework\Tokenizer\ValueObjects\TokenContext;
use PhpToken;

/**
 * Analyzes and maintains token context during parsing
 */
final class TokenContextAnalyzer
{
    /**
     * Analyze context for a token
     */
    public function analyzeContext(
        PhpToken $token,
        array $allTokens,
        int $index,
        TokenContext $currentContext
    ): TokenContext {
        // Handle scope entering tokens
        $context = match($token->id) {
            T_CLASS => $this->enterClassScope($allTokens, $index, $currentContext, 'class'),
            T_INTERFACE => $this->enterClassScope($allTokens, $index, $currentContext, 'interface'),
            T_TRAIT => $this->enterClassScope($allTokens, $index, $currentContext, 'trait'),
            T_ENUM => $this->enterClassScope($allTokens, $index, $currentContext, 'enum'),
            T_FUNCTION, T_FN => $this->enterFunctionScope($allTokens, $index, $currentContext),
            T_NAMESPACE => $this->enterNamespaceScope($allTokens, $index, $currentContext),
            T_ATTRIBUTE => $currentContext->enterScope('attribute'),
            T_DOC_COMMENT => $currentContext->enterScope('doccomment'),
            default => $currentContext
        };

        // Handle scope exiting
        if ($token->text === '}') {
            $context = $this->handleClosingBrace($context);
        } elseif ($token->text === ']' && $context->isInAttribute) {
            $context = $context->exitScope();
        } elseif ($context->isInDocComment && ! $token->is(T_DOC_COMMENT)) {
            $context = $context->exitScope();
        }

        return $context;
    }

    /**
     * Enter a class-like scope
     */
    private function enterClassScope(
        array $tokens,
        int $index,
        TokenContext $context,
        string $type
    ): TokenContext {
        $name = $this->findNextIdentifier($tokens, $index);

        return $context->enterScope($type, $name);
    }

    /**
     * Enter a function scope
     */
    private function enterFunctionScope(
        array $tokens,
        int $index,
        TokenContext $context
    ): TokenContext {
        $name = $this->findNextIdentifier($tokens, $index);
        $type = $context->isInClass ? 'method' : 'function';

        return $context->enterScope($type, $name);
    }

    /**
     * Enter a namespace scope
     */
    private function enterNamespaceScope(
        array $tokens,
        int $index,
        TokenContext $context
    ): TokenContext {
        $namespace = $this->extractNamespace($tokens, $index);

        return $context->enterScope('namespace', $namespace);
    }

    /**
     * Handle closing brace - exit appropriate scope
     */
    private function handleClosingBrace(TokenContext $context): TokenContext
    {
        $currentScope = $context->getCurrentScopeType();

        // Only exit scope for structural braces
        $shouldExit = match($currentScope) {
            'class', 'interface', 'trait', 'enum',
            'function', 'method', 'namespace' => true,
            default => false
        };

        return $shouldExit ? $context->exitScope() : $context;
    }

    /**
     * Find the next identifier token
     */
    private function findNextIdentifier(array $tokens, int $startIndex): ?string
    {
        for ($i = $startIndex + 1; $i < count($tokens); $i++) {
            if ($tokens[$i]->is(T_STRING)) {
                return $tokens[$i]->text;
            }
            // Stop if we hit structural elements
            if ($tokens[$i]->text === '{' || $tokens[$i]->text === ';') {
                break;
            }
        }

        return null;
    }

    /**
     * Extract namespace from tokens
     */
    private function extractNamespace(array $tokens, int $startIndex): string
    {
        $parts = [];
        $inNamespace = false;

        for ($i = $startIndex + 1; $i < count($tokens); $i++) {
            $token = $tokens[$i];

            if ($token->is(T_STRING) || $token->is(T_NS_SEPARATOR) || $token->is(T_NAME_QUALIFIED)) {
                $inNamespace = true;
                $parts[] = $token->text;
            } elseif ($inNamespace && ($token->text === ';' || $token->text === '{')) {
                break;
            } elseif ($token->is(T_WHITESPACE)) {
                continue;
            } elseif ($inNamespace) {
                break;
            }
        }

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