<?php

declare(strict_types=1);

namespace App\Framework\View\Processors;

use App\Framework\DI\Container;
use App\Framework\Meta\MetaData;
use App\Framework\Template\Expression\PlaceholderProcessor;
use App\Framework\Template\Processing\DomProcessor;
use App\Framework\View\DomWrapper;
use App\Framework\View\RenderContext;

final class ForProcessor implements DomProcessor
{
    private PlaceholderProcessor $placeholderProcessor;

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

    public function process(DomWrapper $dom, RenderContext $context): DomWrapper
    {
        // Debug: show a snippet of HTML around select elements
        $selects = $dom->document->querySelectorAll('select');
        foreach ($selects as $idx => $select) {
            $html = $dom->document->saveHTML($select);
            $snippet = substr($html, 0, 200);
        }

        // Debug: show all <for> elements in document
        $allForElements = $dom->document->querySelectorAll('for');
        foreach ($allForElements as $idx => $forEl) {
            $attrs = [];
            foreach ($forEl->attributes as $attr) {
                $attrs[] = $attr->name . '="' . $attr->value . '"';
            }
        }

        // Support both syntaxes: var/in (old) and items/as (new)
        $forNodesOld = $dom->document->querySelectorAll('for[var][in]');
        $forNodesNew = $dom->document->querySelectorAll('for[items][as]');

        // Support foreach attribute on any element: <tr foreach="$models as $model">
        $foreachNodes = $dom->document->querySelectorAll('[foreach]');


        // Merge all nodesets
        $forNodes = [];
        foreach ($forNodesOld as $node) {
            $forNodes[] = $node;
        }
        foreach ($forNodesNew as $node) {
            $forNodes[] = $node;
        }
        foreach ($foreachNodes as $node) {
            $forNodes[] = $node;
        }


        foreach ($forNodes as $node) {
            // Detect which syntax is being used
            if ($node->hasAttribute('foreach')) {
                // foreach attribute syntax: <tr foreach="$models as $model">
                $foreachExpr = $node->getAttribute('foreach');

                // Parse "array as var" syntax (with or without $ prefix)
                if (preg_match('/^\$?(\w+)\s+as\s+\$?(\w+)$/', $foreachExpr, $matches)) {
                    $in = $matches[1];
                    $var = $matches[2];
                } else {
                    // Invalid foreach syntax, skip this node
                    continue;
                }

                // Remove foreach attribute from element
                $node->removeAttribute('foreach');
            } elseif ($node->hasAttribute('items') && $node->hasAttribute('as')) {
                // New syntax: <for items="arrayName" as="itemVar">
                $in = $node->getAttribute('items');
                $var = $node->getAttribute('as');
            } else {
                // Old syntax: <for var="itemVar" in="arrayName">
                $var = $node->getAttribute('var');
                $in = $node->getAttribute('in');
            }
            $output = '';

            // Check if this was a foreach attribute (already removed)
            // We detect this by checking if node is NOT a <for> element
            $isForeachAttribute = !in_array(strtolower($node->tagName), ['for']);

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

            // Fallback to model if not found in data
            if ($items === null && isset($context->model)) {
                if (str_starts_with($in, 'model.')) {
                    $items = $this->resolveValue(['model' => $context->model], $in);
                } else {
                    $items = $this->resolveValue(['model' => $context->model], 'model.' . $in);
                }
            }

            if (is_iterable($items)) {
                foreach ($items as $item) {
                    $clone = $node->cloneNode(true);

                    // Create loop context with loop variable
                    $loopContext = new RenderContext(
                        template: $context->template,
                        metaData: new MetaData('', ''),
                        data: array_merge($context->data, [$var => $item]),
                        controllerClass: $context->controllerClass
                    );

                    // For foreach attribute: process the entire element
                    // For <for> element: process only innerHTML
                    if ($isForeachAttribute) {
                        // Process entire element (e.g., <tr>)
                        $innerHTML = $clone->outerHTML;
                    } else {
                        // Get innerHTML from cloned node
                        $innerHTML = $clone->innerHTML;

                        // Handle case where DOM parser treats <for> as self-closing
                        if (trim($innerHTML) === '') {
                            $innerHTML = $this->collectSiblingContent($node, $dom);
                        }
                    }

                    // Replace loop variable placeholders using PlaceholderProcessor
                    $innerHTML = $this->placeholderProcessor->processLoopVariable($innerHTML, $var, $item);

                    // Process placeholders in loop content
                    $placeholderReplacer = $this->container->get(PlaceholderReplacer::class);
                    $processedContent = $placeholderReplacer->process($innerHTML, $loopContext);

                    // Handle nested <for> tags recursively
                    if (str_contains($processedContent, '<for ')) {
                        try {
                            $tempWrapper = DomWrapper::fromString($processedContent);
                            $this->process($tempWrapper, $loopContext);
                            $processedContent = $tempWrapper->toHtml(true);
                        } catch (\Exception $e) {
                            // Continue with unprocessed content on error
                        }
                    }

                    $output .= $processedContent;
                }
            }

            // Replace for node with processed output
            if (! empty($output)) {
                try {
                    $replacement = $dom->document->createDocumentFragment();
                    @$replacement->appendXML($output);
                    $node->parentNode?->replaceChild($replacement, $node);
                } catch (\Exception $e) {
                    // Fallback: Use innerHTML approach
                    $tempDiv = $dom->document->createElement('div');
                    $tempDiv->innerHTML = $output;

                    $parent = $node->parentNode;
                    $nextSibling = $node->nextSibling;

                    $parent->removeChild($node);

                    while ($tempDiv->firstChild) {
                        if ($nextSibling) {
                            $parent->insertBefore($tempDiv->firstChild, $nextSibling);
                        } else {
                            $parent->appendChild($tempDiv->firstChild);
                        }
                    }
                }
            } else {
                // Remove empty for node
                $node->parentNode?->removeChild($node);
            }
        }

        return $dom;
    }

