"""Mouse input tools."""

from fastmcp import Context
from fastmcp.exceptions import ToolError

from mcvnc._state import get_state, mcp

BUTTON_MAP: dict[str, int] = {
    "left": 0,
    "middle": 1,
    "right": 2,
}


def _resolve_button(button: str) -> int:
    """Resolve a button name to asyncvnc2 button number."""
    b = BUTTON_MAP.get(button.lower())
    if b is None:
        raise ToolError(f"Unknown button '{button}'. Use: {list(BUTTON_MAP.keys())}")
    return b


def _validate_coords(session, x: int, y: int) -> None:
    """Validate coordinates are within the remote desktop bounds."""
    if x < 0 or x >= session.width:
        raise ToolError(
            f"x={x} out of range. Valid: 0..{session.width - 1} "
            f"(desktop is {session.width}x{session.height})"
        )
    if y < 0 or y >= session.height:
        raise ToolError(
            f"y={y} out of range. Valid: 0..{session.height - 1} "
            f"(desktop is {session.width}x{session.height})"
        )


@mcp.tool()
async def vnc_click(
    ctx: Context,
    session_id: str,
    x: int,
    y: int,
    button: str = "left",
) -> str:
    """Click at a position on the remote desktop.

    Args:
        session_id: VNC session.
        x: X coordinate (pixels from left).
        y: Y coordinate (pixels from top).
        button: Mouse button -- "left", "middle", or "right".
    """
    state = get_state(ctx)
    session = state.get(session_id)
    btn = _resolve_button(button)
    _validate_coords(session, x, y)

    async with session.lock:
        session.client.mouse.move(x, y)
        session.client.mouse.click(btn)
        await session.client.drain()

    return f"Clicked {button} at ({x}, {y})"


@mcp.tool()
async def vnc_double_click(
    ctx: Context,
    session_id: str,
    x: int,
    y: int,
    button: str = "left",
) -> str:
    """Double-click at a position on the remote desktop.

    Args:
        session_id: VNC session.
        x: X coordinate (pixels from left).
        y: Y coordinate (pixels from top).
        button: Mouse button -- "left", "middle", or "right".
    """
    state = get_state(ctx)
    session = state.get(session_id)
    btn = _resolve_button(button)
    _validate_coords(session, x, y)

    async with session.lock:
        session.client.mouse.move(x, y)
        session.client.mouse.click(btn)
        session.client.mouse.click(btn)
        await session.client.drain()

    return f"Double-clicked {button} at ({x}, {y})"


@mcp.tool()
async def vnc_move(ctx: Context, session_id: str, x: int, y: int) -> str:
    """Move the mouse cursor without clicking.

    Args:
        session_id: VNC session.
        x: X coordinate.
        y: Y coordinate.
    """
    state = get_state(ctx)
    session = state.get(session_id)
    _validate_coords(session, x, y)

    async with session.lock:
        session.client.mouse.move(x, y)
        await session.client.drain()

    return f"Moved cursor to ({x}, {y})"


@mcp.tool()
async def vnc_drag(
    ctx: Context,
    session_id: str,
    start_x: int,
    start_y: int,
    end_x: int,
    end_y: int,
    button: str = "left",
) -> str:
    """Drag from one position to another (press, move, release).

    Args:
        session_id: VNC session.
        start_x: Starting X coordinate.
        start_y: Starting Y coordinate.
        end_x: Ending X coordinate.
        end_y: Ending Y coordinate.
        button: Mouse button to hold during drag.
    """
    state = get_state(ctx)
    session = state.get(session_id)
    btn = _resolve_button(button)
    _validate_coords(session, start_x, start_y)
    _validate_coords(session, end_x, end_y)

    async with session.lock:
        session.client.mouse.move(start_x, start_y)
        with session.client.mouse.hold(btn):
            session.client.mouse.move(end_x, end_y)
        await session.client.drain()

    return f"Dragged {button} from ({start_x}, {start_y}) to ({end_x}, {end_y})"


@mcp.tool()
async def vnc_scroll(
    ctx: Context,
    session_id: str,
    x: int,
    y: int,
    direction: str = "down",
    clicks: int = 3,
) -> str:
    """Scroll at a position on the remote desktop.

    Args:
        session_id: VNC session.
        x: X coordinate to scroll at.
        y: Y coordinate to scroll at.
        direction: "up" or "down".
        clicks: Number of scroll clicks (default 3).
    """
    state = get_state(ctx)
    session = state.get(session_id)
    _validate_coords(session, x, y)

    if direction not in ("up", "down"):
        raise ToolError(f"direction must be 'up' or 'down', got '{direction}'")
    if clicks < 1:
        raise ToolError("clicks must be >= 1")

    async with session.lock:
        session.client.mouse.move(x, y)
        if direction == "up":
            session.client.mouse.scroll_up(repeat=clicks)
        else:
            session.client.mouse.scroll_down(repeat=clicks)
        await session.client.drain()

    return f"Scrolled {direction} {clicks}x at ({x}, {y})"
