<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Validation;

final class EventModelAuditor
{
    public function audit(string $basePath): array
    {
        $issues = [];
        $root = rtrim($basePath, DIRECTORY_SEPARATOR) . '/core/Platform/Event';

        if (!is_dir($root)) {
            return [['file' => $root, 'message' => 'Event subsystem directory does not exist.']];
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($root, \FilesystemIterator::SKIP_DOTS)
        );

        foreach ($iterator as $file) {
            if (!$file instanceof \SplFileInfo || !$file->isFile() || strtolower($file->getExtension()) !== 'php') {
                continue;
            }

            $relative = str_replace(rtrim($basePath, DIRECTORY_SEPARATOR) . '/', '', $file->getPathname());

            foreach ($this->findUnsupportedCallableProperties((string) file_get_contents($file->getPathname())) as $message) {
                $issues[] = [
                    'file' => $relative,
                    'message' => $message,
                ];
            }
        }

        return $issues;
    }

    private function findUnsupportedCallableProperties(string $source): array
    {
        $issues = [];
        $tokens = token_get_all($source);
        $count = count($tokens);

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

            if (!$this->isVisibilityToken($token)) {
                continue;
            }

            $statement = $this->collectStatement($tokens, $i);

            if (!$this->statementContainsVariable($statement)) {
                continue;
            }

            if ($this->statementContainsCallableType($statement)) {
                $issues[] = 'Unsupported callable property type detected: ' . trim($this->statementToString($statement));
            }
        }

        return $issues;
    }

    private function isVisibilityToken(mixed $token): bool
    {
        return is_array($token) && in_array($token[0], [T_PRIVATE, T_PROTECTED, T_PUBLIC], true);
    }

    private function collectStatement(array $tokens, int $start): array
    {
        $statement = [];
        $count = count($tokens);

        for ($i = $start; $i < $count; $i++) {
            $token = $tokens[$i];
            $statement[] = $token;

            if ($token === ';') {
                break;
            }

            if ($token === '{' || $token === '(') {
                break;
            }
        }

        return $statement;
    }

    private function statementContainsVariable(array $statement): bool
    {
        foreach ($statement as $token) {
            if (is_array($token) && $token[0] === T_VARIABLE) {
                return true;
            }
        }

        return false;
    }

    private function statementContainsCallableType(array $statement): bool
    {
        foreach ($statement as $token) {
            if (is_array($token) && strtolower($token[1]) === 'callable') {
                return true;
            }
        }

        return false;
    }

    private function statementToString(array $statement): string
    {
        $text = '';

        foreach ($statement as $token) {
            $text .= is_array($token) ? $token[1] : $token;
        }

        return preg_replace('/\s+/', ' ', $text) ?? $text;
    }
}
