<?php

namespace HelpGent\Gettext\Utils;

class JsFunctionsScanner extends FunctionsScanner
{
    protected $code;
    protected $status = [];
    /**
     * Constructor.
     *
     * @param string $code The php code to scan
     */
    public function __construct($code)
    {
        // Normalize newline characters
        $this->code = \str_replace(["\r\n", "\n\r", "\r"], "\n", $code);
    }
    /**
     * {@inheritdoc}
     */
    public function getFunctions(array $constants = [])
    {
        $length = \strlen($this->code);
        $line = 1;
        $buffer = '';
        $functions = [];
        $bufferFunctions = [];
        $char = null;
        for ($pos = 0; $pos < $length; ++$pos) {
            $prev = $char;
            $char = $this->code[$pos];
            $next = isset($this->code[$pos + 1]) ? $this->code[$pos + 1] : null;
            switch ($char) {
                case '\\':
                    switch ($this->status()) {
                        case 'simple-quote':
                            if ($next !== "'") {
                                break 2;
                            }
                            break;
                        case 'double-quote':
                            if ($next !== '"') {
                                break 2;
                            }
                            break;
                        case 'back-tick':
                            if ($next !== '`') {
                                break 2;
                            }
                            break;
                    }
                    $prev = $char;
                    $char = $next;
                    $pos++;
                    $next = isset($this->code[$pos]) ? $this->code[$pos] : null;
                    break;
                case "\n":
                    ++$line;
                    if ($this->status('line-comment')) {
                        $this->upStatus();
                    }
                    break;
                case '/':
                    switch ($this->status()) {
                        case 'simple-quote':
                        case 'double-quote':
                        case 'back-tick':
                        case 'line-comment':
                            break;
                        case 'block-comment':
                            if ($prev === '*') {
                                $this->upStatus();
                            }
                            break;
                        default:
                            if ($next === '/') {
                                $this->downStatus('line-comment');
                            } elseif ($next === '*') {
                                $this->downStatus('block-comment');
                            }
                            break;
                    }
                    break;
                case "'":
                    switch ($this->status()) {
                        case 'simple-quote':
                            $this->upStatus();
                            break;
                        case 'line-comment':
                        case 'block-comment':
                        case 'double-quote':
                        case 'back-tick':
                            break;
                        default:
                            $this->downStatus('simple-quote');
                            break;
                    }
                    break;
                case '"':
                    switch ($this->status()) {
                        case 'double-quote':
                            $this->upStatus();
                            break;
                        case 'line-comment':
                        case 'block-comment':
                        case 'simple-quote':
                        case 'back-tick':
                            break;
                        default:
                            $this->downStatus('double-quote');
                            break;
                    }
                    break;
                case '`':
                    switch ($this->status()) {
                        case 'back-tick':
                            $this->upStatus();
                            break;
                        case 'line-comment':
                        case 'block-comment':
                        case 'simple-quote':
                        case 'double-quote':
                            break;
                        default:
                            $this->downStatus('back-tick');
                            break;
                    }
                    break;
                case '(':
                    switch ($this->status()) {
                        case 'simple-quote':
                        case 'double-quote':
                        case 'back-tick':
                        case 'line-comment':
                        case 'block-comment':
                            break;
                        default:
                            if ($buffer && \preg_match('/(\\w+)$/', $buffer, $matches)) {
                                $this->downStatus('function');
                                \array_unshift($bufferFunctions, [$matches[1], $line, []]);
                                $buffer = '';
                                continue 3;
                            }
                            break;
                    }
                    break;
                case ')':
                    switch ($this->status()) {
                        case 'function':
                            if ($argument = static::prepareArgument($buffer)) {
                                $bufferFunctions[0][2][] = $argument;
                            }
                            if (!empty($bufferFunctions)) {
                                $functions[] = \array_shift($bufferFunctions);
                            }
                            $this->upStatus();
                            $buffer = '';
                            continue 3;
                    }
                    break;
                case ',':
                    switch ($this->status()) {
                        case 'function':
                            if ($argument = static::prepareArgument($buffer)) {
                                $bufferFunctions[0][2][] = $argument;
                            }
                            $buffer = '';
                            continue 3;
                    }
                    break;
                case ' ':
                case '\\t':
                    switch ($this->status()) {
                        case 'double-quote':
                        case 'simple-quote':
                        case 'back-tick':
                            break;
                        default:
                            $buffer = '';
                            continue 3;
                    }
                    break;
            }
            switch ($this->status()) {
                case 'line-comment':
                case 'block-comment':
                    break;
                default:
                    $buffer .= $char;
                    break;
            }
        }
        return $functions;
    }
    /**
     * Get the current context of the scan.
     *
     * @param null|string $match To check whether the current status is this value
     *
     * @return string|bool
     */
    protected function status($match = null)
    {
        $status = isset($this->status[0]) ? $this->status[0] : null;
        if ($match !== null) {
            return $status === $match;
        }
        return $status;
    }
    /**
     * Add a new status to the stack.
     *
     * @param string $status
     */
    protected function downStatus($status)
    {
        \array_unshift($this->status, $status);
    }
    /**
     * Removes and return the current status.
     *
     * @return string|null
     */
    protected function upStatus()
    {
        return \array_shift($this->status);
    }
    /**
     * Prepares the arguments found in functions.
     *
     * @param string $argument
     *
     * @return string
     */
    protected static function prepareArgument($argument)
    {
        if ($argument && \in_array($argument[0], ['"', "'", '`'], \true)) {
            return static::convertString(\substr($argument, 1, -1));
        }
    }
    /**
     * Decodes a string with an argument.
     *
     * @param string $value
     *
     * @return string
     */
    protected static function convertString($value)
    {
        if (\strpos($value, '\\') === \false) {
            return $value;
        }
        return \preg_replace_callback('/\\\\(n|r|t|v|e|f|"|\\\\)/', function ($match) {
            switch ($match[1][0]) {
                case 'n':
                    return "\n";
                case 'r':
                    return "\r";
                case 't':
                    return "\t";
                case 'v':
                    return "\v";
                case 'e':
                    return "\x1b";
                case 'f':
                    return "\f";
                case '"':
                    return '"';
                case '\\':
                    return '\\';
            }
        }, $value);
    }
}