    /**
     * Resolves 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)) {
                // Try property access first
                if (isset($value->$key)) {
                    $value = $value->$key;
                } elseif (method_exists($value, $key)) {
                    // Try method call
                    $value = $value->$key();
                } elseif (method_exists($value, 'get' . ucfirst($key))) {
                    // Try getter method
                    $getterMethod = 'get' . ucfirst($key);
                    $value = $value->$getterMethod();
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        return $value;
    }

    /**
     * Collects content from sibling nodes when <for> is treated as self-closing
     */
    private function collectSiblingContent($forNode, DomWrapper $dom): string
    {
        $content = '';
        $currentNode = $forNode->nextSibling;

        while ($currentNode !== null) {
            if ($currentNode->nodeType === XML_ELEMENT_NODE) {
                // Check for loop content elements (TR for tables, DIV for other structures)
                if ($currentNode->tagName === 'TR') {
                    $content .= $dom->document->saveHTML($currentNode);

                    $nextNode = $currentNode->nextSibling;
                    $currentNode->parentNode->removeChild($currentNode);
                    $currentNode = $nextNode;

                    break; // One TR per iteration
                } elseif ($currentNode->tagName === 'TABLE') {
                    // Look for template TR inside table
                    $tableRows = $currentNode->querySelectorAll('tr');
                    foreach ($tableRows as $row) {
                        $rowHtml = $dom->document->saveHTML($row);
                        // Find row with placeholders (template row)
                        if (str_contains($rowHtml, '{{')) {
                            $content = $rowHtml;

                            break 2;
                        }
                    }
                    $currentNode = $currentNode->nextSibling;
                } else {
                    $currentNode = $currentNode->nextSibling;
                }
            } else {
                $currentNode = $currentNode->nextSibling;
            }
        }

        return $content;
    }
}
