<?php

declare(strict_types=1);

namespace App\Framework\View\Dom\Transformer;

use App\Framework\Template\Processing\AstTransformer;
use App\Framework\View\Dom\DocumentNode;
use App\Framework\View\Dom\ElementNode;
use App\Framework\View\Dom\Node;
use App\Framework\View\Dom\TextNode;
use App\Framework\View\RenderContext;

/**
 * WhitespaceCleanupTransformer - Removes empty text nodes from AST
 *
 * Recursively removes TextNode instances that contain only whitespace.
 * This includes empty lines, spaces-only nodes, etc.
 *
 * Before:  <div>   \n   <p>text</p>   \n   </div>
 * After:   <div><p>text</p></div>
 *
 * Preserves whitespace in whitespace-sensitive elements like <pre>, <code>, <textarea>
 *
 * Framework Pattern: readonly class, AST-based transformation
 */
final readonly class WhitespaceCleanupTransformer implements AstTransformer
{
    /**
     * Tags where whitespace should be preserved
     */
    private const WHITESPACE_SENSITIVE_TAGS = [
        'pre',
        'code',
        'textarea',
        'script',
        'style',
    ];

    public function transform(DocumentNode $document, RenderContext $context): DocumentNode
    {
        // Process all children recursively
        $this->removeEmptyTextNodes($document, false);

        return $document;
    }

    /**
     * Recursively remove text nodes containing only whitespace
     *
     * @param bool $preserveWhitespace Whether to preserve whitespace (inside whitespace-sensitive tags)
     */
    private function removeEmptyTextNodes(Node $node, bool $preserveWhitespace): void
    {
        if (! $node instanceof ElementNode && ! $node instanceof DocumentNode) {
            return;
        }

        // Check if this element is whitespace-sensitive
        // Once we're inside a whitespace-sensitive element, ALL descendants should preserve whitespace
        $isWhitespaceSensitive = $preserveWhitespace;
        if ($node instanceof ElementNode) {
            $tagName = strtolower($node->getTagName());

            // Check if tag is in whitespace-sensitive list
            if (in_array($tagName, self::WHITESPACE_SENSITIVE_TAGS, true)) {
                $isWhitespaceSensitive = true;
            }

            // Special case: <span class="code"> is also whitespace-sensitive (syntax highlighting)
            if ($tagName === 'span' && $node->hasAttribute('class')) {
                $classes = explode(' ', $node->getAttribute('class'));
                if (in_array('code', $classes, true)) {
                    $isWhitespaceSensitive = true;
                }
            }
        }

        // Get children via public API
        $children = $node->getChildren();

        // Remove empty TextNode children (but not if we're in whitespace-sensitive context)
        if (! $isWhitespaceSensitive) {
            foreach ($children as $child) {
                if ($this->isEmptyTextNode($child)) {
                    $node->removeChild($child);
                }
            }
        }

        // Recurse into remaining children
        // IMPORTANT: Pass $isWhitespaceSensitive to children so they inherit the parent's context
        foreach ($node->getChildren() as $child) {
            $this->removeEmptyTextNodes($child, $isWhitespaceSensitive);
        }
    }

    /**
     * Check if node is a TextNode containing only whitespace
     */
    private function isEmptyTextNode(Node $node): bool
    {
        if (! $node instanceof TextNode) {
            return false;
        }

        // Check if content is only whitespace via public API
        return preg_match('/^\s*$/', $node->getTextContent()) === 1;
    }
}
