<?php

declare(strict_types=1);

namespace App\Framework\Template\Expression;

use App\Framework\View\RawHtml;

/**
 * PlaceholderProcessor - Processes template placeholders using ExpressionEvaluator
 *
 * Replaces placeholders like {{ $var }}, {{ $var['key'] }}, {{ $obj->prop }} with actual values
 * Uses ExpressionEvaluator for consistent expression evaluation across the framework
 *
 * Supports:
 * - Simple variables: {{ $name }}, {{ name }}
 * - Array access: {{ $user['email'] }}, {{ $items[0] }}
 * - Object properties: {{ $user->name }}, {{ $date->format }}
 * - Dot notation: {{ user.name }}, {{ items.0 }}
 * - Expressions: {{ $count > 0 }}, {{ $user->isAdmin }}
 *
 * Framework Pattern: readonly class, composition with ExpressionEvaluator
 */
final readonly class PlaceholderProcessor
{
    private ExpressionEvaluator $evaluator;

    public function __construct()
    {
        $this->evaluator = new ExpressionEvaluator();
    }

    /**
     * Replace all placeholders in HTML content
     *
     * @param string $html HTML content with placeholders
     * @param array $context Variable context
     * @return string HTML with replaced placeholders
     */
    public function process(string $html, array $context): string
    {
        // Pattern matches {{ expression }} with optional whitespace
        $pattern = '/{{\\s*(.+?)\\s*}}/';

        return preg_replace_callback(
            $pattern,
            function ($matches) use ($context) {
                $expression = $matches[1];

                // Evaluate expression using ExpressionEvaluator
                $value = $this->evaluator->evaluate($expression, $context);

                // Format value for HTML output
                return $this->formatValue($value);
            },
            $html
        );
    }

    /**
     * Replace placeholders for a specific loop variable
     *
     * Useful in foreach loops where we want to replace only the loop variable placeholders
     * and leave other placeholders for later processing
     *
     * @param string $html HTML content
     * @param string $varName Loop variable name (without $)
     * @param mixed $item Loop item value
     * @return string HTML with loop variable placeholders replaced
     */
    public function processLoopVariable(string $html, string $varName, mixed $item): string
    {
        // Pattern 1: Array access {{ $varName['property'] }} or {{ varName['property'] }} ($ optional)
        // We need to handle both escaped (&quot;, &#039;) and unescaped quotes
        $arrayPatternDouble = '/{{\\s*\\$?' . preg_quote($varName, '/') . '\\[(?:&quot;|")([^"&]+?)(?:&quot;|")\\]\\s*}}/';
        $arrayPatternSingle = '/{{\\s*\\$?' . preg_quote($varName, '/') . '\\[(?:&#039;|\')([^\'&]+?)(?:&#039;|\')\\]\\s*}}/';

        // Pattern 2: Object property {{ $varName->property }} or {{ varName->property }} ($ optional)
        $objectPattern = '/{{\\s*\\$?' . preg_quote($varName, '/') . '->([\\w]+)\\s*}}/';

        // Pattern 3: Dot notation {{ varName.property }} or {{ $varName.property }} ($ already optional)
        $dotPattern = '/{{\\s*\\$?' . preg_quote($varName, '/') . '\\.([\\w]+)\\s*}}/';

        // Pattern 4: Simple variable {{ $varName }} or {{ varName }} ($ optional)
        $simplePattern = '/{{\\s*\\$?' . preg_quote($varName, '/') . '\\s*}}/';

        // Replace in order: array access (double quotes), array access (single quotes), object property, dot notation, simple variable
        $html = preg_replace_callback(
            $arrayPatternDouble,
            function($matches) use ($item) {
                return $this->formatValue($this->getProperty($item, $matches[1]));
            },
            $html
        );

        $html = preg_replace_callback(
            $arrayPatternSingle,
            function($matches) use ($item) {
                return $this->formatValue($this->getProperty($item, $matches[1]));
            },
            $html
        );

        $html = preg_replace_callback(
            $objectPattern,
            function($matches) use ($item) {
                return $this->formatValue($this->getProperty($item, $matches[1]));
            },
            $html
        );

        $html = preg_replace_callback(
            $dotPattern,
            function($matches) use ($item) {
                return $this->formatValue($this->getProperty($item, $matches[1]));
            },
            $html
        );

        $html = preg_replace_callback(
            $simplePattern,
            function($matches) use ($item) {
                return $this->formatValue($item);
            },
            $html
        );

        return $html;
    }

    /**
     * Get property value from item (array or object)
     */
    private function getProperty(mixed $item, string $property): mixed
    {
        if (is_array($item) && array_key_exists($property, $item)) {
            return $item[$property];
        }

        if (is_object($item) && isset($item->$property)) {
            return $item->$property;
        }

        return null;
    }

    /**
     * Format value for HTML output
     */
    private function formatValue(mixed $value): string
    {
        if ($value === null) {
            return '';
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if ($value instanceof RawHtml) {
            return $value->content;
        }

        if (is_array($value) || is_object($value)) {
            // Don't render complex types, return empty string
            return '';
        }

        return htmlspecialchars((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}
