# Input sanitization utilities
import os
import re
import socket

_SAFE_NAME_RE = re.compile(r'^[a-zA-Z0-9_\-]+$')
_ALLOWED_SAVE_DIRS = [os.path.expanduser("~/maps"), "/tmp/maps"]


def _safe_name(name: str, default: str = "map") -> str:
    """Remove dangerous characters from file/map names."""
    if not name or not _SAFE_NAME_RE.match(name):
        return default
    return name


def _safe_dir(path: str) -> str:
    """Validate directory against allowed whitelist."""
    resolved = os.path.realpath(os.path.expanduser(path))
    for allowed in _ALLOWED_SAVE_DIRS:
        allowed_resolved = os.path.realpath(os.path.expanduser(allowed))
        if resolved.startswith(allowed_resolved):
            return resolved
    return os.path.realpath(os.path.expanduser(_ALLOWED_SAVE_DIRS[0]))


def get_lan_ip() -> str:
    # UDP connect to a public IP; no packet is actually sent, but the kernel
    # picks the outbound interface IP — works offline too for LAN-only setups.
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    except OSError:
        return "127.0.0.1"
    finally:
        s.close()


def rosbridge_port() -> int:
    try:
        return int(os.environ.get("ROSBRIDGE_PORT", "9090"))
    except ValueError:
        return 9090


def rosbridge_alive(host: str = "127.0.0.1", port: int | None = None, timeout: float = 0.3) -> bool:
    if port is None:
        port = rosbridge_port()
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
    except OSError:
        return False
