@Override
  protected @NotNull Promise<Object> _execute(final @NotNull PlaybackContext context) {
    final String cmd = getText().substring(PREFIX.length()).trim();
    final int open = cmd.indexOf("(");
    if (open == -1) {
      context.error("( expected", getLine());
      return Promises.resolvedPromise();
    }

    final int close = cmd.lastIndexOf(")");
    if (close == -1) {
      context.error(") expected", getLine());
      return Promises.resolvedPromise();
    }

    final String methodName = cmd.substring(0, open);
    String[] args = cmd.substring(open + 1, close).split(",");
    final boolean noArgs = args.length == 1 && args[0].isEmpty();
    Class[] types = noArgs ? new Class[1] : new Class[args.length + 1];
    types[0] = PlaybackContext.class;
    for (int i = 1; i < types.length; i++) {
      types[i] = String.class;
    }


    final AsyncPromise<Object> cmdResult = new AsyncPromise<>();
    try {
      Pair<Method, Class<?>> methodClass = findMethod(context, methodName, types);
      if (methodClass == null) {
        context.error("No method \"" + methodName + "\" with parameters ["
                      + StringUtil.join(types, type -> type.getName(), ", ")
                      + "] found in facade classes: " + context.getCallClasses(), getLine());
        return Promises.rejectedPromise();
      }

      Method m = methodClass.getFirst();

      if (!m.getReturnType().isAssignableFrom(Promise.class)) {
        context.error("Method " + methodClass.getSecond() + ":" + methodName + " must return Promise object", getLine());
        return Promises.rejectedPromise();
      }

      Object[] actualArgs = noArgs ? new Object[1] : new Object[args.length + 1];
      actualArgs[0] = context;
      System.arraycopy(args, 0, actualArgs, 1, actualArgs.length - 1);


      Promise<String> result = (Promise<String>)m.invoke(null, actualArgs);
      if (result == null) {
        context.error("Method " + methodClass.getSecond() + ":" + methodName + " must return AsyncResult object, but was null", getLine());
        return Promises.rejectedPromise();
      }

      result
        .onSuccess(s -> {
          if (s != null) {
            context.message(s, getLine());
          }
          cmdResult.setResult(null);
        })
        .onError(error -> {
          context.error(error.getMessage(), getLine());
          cmdResult.setError(error);
        });
    }
    catch (InvocationTargetException ignored) {
      context.error("InvocationTargetException while executing command: " + cmd, getLine());
    }
    catch (IllegalAccessException ignored) {
      context.error("IllegalAccessException while executing command: " + cmd, getLine());
    }
    return cmdResult;
  }
@Override
  protected @NotNull Promise<Object> _execute(final @NotNull PlaybackContext context)