<?php

declare(strict_types=1);

namespace App\Framework\View\Dom\Transformer;

use App\Framework\DI\Container;
use App\Framework\Meta\MetaData;
use App\Framework\Template\Expression\PlaceholderProcessor;
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\Processors\PlaceholderReplacer;
use App\Framework\View\RenderContext;

/**
 * ForTransformer - AST-based foreach loop processor using PlaceholderProcessor
 *
 * Processes:
 * - foreach attributes: <div foreach="$items as $item">
 * - <for> elements: <for items="items" as="item">
 *
 * Uses PlaceholderProcessor for consistent placeholder replacement with ExpressionEvaluator
 */
final readonly class ForTransformer implements AstTransformer
{
    private PlaceholderProcessor $placeholderProcessor;

    public function __construct(
        private Container $container
    ) {
        $this->placeholderProcessor = new PlaceholderProcessor();
    }

    public function transform(DocumentNode $document, RenderContext $context): DocumentNode
    {
        $this->processForLoops($document, $context);
        return $document;
    }

    private function processForLoops(Node $node, RenderContext $context): void
    {
        if (!$node instanceof ElementNode && !$node instanceof DocumentNode) {
            return;
        }

        // Process children first (depth-first for nested loops)
        $children = $node->getChildren();
        foreach ($children as $child) {
            $this->processForLoops($child, $context);
        }

        // Process foreach attribute on this element
        if ($node instanceof ElementNode && $node->hasAttribute('foreach')) {
            $this->processForeachAttribute($node, $context);
        }

        // Process <for> elements
        if ($node instanceof ElementNode && $node->getTagName() === 'for') {
            $this->processForElement($node, $context);
        }
    }

    /**
     * Process foreach attribute: <div foreach="$items as $item">
     */
    private function processForeachAttribute(ElementNode $node, RenderContext $context): void
    {
        $foreachExpr = $node->getAttribute('foreach');

        // Parse "array as var" syntax (with or without $ prefix)
        if (!preg_match('/^\$?(\w+)\s+as\s+\$?(\w+)$/', $foreachExpr, $matches)) {
            return; // Invalid syntax
        }

        $dataKey = $matches[1];
        $varName = $matches[2];

        // Remove foreach attribute
        $node->removeAttribute('foreach');

        // Resolve items from context
        $items = $this->resolveValue($context->data, $dataKey);

        if (!is_iterable($items)) {
            // Remove element if not iterable
            $parent = $node->getParent();
            if ($parent instanceof ElementNode || $parent instanceof DocumentNode) {
                $parent->removeChild($node);
            }
            return;
        }

        // Get parent and position
        $parent = $node->getParent();
        if (!($parent instanceof ElementNode || $parent instanceof DocumentNode)) {
            return;
        }

        // Clone and process for each item
        $fragments = [];
        foreach ($items as $item) {
            $clone = $node->clone();

            // Process placeholders in cloned element
            $this->replacePlaceholdersInNode($clone, $varName, $item);

            $fragments[] = $clone;
        }

        // Replace original node with all fragments
        $parent->removeChild($node);

        foreach ($fragments as $fragment) {
            $parent->appendChild($fragment);
        }
    }

    /**
     * Process <for> element: <for items="items" as="item">
     */
    private function processForElement(ElementNode $node, RenderContext $context): void
    {
        // Support both syntaxes
        $dataKey = $node->getAttribute('items') ?? $node->getAttribute('in');
        $varName = $node->getAttribute('as') ?? $node->getAttribute('var');

        if (!$dataKey || !$varName) {
            return; // Invalid syntax
        }

        // Resolve items from context
        $items = $this->resolveValue($context->data, $dataKey);

        if (!is_iterable($items)) {
            // Remove element if not iterable
            $parent = $node->getParent();
            if ($parent instanceof ElementNode || $parent instanceof DocumentNode) {
                $parent->removeChild($node);
            }
            return;
        }

        // Get parent and position
        $parent = $node->getParent();
        if (!($parent instanceof ElementNode || $parent instanceof DocumentNode)) {
            return;
        }

        // Process children for each item
        $fragments = [];
        foreach ($items as $item) {
            foreach ($node->getChildren() as $child) {
                $clone = $child->clone();

                // Process placeholders
                $this->replacePlaceholdersInNode($clone, $varName, $item);

                $fragments[] = $clone;
            }
        }

        // Replace <for> element with processed fragments
        $parent->removeChild($node);

        foreach ($fragments as $fragment) {
            $parent->appendChild($fragment);
        }
    }

    /**
     * Replace placeholders in a node and its children using PlaceholderProcessor
     */
    private function replacePlaceholdersInNode(Node $node, string $varName, mixed $item): void
    {
        if ($node instanceof TextNode) {
            // Process text content with PlaceholderProcessor
            $node->setText(
                $this->placeholderProcessor->processLoopVariable($node->getTextContent(), $varName, $item)
            );
            return;
        }

        if ($node instanceof ElementNode) {
            // Process attributes - HTML decode first to handle entity-encoded quotes
            foreach (array_keys($node->getAttributes()) as $attrName) {
                $attrValue = $node->getAttribute($attrName);
                if ($attrValue !== null) {
                    // Decode HTML entities (&#039; -> ', &quot; -> ")
                    $decodedValue = html_entity_decode($attrValue, ENT_QUOTES | ENT_HTML5, 'UTF-8');

                    // Process placeholders with decoded value
                    $processedValue = $this->placeholderProcessor->processLoopVariable($decodedValue, $varName, $item);

                    // Set the processed value (will be re-encoded if needed during rendering)
                    $node->setAttribute($attrName, $processedValue);
                }
            }

            // Process children recursively
            foreach ($node->getChildren() as $child) {
                $this->replacePlaceholdersInNode($child, $varName, $item);
            }
        }
    }

    /**
     * Resolve nested property paths like "redis.key_sample"
     */
    private function resolveValue(array $data, string $expr): mixed
    {
        $keys = explode('.', $expr);
        $value = $data;

        foreach ($keys as $key) {
            if (is_array($value) && array_key_exists($key, $value)) {
                $value = $value[$key];
            } elseif (is_object($value)) {
                if (isset($value->$key)) {
                    $value = $value->$key;
                } elseif (method_exists($value, $key)) {
                    $value = $value->$key();
                } elseif (method_exists($value, 'get' . ucfirst($key))) {
                    $getterMethod = 'get' . ucfirst($key);
                    $value = $value->$getterMethod();
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        return $value;
    }
}
