<?php

declare(strict_types=1);

namespace App\Framework\View;

use App\Framework\DI\Container;
use App\Framework\Template\Processing\AstTransformer;
use App\Framework\Template\Processing\StringProcessor;
use App\Framework\View\Dom\Parser\HtmlParser;
use App\Framework\View\Dom\Renderer\HtmlRenderer;
use App\Framework\View\Processing\AstProcessingPipeline;

final class TemplateProcessor
{
    private array $resolvedStringProcessors = [];
    private array $resolvedAstTransformers = [];

    private ?AstProcessingPipeline $astPipeline = null;
    private ?HtmlParser $parser = null;
    private ?HtmlRenderer $renderer = null;

    public function __construct(
        private readonly array $astTransformers,
        private readonly array $stringProcessors,
        private readonly Container $container,
        private readonly ?ProcessorChainOptimizer $chainOptimizer = null,
        private readonly ?CompiledTemplateCache $compiledTemplateCache = null,
        private readonly ?ProcessorPerformanceTracker $performanceTracker = null
    ) {
    }

    public function render(RenderContext $context, string $html, bool $component = false): string
    {
        // Step 1: Process strings FIRST (handles <for> loops, placeholders, etc.)
        $processedHtml = $this->processString($html, $context);

        // Step 2: Determine which AST transformers to use based on processing mode
        $transformersToUse = $this->getTransformersForMode($context->processingMode);

        // Skip AST processing if no transformers or MINIMAL mode
        if (empty($transformersToUse) || $context->processingMode === ProcessingMode::MINIMAL) {
            return $processedHtml;
        }

        // Step 3: Process through AST pipeline
        $this->initializeAstPipeline($transformersToUse);

        $document = $this->astPipeline->process($context, $processedHtml);

        // Step 4: Render AST back to HTML
        $this->renderer ??= new HtmlRenderer();
        $finalHtml = $this->renderer->render($document);

        // Step 5: Extract body content if component mode
        if ($component) {
            $finalHtml = $this->extractBodyContent($finalHtml);
        }

        return $finalHtml;
    }

    /**
     * Get AST transformers based on processing mode
     */
    private function getTransformersForMode(ProcessingMode $mode): array
    {
        return match ($mode) {
            ProcessingMode::FULL => $this->astTransformers,
            ProcessingMode::COMPONENT => $this->filterComponentTransformers($this->astTransformers),
            ProcessingMode::MINIMAL => [],
        };
    }

    /**
     * Filter transformers for component mode
     * (similar to COMPONENT_ALLOWED_PROCESSORS logic)
     */
    private function filterComponentTransformers(array $transformers): array
    {
        // For now, allow all transformers in component mode
        // This can be refined later based on requirements
        return $transformers;
    }

    /**
     * Initialize AST pipeline with resolved transformer instances
     */
    private function initializeAstPipeline(array $transformerClasses): void
    {
        $transformerInstances = [];

        foreach ($transformerClasses as $transformerClass) {
            $transformerInstances[] = $this->resolveAstTransformer($transformerClass);
        }

        $this->parser ??= new HtmlParser();
        $this->astPipeline = new AstProcessingPipeline($transformerInstances, $this->parser);
    }

    public function __debugInfo(): ?array
    {
        return [
            'astTransformers' => $this->astTransformers,
            'stringProcessors' => $this->stringProcessors,
        ];
    }

    private function processString(string $html, RenderContext $context): string
    {
        // Optimize processor chain if optimizer available
        $processors = $this->chainOptimizer !== null
            ? $this->optimizeProcessorChain($this->stringProcessors, $html)
            : $this->stringProcessors;

        // Process through String processors (optimized order)
        foreach ($processors as $processorClass) {
            $processor = $this->resolveStringProcessor($processorClass);

            // Optional: Performance Tracking
            if ($this->performanceTracker !== null) {
                $html = $this->performanceTracker->measure(
                    $processorClass,
                    fn () => $processor->process($html, $context)
                );
            } else {
                $html = $processor->process($html, $context);
            }
        }

        return $html;
    }

    /**
     * Optimize processor chain based on template content
     *
     * @param array<string> $processors Processor class names
     * @param string $html Template content
     * @return array<string> Optimized processor class names
     */
    private function optimizeProcessorChain(array $processors, string $html): array
    {
        if ($this->chainOptimizer === null) {
            return $processors;
        }

        // Convert class names to processor instances for optimization
        $processorInstances = array_map(
            fn (string $class) => $this->resolveStringProcessor($class),
            $processors
        );

        // Optimize and get back optimized instances
        $optimizedInstances = $this->chainOptimizer->optimize($processorInstances, $html);

        // Convert back to class names
        return array_map(
            fn ($processor) => $processor::class,
            $optimizedInstances
        );
    }

    /** @param class-string<StringProcessor> $processorClass */
    public function resolveStringProcessor(string $processorClass): StringProcessor
    {
        if (! isset($this->resolvedStringProcessors[$processorClass])) {
            $this->resolvedStringProcessors[$processorClass] = $this->container->get($processorClass);
        }

        return $this->resolvedStringProcessors[$processorClass];
    }

    /** @param class-string<AstTransformer> $transformerClass */
    private function resolveAstTransformer(string $transformerClass): AstTransformer
    {
        if (! isset($this->resolvedAstTransformers[$transformerClass])) {
            $this->resolvedAstTransformers[$transformerClass] = $this->container->get($transformerClass);
        }

        return $this->resolvedAstTransformers[$transformerClass];
    }

    private function extractBodyContent(string $html): string
    {
        // Extract content between <body> and </body> tags
        if (preg_match('/<body[^>]*>(.*?)<\/body>/is', $html, $matches)) {
            return $matches[1];
        }

        // Fallback: return full HTML if no body tag found
        return $html;
    }
}
