<?php

namespace Miruni\Merge_Syntax;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

use Miruni\Merge_Syntax\Handlers\Miruni_Attachment_Url_Preview_Handler;

/**
 * Processor: finds Miruni merge tokens in strings and replaces them using handlers.
 */
class Miruni_Merge_Processor
{
    /** @var Miruni_Merge_Handler[] */
    private array $handlers = [];

    /**
     * Optionally accept pre-constructed handlers.
     * If the attachment handler class exists it will be auto-registered.
     *
     * @param Miruni_Merge_Handler[] $handlers
     */
    public function __construct(array $handlers = [])
    {
        foreach ($handlers as $h) {
            $this->register_handler($h);
        }

        // Auto-register the attachment preview handler when available
        if (class_exists(Miruni_Attachment_Url_Preview_Handler::class)) {
            $this->register_handler(new Miruni_Attachment_Url_Preview_Handler());
        }
    }

    public function register_handler(Miruni_Merge_Handler $handler): void
    {
        $this->handlers[] = $handler;
    }

    /**
     * Process a string, replacing all Miruni merge tokens with handler output.
     *
     * @param string $text
     * @param array<string,mixed> $context Optional context passed to handlers
     * @return string
     */
    public function process(string $text, array $context = []): string
    {
        $pattern = '/\{\{\s*MIRUNI:([A-Z_]+)([^}]*)\}\}/';
        if (!preg_match_all($pattern, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
            return $text;
        }


        $out = '';
        $lastPos = 0;
        foreach ($matches as $m) {
            error_log("Processing match: " . json_encode($m));
            $full = $m[0][0];
            $pos = $m[0][1];
            $action = $m[1][0];
            $argString = trim($m[2][0]);

            // Append text before the token
            $out .= substr($text, $lastPos, $pos - $lastPos);

            // Parse args (same rules as the parser)
            $args = [];
            if ($argString !== '') {
                $argPattern = <<<'PAT'
/([a-zA-Z0-9_]+)=("(?:[^"\\]|\\.)*"|'(?:(?:[^'\\]|\\.)*)'|[^\s]+)/
PAT;
                if (preg_match_all($argPattern, $argString, $argMatches, PREG_SET_ORDER)) {
                    foreach ($argMatches as $am) {
                        $key = $am[1];
                        $val = $am[2];

                        if ((str_starts_with($val, '"') && str_ends_with($val, '"')) || (str_starts_with($val, "'") && str_ends_with($val, "'"))) {
                            $val = substr($val, 1, -1);
                            $val = stripcslashes($val);
                        }

                        $args[$key] = $val;
                    }
                }
            }

            // Attempt to create an operation (may throw on unknown enum)
            try {
                $op = new Miruni_Merge_SyntaxOperation($action, $args);
            } catch (\ValueError $e) {
                // Unknown action: keep original token
                $out .= $full;
                $lastPos = $pos + strlen($full);
                continue;
            }

            // Find a handler
            $handled = false;
            foreach ($this->handlers as $handler) {
                if ($handler->supports($op->get_action())) {
                    $res = $handler->execute($op, $context);
                    // We are building $out incrementally. The previous logic incorrectly
                    // replaced the entire original $text each time, discarding earlier
                    // processed segments. We only need to append the replacement result
                    // for this token since the preceding unmodified text has already
                    // been appended above.
                    $out .= $res;
                    $handled = true;
                    break;
                }
            }

            if (!$handled) {
                // No handler: keep original token
                $out .= $full;
            }

            $lastPos = $pos + strlen($full);
        }

        // Append remainder
        $out .= substr($text, $lastPos);

        return $out;
    }
}
