<?php

declare(strict_types=1);

namespace Codelicia\Xulieta\Validator;

use Codelicia\Xulieta\ValueObject\SampleCode;
use Codelicia\Xulieta\ValueObject\Violation;
use LogicException;
use Override;
use PhpParser\Parser as PhpParser;
use PhpParser\ParserFactory;
use PhpParser\PhpVersion;
use Throwable;

use function preg_match;

use const PHP_EOL;

/** @psalm-suppress UnusedClass */
final class PhpValidator implements Validator
{
    private PhpParser $phpParser;

    public function __construct(PhpParser|null $phpParser = null)
    {
        $this->phpParser = $phpParser ?? new ParserFactory()->createForVersion(PhpVersion::getNewestSupported());
    }

    #[Override]
    public function supports(SampleCode $sampleCode): bool
    {
        return $sampleCode->language === 'php';
    }

    #[Override]
    public function hasViolation(SampleCode $sampleCode): bool
    {
        try {
            $this->phpParser->parse(
                $this->ensureCodePrefix($sampleCode->code),
            );
        } catch (Throwable) {
            return true;
        }

        return false;
    }

    #[Override]
    public function getViolation(SampleCode $sampleCode): Violation
    {
        try {
            $this->phpParser->parse($this->ensureCodePrefix($sampleCode->code));
        } catch (Throwable $e) {
            preg_match('{on line (\d+)}', $e->getMessage(), $line);

            $validationErrorInLine = $line[1] ?? 0;

            return new Violation($sampleCode, $e->getMessage(), (int) $validationErrorInLine, self::class);
        }

        throw new LogicException();
    }

    private function ensureCodePrefix(string $code): string
    {
        if (! preg_match('/<\?(=|php)/i', $code)) {
            return '<?php ' . PHP_EOL . $code;
        }

        return $code;
    }
}
