```python
from typing import List, Dict

def split_segments(path: str) -> List[str]:
    if not isinstance(path, str):
        raise ValueError("path must be a non-empty string")
    if path == "":
        return []
    segments = path.split("/")
    for i in range(len(segments)):
        if segments[i] == "" or segments[i][0] != "/":
            raise ValueError(f"invalid segment {segments[i]} at index {i}")
    return segments

def first_route(patterns: List[str], path: str) -> int:
    if not isinstance(path, str):
        raise ValueError("path must be a non-empty string")
    if not isinstance(patterns, list):
        raise ValueError("patterns must be a list of strings")
    
    for i in range(len(patterns)):
        pattern = patterns[i]
        if not isinstance(pattern, str):
            raise ValueError(f"pattern {i} must be a non-empty string")
        
        # Check for invalid characters in capture names
        for name in pattern.split(":"):
            if not (name[0].isalpha() or name[0] == "_") or not all(c.isalnum() or c == "_" for c in name[1:]):
                raise ValueError(f"invalid capture name {name} in pattern {i}")
        
        # Check for duplicate capture names
        seen = set()
        for name in pattern.split(":"):
            if name in seen:
                raise ValueError(f"duplicate capture name {name} in pattern {i}")
            seen.add(name)
    
    # Check for "**" more than once
    if "**" in patterns:
        if patterns.count("**") > 1:
            raise ValueError("pattern '**' can only appear once")
    
    # Match the path against each pattern
    for i, pattern in enumerate(patterns):
        if match_route(pattern, path) is not None:
            return i
    
    return -1

def match_route(pattern: str, path: str) -> Dict[str, str] | None:
    if not isinstance(path, str):
        raise ValueError("path must be a non-empty string")
    if not isinstance(pattern, str):
        raise ValueError("pattern must be a non-empty string")
    
    # Split the pattern into segments
    pattern_segments = pattern.split(":")
    path_segments = split_segments(path)
    
    # Check for invalid characters in capture names
    for name in pattern_segments:
        if not (name[0].isalpha() or name[0] == "_") or not all(c.isalnum() or c == "_" for c in name[1:]):
            raise ValueError(f"invalid capture name {name} in pattern")
    
    # Check for duplicate capture names
    seen = set()
    for name in pattern_segments:
        if name in seen:
            raise ValueError(f"duplicate capture name {name} in pattern")
        seen.add(name)
    
    # Match the path segments against the pattern segments
    i = 0
    while i < len(pattern_segments):
        segment = pattern_segments[i]
        if segment == "*":
            i += 1
        elif segment == "**":
            j = i + 1
            while j < len(pattern_segments) and pattern_segments[j] != "**":
                j += 1
            if j == len(pattern_segments):
                return None
            match = split_segments(path[i:j])
            if match is not None:
                return {name: value for name, value in zip(segment.split(":")[1:], match)}
            i = j + 1
        elif segment.startswith(":"):
            name = segment[1:]
            if name not in path_segments or path_segments.index(name) != i:
                return None
            i += 1
        else:
            if pattern_segments[i] != path_segments[i]:
                return None
            i += 1
    
    # Check for trailing slash
    if i < len(pattern_segments):
        if pattern_segments[i] != "":
            raise ValueError("path must not end with a trailing slash")
    
    return {name: value for name, value in zip(pattern.split(":")[1:], path_segments[i:])}
```