<?php

declare(strict_types=1);

namespace App\Framework\SyntaxHighlighter;

use App\Framework\SyntaxHighlighter\Formatters\ConsoleFormatter;
use App\Framework\SyntaxHighlighter\Formatters\FormatterInterface;
use App\Framework\SyntaxHighlighter\Formatters\HtmlFormatter;
use App\Framework\Tokenizer\PhpTokenizer;
use App\Framework\Tokenizer\ValueObjects\TokenCollection;

/**
 * Modern syntax highlighter using the new tokenizer module
 */
final class SyntaxHighlighter
{
    private readonly PhpTokenizer $tokenizer;

    private array $formatters = [];

    public function __construct(
        ?PhpTokenizer $tokenizer = null
    ) {
        $this->tokenizer = $tokenizer ?? new PhpTokenizer();
        $this->registerDefaultFormatters();
    }

    /**
     * Register default formatters
     */
    private function registerDefaultFormatters(): void
    {
        $this->formatters['html'] = new HtmlFormatter();
        $this->formatters['console'] = new ConsoleFormatter();
    }

    /**
     * Highlight code with specified formatter
     */
    public function highlight(string $code, string $format = 'html', array $options = []): string
    {
        if (! isset($this->formatters[$format])) {
            throw new \InvalidArgumentException("Unknown formatter: {$format}");
        }

        $tokens = $this->tokenizer->tokenize($code);

        return $this->formatters[$format]->format($tokens, $options);
    }

    /**
     * Get a specific formatter
     */
    public function getFormatter(string $name): FormatterInterface
    {
        if (! isset($this->formatters[$name])) {
            throw new \InvalidArgumentException("Unknown formatter: {$name}");
        }

        return $this->formatters[$name];
    }

    /**
     * Register a custom formatter
     */
    public function registerFormatter(string $name, FormatterInterface $formatter): self
    {
        $this->formatters[$name] = $formatter;

        return $this;
    }

    /**
     * Tokenize code without formatting
     */
    public function tokenize(string $code): TokenCollection
    {
        return $this->tokenizer->tokenize($code);
    }

    /**
     * Highlight with CSS included
     */
    public function highlightWithCss(string $code, array $options = []): string
    {
        $options['includeCss'] = true;

        return $this->highlight($code, 'html', $options);
    }

    /**
     * Highlight without CSS
     */
    public function highlightWithoutCss(string $code, array $options = []): string
    {
        $options['includeCss'] = false;

        return $this->highlight($code, 'html', $options);
    }

    /**
     * Get CSS for HTML formatter
     */
    public function getCss(array $options = []): string
    {
        $formatter = $this->getFormatter('html');
        if (method_exists($formatter, 'getCss')) {
            return $formatter->getCss($options);
        }

        return '';
    }

    /**
     * Highlight with line offset
     */
    public function highlightWithOffset(string $code, int $lineOffset, array $options = []): string
    {
        $options['lineOffset'] = $lineOffset;

        return $this->highlight($code, 'html', $options);
    }

    /**
     * Highlight a file segment
     */
    public function highlightFileSegment(string $code, int $startLineInFile, array $options = []): string
    {
        $options['lineOffset'] = $startLineInFile - 1;

        return $this->highlight($code, 'html', $options);
    }

    /**
     * Stream highlight for large files
     */
    public function highlightStream(string $filePath, string $format = 'html', array $options = []): \Generator
    {
        if (! isset($this->formatters[$format])) {
            throw new \InvalidArgumentException("Unknown formatter: {$format}");
        }

        $formatter = $this->formatters[$format];

        foreach ($this->tokenizer->tokenizeStream($filePath) as $token) {
            yield $formatter->formatSingle($token, $options);
        }
    }

    /**
     * Reset CSS output flag for HTML formatter (backward compatibility)
     */
    public function resetCssOutput(): self
    {
        $formatter = $this->getFormatter('html');
        if (method_exists($formatter, 'resetCssOutput')) {
            $formatter::resetCssOutput();
        }

        return $this;
    }
}
