<?php

namespace BitApps\PiPro\src\MixInputFunctions;

// Prevent direct script access
if (!\defined('ABSPATH')) {
    exit;
}

use Exception;

/**
 * Class MathParser
 * Supported Operations:
 * + - Addition
 * - - Subtraction
 * * - Multiplication
 * / - Division
 * % - Modulo (remainder)
 * ** - Exponentiation (power)
 * ^ - Exponentiation (converted to **)
 * () - Parentheses for grouping
 * % - Percentage - when used after a number.
 */
class MathParser
{
    private const MAX_EXPRESSION_LENGTH = 1000;

    private const MAX_PARENTHESES_DEPTH = 10;

    private $tokens = [];

    private $position = 0;

    /**
     * Parse and evaluate a mathematical expression safely.
     *
     * @param string $expression
     *
     * @return float|string
     */
    public static function evaluate($expression)
    {
        $parser = new self();

        return $parser->parse($expression);
    }

    /**
     * Parse the expression.
     *
     * @param string $expression
     *
     * @return float|string
     */
    public function parse($expression)
    {
        // Validate input
        if (!$this->validateExpression($expression)) {
            return '';
        }

        // Clean and prepare expression
        $expression = $this->prepareExpression($expression);

        // Tokenize
        $this->tokens = $this->tokenize($expression);
        $this->position = 0;

        try {
            $result = $this->parseExpression();

            return is_numeric($result) ? $result : '';
        } catch (Exception $e) {
            return '';
        }
    }

    /**
     * Validate expression for security.
     *
     * @param string $expression
     *
     * @return bool
     */
    private function validateExpression($expression)
    {
        if (!\is_string($expression) || empty($expression)) {
            return false;
        }

        // Length limit
        if (\strlen($expression) > self::MAX_EXPRESSION_LENGTH) {
            return false;
        }

        if (!preg_match('/^[0-9+\-*\/%\(\)\.\^*\s]+$/', $expression)) {
            return false;
        }

        if (substr_count($expression, '(') > self::MAX_PARENTHESES_DEPTH) {
            return false;
        }

        // Check for balanced parentheses
        $openCount = substr_count($expression, '(');

        $closeCount = substr_count($expression, ')');

        if ($openCount !== $closeCount) {
            return false;
        }

        // Check for empty parentheses
        return !(strpos($expression, '()') !== false);
    }

    /**
     * Prepare expression for parsing.
     *
     * @param string $expression
     *
     * @return string
     */
    private function prepareExpression($expression)
    {
        $expression = str_replace(' ', '', $expression);

        $expression = preg_replace('/(\d+(?:\.\d+)?)%(?!\d)/', '($1/100)', $expression);

        return str_replace('^', '**', $expression);
    }

    /**
     * Tokenize the expression.
     *
     * @param string $expression
     *
     * @return array
     */
    private function tokenize($expression)
    {
        $tokens = [];
        $i = 0;
        $len = \strlen($expression);

        while ($i < $len) {
            $char = $expression[$i];

            if (is_numeric($char) || $char === '.') {
                // Number token
                $number = '';
                while ($i < $len && (is_numeric($expression[$i]) || $expression[$i] === '.')) {
                    $number .= $expression[$i];
                    ++$i;
                }
                $tokens[] = ['type' => 'number', 'value' => (float) $number];
            } elseif ($char === '+' || $char === '-') {
                // Plus or minus
                $tokens[] = ['type' => 'operator', 'value' => $char];
                ++$i;
            } elseif ($char === '*') {
                // Multiplication or exponentiation
                if ($i + 1 < $len && $expression[$i + 1] === '*') {
                    $tokens[] = ['type' => 'operator', 'value' => '**'];
                    $i += 2;
                } else {
                    $tokens[] = ['type' => 'operator', 'value' => '*'];
                    ++$i;
                }
            } elseif ($char === '/' || $char === '%') {
                // Division or modulo
                $tokens[] = ['type' => 'operator', 'value' => $char];
                ++$i;
            } elseif ($char === '(') {
                $tokens[] = ['type' => 'paren', 'value' => '('];
                ++$i;
            } elseif ($char === ')') {
                $tokens[] = ['type' => 'paren', 'value' => ')'];
                ++$i;
            } else {
                ++$i;
            }
        }

        return $tokens;
    }

    /**
     * Parse expression with operator precedence.
     *
     * @return float
     */
    private function parseExpression()
    {
        $left = $this->parseTerm();

        while ($this->position < \count($this->tokens)) {
            $token = $this->tokens[$this->position];
            if ($token['type'] === 'operator' && ($token['value'] === '+' || $token['value'] === '-')) {
                ++$this->position;
                $right = $this->parseTerm();
                $left = $this->applyOperator($left, $token['value'], $right);
            } else {
                break;
            }
        }

        return $left;
    }

    /**
     * Parse term (multiplication, division, modulo).
     *
     * @return float
     */
    private function parseTerm()
    {
        $left = $this->parseFactor();

        while ($this->position < \count($this->tokens)) {
            $token = $this->tokens[$this->position];
            if ($token['type'] === 'operator' && ($token['value'] === '*' || $token['value'] === '/' || $token['value'] === '%')) {
                ++$this->position;
                $right = $this->parseFactor();
                $left = $this->applyOperator($left, $token['value'], $right);
            } else {
                break;
            }
        }

        return $left;
    }

    /**
     * Parse factor (exponentiation, parentheses, numbers).
     *
     * @return float
     */
    private function parseFactor()
    {
        $left = $this->parsePrimary();

        if ($this->position < \count($this->tokens)) {
            $token = $this->tokens[$this->position];
            if ($token['type'] === 'operator' && $token['value'] === '**') {
                ++$this->position;
                $right = $this->parseFactor(); // Recursive call for right-associativity

                return $this->applyOperator($left, '**', $right);
            }
        }

        return $left;
    }

    /**
     * Parse primary (numbers, parentheses).
     *
     * @return float
     */
    private function parsePrimary()
    {
        $token = $this->tokens[$this->position];

        if ($token['type'] === 'operator' && ($token['value'] === '+' || $token['value'] === '-')) {
            $op = $token['value'];
            ++$this->position;
            $operand = $this->parseFactor();
            if ($op === '-') {
                return -$operand;
            }

            return $operand;
        }

        if ($token['type'] === 'number') {
            ++$this->position;

            return $token['value'];
        }
        if ($token['type'] === 'paren' && $token['value'] === '(') {
            ++$this->position; // consume '('
            $result = $this->parseExpression();
            if ($this->position >= \count($this->tokens) || $this->tokens[$this->position]['value'] !== ')') {
                throw new Exception('Unmatched parentheses');
            }
            ++$this->position; // consume ')'

            return $result;
        }

        throw new Exception('Unexpected token: ' . $token['value']);
    }

    /**
     * Apply operator to operands.
     *
     * @param float $left
     * @param string $operator
     * @param float $right
     *
     * @return float
     */
    private function applyOperator($left, $operator, $right)
    {
        switch ($operator) {
            case '+':
                return $left + $right;

            case '-':
                return $left - $right;

            case '*':
                return $left * $right;

            case '/':
                if ($right === 0) {
                    throw new Exception('Division by zero');
                }

                return $left / $right;

            case '%':
                if ($right === 0) {
                    throw new Exception('Modulo by zero');
                }

                return $left % $right;

            case '**':
                return pow($left, $right);


            default:
                throw new Exception('Unknown operator: ' . $operator);
        }
    }
}
