<?php

declare(strict_types=1);

namespace App\Framework\Tokenizer;

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

/**
 * Classifies PHP tokens into semantic token types
 */
final readonly class TokenClassifier
{
    /**
     * Classify a token based on its context
     */
    public function classify(
        PhpToken $token,
        array $allTokens,
        int $index,
        TokenContext $context
    ): TokenType {
        // Handle basic token types first
        $basicType = $this->getBasicTokenType($token);

        // Apply contextual classification for T_STRING tokens
        if ($token->is(T_STRING)) {
            return $this->classifyString($token, $allTokens, $index, $context);
        }

        return $basicType;
    }

    /**
     * Get basic token type from PHP token ID
     */
    private function getBasicTokenType(PhpToken $token): TokenType
    {
        return match($token->id) {
            // Keywords
            T_ABSTRACT, T_AS, T_BREAK, T_CALLABLE, T_CASE, T_CATCH, T_CLASS,
            T_CLONE, T_CONST, T_CONTINUE, T_DECLARE, T_DEFAULT, T_DO, T_ECHO,
            T_ELSE, T_ELSEIF, T_EMPTY, T_ENDDECLARE, T_ENDFOR, T_ENDFOREACH,
            T_ENDIF, T_ENDSWITCH, T_ENDWHILE, T_ENUM, T_EVAL, T_EXIT, T_EXTENDS,
            T_FINAL, T_FINALLY, T_FN, T_FOR, T_FOREACH, T_FUNCTION, T_GLOBAL,
            T_GOTO, T_IF, T_IMPLEMENTS, T_INCLUDE, T_INCLUDE_ONCE, T_INSTANCEOF,
            T_INSTEADOF, T_INTERFACE, T_ISSET, T_LIST, T_MATCH, T_NAMESPACE,
            T_NEW, T_PRINT, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_READONLY,
            T_REQUIRE, T_REQUIRE_ONCE, T_RETURN, T_STATIC, T_SWITCH, T_THROW,
            T_TRAIT, T_TRY, T_UNSET, T_USE, T_VAR, T_WHILE, T_YIELD,
            T_YIELD_FROM => TokenType::KEYWORD,

            // Variables
            T_VARIABLE => TokenType::VARIABLE,

            // String literals
            T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE => TokenType::STRING_LITERAL,

            // Number literals
            T_LNUMBER, T_DNUMBER => TokenType::NUMBER_LITERAL,

            // Comments
            T_COMMENT => TokenType::COMMENT,
            T_DOC_COMMENT => TokenType::DOC_COMMENT,

            // Operators
            T_AND_EQUAL, T_BOOLEAN_AND, T_BOOLEAN_OR, T_COALESCE, T_COALESCE_EQUAL,
            T_CONCAT_EQUAL, T_DEC, T_DIV_EQUAL, T_DOUBLE_ARROW, T_DOUBLE_COLON,
            T_ELLIPSIS, T_INC, T_IS_EQUAL, T_IS_GREATER_OR_EQUAL, T_IS_IDENTICAL,
            T_IS_NOT_EQUAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_LOGICAL_AND,
            T_LOGICAL_OR, T_LOGICAL_XOR, T_MINUS_EQUAL, T_MOD_EQUAL, T_MUL_EQUAL,
            T_NULLSAFE_OBJECT_OPERATOR, T_OBJECT_OPERATOR, T_OR_EQUAL, T_PLUS_EQUAL,
            T_POW, T_POW_EQUAL, T_SL, T_SL_EQUAL, T_SPACESHIP, T_SR, T_SR_EQUAL,
            T_XOR_EQUAL => TokenType::OPERATOR,

            // Whitespace
            T_WHITESPACE => TokenType::WHITESPACE,

            // PHP tags
            T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG => TokenType::PHP_TAG,

            // Attributes
            T_ATTRIBUTE => TokenType::ATTRIBUTE,

            // HTML/inline content
            T_INLINE_HTML => TokenType::HTML,

            // Default for known tokens
            default => $this->classifySingleCharacter($token)
        };
    }

    /**
     * Classify single character tokens
     */
    private function classifySingleCharacter(PhpToken $token): TokenType
    {
        return match($token->text) {
            // Operators
            '+', '-', '*', '/', '%', '=', '<', '>', '!', '&', '|', '^', '~', '?', ':' => TokenType::OPERATOR,

            // Brackets
            '[', ']' => TokenType::BRACKET,

            // Parentheses
            '(', ')' => TokenType::PARENTHESIS,

            // Braces
            '{', '}' => TokenType::BRACE,

            // Punctuation
            ',', '.' => TokenType::PUNCTUATION,

            // Semicolon
            ';' => TokenType::SEMICOLON,

            // Default
            default => TokenType::DEFAULT
        };
    }

    /**
     * Classify T_STRING tokens based on context
     */
    private function classifyString(
        PhpToken $token,
        array $allTokens,
        int $index,
        TokenContext $context
    ): TokenType {
        // Check previous token for context clues
        $prevToken = $index > 0 ? $allTokens[$index - 1] : null;
        $nextToken = $index < count($allTokens) - 1 ? $allTokens[$index + 1] : null;

        // After class/interface/trait/enum keywords
        if ($prevToken && $prevToken->is([T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM])) {
            return match($prevToken->id) {
                T_CLASS => TokenType::CLASS_NAME,
                T_INTERFACE => TokenType::INTERFACE_NAME,
                T_TRAIT => TokenType::TRAIT_NAME,
                T_ENUM => TokenType::ENUM_NAME,
                default => TokenType::CLASS_NAME
            };
        }

        // After new/extends/implements
        if ($prevToken && $prevToken->is([T_NEW, T_EXTENDS, T_IMPLEMENTS])) {
            return TokenType::CLASS_NAME;
        }

        // After function keyword
        if ($prevToken && $prevToken->is(T_FUNCTION)) {
            return $context->isInClass ? TokenType::METHOD_NAME : TokenType::FUNCTION_NAME;
        }

        // After :: (static method/constant)
        if ($prevToken && $prevToken->is(T_DOUBLE_COLON)) {
            return $nextToken && $nextToken->text === '('
                ? TokenType::METHOD_NAME
                : TokenType::CONSTANT_NAME;
        }

        // After -> (object method/property)
        if ($prevToken && $prevToken->is([T_OBJECT_OPERATOR, T_NULLSAFE_OBJECT_OPERATOR])) {
            return $nextToken && $nextToken->text === '('
                ? TokenType::METHOD_NAME
                : TokenType::PROPERTY_NAME;
        }

        // In attribute context
        if ($context->isInAttribute) {
            return TokenType::ATTRIBUTE_NAME;
        }

        // Function/method call (followed by parenthesis)
        if ($nextToken && $nextToken->text === '(') {
            // Check if it's after 'new' (constructor)
            if ($prevToken && $prevToken->is(T_NEW)) {
                return TokenType::CLASS_NAME;
            }

            return $context->isInClass ? TokenType::METHOD_NAME : TokenType::FUNCTION_NAME;
        }

        // Constants (all uppercase)
        if (ctype_upper($token->text) && strlen($token->text) > 1) {
            return TokenType::CONSTANT_NAME;
        }

        // Type hints (in function parameters or return types)
        if ($this->isTypeHintContext($allTokens, $index)) {
            return TokenType::TYPE_HINT;
        }

        // Class names (PascalCase)
        if (preg_match('/^[A-Z][a-zA-Z0-9]*$/', $token->text)) {
            return TokenType::CLASS_NAME;
        }

        // Default to keyword if it's a known keyword
        if ($this->isKeyword($token->text)) {
            return TokenType::KEYWORD;
        }

        return TokenType::DEFAULT;
    }

    /**
     * Check if we're in a type hint context
     */
    private function isTypeHintContext(array $tokens, int $index): bool
    {
        // Look for : (return type) or function parameter context
        for ($i = $index - 1; $i >= max(0, $index - 5); $i--) {
            if ($tokens[$i]->text === ':') {
                return true;
            }
            if ($tokens[$i]->is([T_FUNCTION, T_FN])) {
                // Check if we're in parameter list
                $parenCount = 0;
                for ($j = $i; $j < $index; $j++) {
                    if ($tokens[$j]->text === '(') {
                        $parenCount++;
                    }
                    if ($tokens[$j]->text === ')') {
                        $parenCount--;
                    }
                }

                return $parenCount > 0;
            }
        }

        return false;
    }

    /**
     * Check if a string is a PHP keyword
     */
    private function isKeyword(string $text): bool
    {
        static $keywords = [
            'true', 'false', 'null', 'void', 'never', 'mixed',
            'int', 'float', 'string', 'bool', 'array', 'object',
            'callable', 'iterable', 'resource', 'self', 'parent', 'static',
        ];

        return in_array(strtolower($text), $keywords, true);
    }
}
