```python
import re

def split_segments(path: str) -> list:
    """Split path into segments, rejecting invalid paths."""
    if not isinstance(path, str) or not path.startswith('/') or path.endswith('/'):
        raise ValueError("Invalid path")
    if '/' in path[1:]:
        raise ValueError("Empty segment inside path")
    return path[1:].split('/')

def match_route(pattern: str, path: str) -> dict | None:
    """Match pattern against path and return capture mapping."""
    segments = split_segments(path)
    pattern_segments = split_segments(pattern)
    
    if '**' in pattern_segments and pattern_segments.count('**') > 1:
        raise ValueError("Pattern cannot contain more than one '**'")
    
    captures = {}
    i, j = 0, 0
    while i < len(segments) and j < len(pattern_segments):
        p_segment = pattern_segments[j]
        if p_segment == '**':
            captures[p_segment] = '/'.join(segments[i:])
            break
        elif p_segment.startswith(':'):
            if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', p_segment[1:]):
                raise ValueError("Invalid capture name")
            captures[p_segment[1:]] = segments[i]
            i += 1
        elif p_segment == '*':
            if i + 1 < len(segments):
                captures['*'] = segments[i]
                i += 1
            else:
                break
        elif segments[i] != p_segment:
            return None
        i += 1
        j += 1
    
    if i != len(segments) or j != len(pattern_segments):
        return None
    
    return captures

def first_route(patterns: list, path: str) -> int:
    """Return index of the first matching pattern."""
    for i, pattern in enumerate(patterns):
        if match_route(pattern, path) is not None:
            return i
    return -1
```