<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Utility\Reflection;

use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use SplFileObject;

/**
 * @internal
 *
 * Imported from `doctrine/annotations`:
 * @link https://github.com/doctrine/annotations/blob/4858ab786a6cb568149209a9112dad3808c8a4de/lib/Doctrine/Common/Annotations/PhpParser.php
 */
final class PhpParser
{
    /** @var array<string, array<string, string>> */
    private static array $statements = [];

    /**
     * @param ReflectionClass<covariant object>|ReflectionFunction|ReflectionMethod $reflection
     * @return array<string, string>
     */
    public static function parseUseStatements(ReflectionClass|ReflectionFunction|ReflectionMethod $reflection): array
    {
        $signature = "{$reflection->getFileName()}:{$reflection->getStartLine()}";

        // @infection-ignore-all
        return self::$statements[$signature] ??= self::fetchUseStatements($reflection);
    }

    public static function parseNamespace(ReflectionFunction $reflection): ?string
    {
        $content = self::getFileContent($reflection);

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

        return (new NamespaceFinder())->findNamespace($content);
    }

    /**
     * @param ReflectionClass<covariant object>|ReflectionFunction|ReflectionMethod $reflection
     * @return array<string, string>
     */
    private static function fetchUseStatements(ReflectionClass|ReflectionFunction|ReflectionMethod $reflection): array
    {
        if ($reflection instanceof ReflectionMethod) {
            $namespaceName = $reflection->getDeclaringClass()->getNamespaceName();
        } elseif ($reflection instanceof ReflectionFunction && $reflection->getClosureScopeClass()) {
            $namespaceName = $reflection->getClosureScopeClass()->getNamespaceName();
        } else {
            $namespaceName = $reflection->getNamespaceName();
        }

        $content = self::getFileContent($reflection);

        if ($content === null) {
            return [];
        }

        return (new TokenParser($content))->parseUseStatements($namespaceName);
    }

    /**
     * @param ReflectionClass<covariant object>|ReflectionFunction|ReflectionMethod $reflection
     */    private static function getFileContent(ReflectionClass|ReflectionFunction|ReflectionMethod $reflection): ?string
    {
        $filename = $reflection->getFileName();
        $startLine = $reflection->getStartLine();

        // @infection-ignore-all these values will never be `true`
        if ($filename === false || $startLine === false) {
            return null;
        }

        if (! is_file($filename)) {
            return null;
        }

        // @infection-ignore-all no need to test with `-1`
        $lineCnt = 0;
        $content = '';
        $file = new SplFileObject($filename);

        while (! $file->eof()) {
            if ($lineCnt++ === $startLine) {
                break;
            }

            $content .= $file->fgets();
        }

        return $content;
    }
}
