<?php

namespace RTMMailComposer\ConditionProcessor;

use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Twig\Error\SyntaxError;

class ConditionProcessor implements ConditionProcessorInterface
{

    /**
     * @var ConditionFunctionInterface[]
     */
    protected array $conditionFunctions = [];

    public function __construct(protected ConditionExpressionLanguage $language = new ConditionExpressionLanguage())
    {
    }

    /**
     * Registers a new function provider
     * @param ExpressionFunctionProviderInterface $fpi
     * @return void
     */
    protected function registerFunctionProvider(ExpressionFunctionProviderInterface $fpi)
    {
        $this->language->getLanguage()->registerProvider($fpi);
    }

    protected function registerFunction(ExpressionFunction $function)
    {
        $this->language->getLanguage()->addFunction($function);
    }

    public function registerConditionFunction(ConditionFunctionInterface $ci): void
    {
        $this->conditionFunctions[$ci->getName()] = $ci;
        $this->registerFunction(new ExpressionFunction(
            $ci->getName(),
            fn() => '',
            $ci->evaluate(...)
        ));
    }

    public function validate(string $expression, array $parameters): bool
    {
        try {
            // try to lint the expression
            $this->language->getLanguage()->lint($expression, array_keys($parameters));
        } catch (SyntaxError) {
            // always fail on syntax error
            return false;
        }

        return true;
    }

    /**
     * @inheritDoc
     */
    public function process(array $conditionFunction, array $parameters): bool
    {
        // build expression
        $function = $conditionFunction['function'] ?? '';
        if (!isset($this->conditionFunctions[$function])) {
            throw new ConditionProcessorException("Function $function not registered",
                ConditionProcessorException::INVALID_FUNCTION);
        }

        $functionArguments = $conditionFunction['arguments'];
        $orderedArguments = [];
        // validate arguments
        foreach ($this->conditionFunctions[$function]->getParameters() as $key => $schema) {
            if (!isset($functionArguments[$key]) ||
                !rest_validate_value_from_schema($functionArguments[$key], $schema, $key)) {
                throw new ConditionProcessorException("Invalid argument $key for function $function",
                    ConditionProcessorException::INVALID_PARAMETERS
                );
            }

            $orderedArguments[$key] = $functionArguments[$key];
        }

        $expression = $function . '(' . implode(',', array_keys($orderedArguments)) . ')';

        try {
            return $this->language->evaluate($expression, $parameters + $orderedArguments);
        } catch (SyntaxError) {
            // always fail on syntax error
            return false;
        }
    }
}
