/**
 * Android use MCP tools — ADB-based mobile action space.
 *
 * Implements the standard action space from gbox-mini-agent:
 *   click, long_press, type, scroll, drag, press, screenshot, open
 *
 * All coordinate-based actions use **pixel coordinates** based on the
 * screenshot image returned by android_use_screenshot. The model sees
 * the resized screenshot and outputs pixel positions in that image space.
 * The execution layer handles scaling from screenshot coords to actual
 * device resolution.
 *
 * The underlying ADB execution layer (android-executor.ts) is unchanged.
 */
import { z } from "zod";
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { captureScreen, getDisplaySize, initDisplaySize, isDisplaySizeInitialized, tap, doubleTap, longPress, swipe, typeText, pressKey, launchApp, getCurrentActivity, } from "./android-executor.js";
import { getVisionScaleFactor } from "./screenshot.js";
import { logger } from "../logger.js";
// ── Helpers ───────────────────────────────────────────────────────────
/** Verify an ADB device is connected. */
async function requireDevice() {
    try {
        const { exec } = await import("node:child_process");
        const { promisify } = await import("node:util");
        const execAsync = promisify(exec);
        const { stdout } = await execAsync("adb devices");
        const devices = stdout
            .trim()
            .split("\n")
            .slice(1)
            .filter((l) => l.includes("device"));
        if (devices.length === 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: "No Android device connected via ADB. Please connect a device and enable USB debugging.",
                    },
                ],
                isError: true,
            };
        }
        return null;
    }
    catch {
        return {
            content: [
                {
                    type: "text",
                    text: "ADB is not available. Please install Android platform-tools.",
                },
            ],
            isError: true,
        };
    }
}
/** Ensure display size is initialized from the actual device. */
async function ensureDisplaySize() {
    if (!isDisplaySizeInitialized()) {
        await initDisplaySize();
    }
}
/**
 * Compute the expected screenshot image dimensions after vision resize.
 * The screenshot pipeline resizes the raw capture to fit Claude's vision
 * constraints. This function computes those dimensions deterministically
 * from the device resolution so we can map coordinates without reading
 * the image file.
 */
export function getScreenshotSize(deviceSize) {
    const scale = getVisionScaleFactor(deviceSize.width, deviceSize.height);
    if (scale >= 1.0)
        return { ...deviceSize };
    return {
        width: Math.max(1, Math.round(deviceSize.width * scale)),
        height: Math.max(1, Math.round(deviceSize.height * scale)),
    };
}
/**
 * Convert pixel coordinates in screenshot space to absolute device coordinates.
 * The model outputs coordinates based on the resized screenshot it sees;
 * we scale them to the actual device resolution for ADB input events.
 */
export function screenshotToDevice(point, screenshotSize, deviceSize) {
    return {
        x: Math.round((point.x / screenshotSize.width) * deviceSize.width),
        y: Math.round((point.y / screenshotSize.height) * deviceSize.height),
    };
}
/** Zod schema for a pixel coordinate point in the screenshot image. */
const pointSchema = z.object({
    x: z
        .number()
        .min(0)
        .describe("X pixel coordinate in the screenshot image (0 = left edge)"),
    y: z
        .number()
        .min(0)
        .describe("Y pixel coordinate in the screenshot image (0 = top edge)"),
});
/**
 * Capture a post-action screenshot and return a standard result.
 */
async function actionResult(text, delayMs = 1000) {
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    const { base64: resultScreenshot } = await captureScreen();
    logger.screenshot(resultScreenshot);
    return {
        content: [
            { type: "text", text },
            {
                type: "image",
                data: resultScreenshot,
                mimeType: "image/png",
            },
        ],
    };
}
/** Wrap a tool handler with device check and error handling. */
function withDeviceCheck(toolName, fn) {
    return (async () => {
        const err = await requireDevice();
        if (err)
            return err;
        try {
            return await fn();
        }
        catch (e) {
            logger.err(`${toolName} failed`, { error: String(e) });
            return {
                content: [
                    {
                        type: "text",
                        text: `${toolName} failed: ${e instanceof Error ? e.message : String(e)}`,
                    },
                ],
                isError: true,
            };
        }
    })();
}
// ── Standard keys (matching gbox-mini-agent PRESS_KEYS) ─────────────
/** Alias map for key normalization. */
export const KEY_ALIASES = {
    return: "enter",
    esc: "escape",
    del: "delete",
    bksp: "backspace",
    appswitch: "app_switch",
    app_switcher: "app_switch",
    recent: "app_switch",
    overview: "app_switch",
    volup: "volume_up",
    volumeup: "volume_up",
    voldown: "volume_down",
    volumedown: "volume_down",
    // Map desktop modifiers to mobile equivalents
    super: "home",
    meta: "home",
    windows: "home",
    win: "home",
    command: "home",
    cmd: "home",
};
export function normalizeKey(raw) {
    const normalized = raw
        .trim()
        .toLowerCase()
        .replace(/\s+/g, "_")
        .replace(/-/g, "_");
    return KEY_ALIASES[normalized] ?? normalized;
}
// ── Tool definitions (Standard Action Space) ─────────────────────────
export const androidUseClickTool = tool("android_use_click", "Click (tap) at a point on the Android device screen. Coordinates are pixel positions in the screenshot image returned by android_use_screenshot. Supports single tap, double tap, and button variants.", {
    point: pointSchema.describe("Pixel coordinates in the screenshot image to click"),
    button: z
        .enum(["left", "right", "middle"])
        .optional()
        .default("left")
        .describe("Click button (default: left). On Android, all buttons map to tap."),
    double: z
        .boolean()
        .optional()
        .default(false)
        .describe("If true, perform a double-tap instead of a single tap"),
}, async (args) => {
    return withDeviceCheck("android_use_click", async () => {
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        const abs = screenshotToDevice(args.point, ssSize, deviceSize);
        if (args.double) {
            await doubleTap(abs.x, abs.y);
        }
        else {
            await tap(abs.x, abs.y);
        }
        return actionResult(`${args.double ? "Double-clicked" : "Clicked"} at screenshot (${Math.round(args.point.x)}, ${Math.round(args.point.y)}) → device (${abs.x}, ${abs.y})`);
    });
});
export const androidUseLongPressTool = tool("android_use_long_press", "Long press at a point on the Android device screen. Coordinates are pixel positions in the screenshot image.", {
    point: pointSchema.describe("Pixel coordinates in the screenshot image to long-press"),
    durationMs: z
        .number()
        .int()
        .positive()
        .optional()
        .default(1000)
        .describe("Long press duration in milliseconds (default: 1000)"),
}, async (args) => {
    return withDeviceCheck("android_use_long_press", async () => {
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        const abs = screenshotToDevice(args.point, ssSize, deviceSize);
        await longPress(abs.x, abs.y, args.durationMs);
        return actionResult(`Long-pressed at screenshot (${Math.round(args.point.x)}, ${Math.round(args.point.y)}) → device (${abs.x}, ${abs.y}) for ${args.durationMs}ms`);
    });
});
export const androidUseTypeTool = tool("android_use_type", "Type text into the currently focused input field on the Android device. Optionally replace existing text first (select all + delete before typing).", {
    content: z.string().describe("Text content to type"),
    replace: z
        .boolean()
        .optional()
        .default(false)
        .describe("If true, select all and delete existing text before typing"),
}, async (args) => {
    return withDeviceCheck("android_use_type", async () => {
        await typeText(args.content, { replace: args.replace });
        return actionResult(`Typed "${args.content.length > 50 ? args.content.slice(0, 50) + "..." : args.content}"${args.replace ? " (replaced existing text)" : ""}`);
    });
});
export const androidUseScrollTool = tool("android_use_scroll", "Scroll the Android device screen in a direction from a point. Coordinates are pixel positions in the screenshot image. Amount is a float in [0, 1] representing scroll distance relative to screen size.", {
    direction: z
        .enum(["up", "down", "left", "right"])
        .describe("Scroll direction"),
    amount: z
        .number()
        .min(0)
        .max(1)
        .describe("Scroll distance as fraction of screen size (0.0 to 1.0, e.g. 0.25 = quarter screen)"),
    point: pointSchema
        .optional()
        .describe("Pixel coordinates in the screenshot image for scroll origin (default: center of screen)"),
}, async (args) => {
    return withDeviceCheck("android_use_scroll", async () => {
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        // Default to center of screenshot if no point specified
        const ssPoint = args.point ?? {
            x: ssSize.width / 2,
            y: ssSize.height / 2,
        };
        const origin = screenshotToDevice(ssPoint, ssSize, deviceSize);
        // Convert normalized amount to pixel distance
        const isVertical = args.direction === "up" || args.direction === "down";
        const distance = Math.round(args.amount * (isVertical ? deviceSize.height : deviceSize.width));
        let endX = origin.x;
        let endY = origin.y;
        switch (args.direction) {
            case "up":
                endY = origin.y + distance; // finger moves down to scroll content up
                break;
            case "down":
                endY = origin.y - distance; // finger moves up to scroll content down
                break;
            case "left":
                endX = origin.x + distance;
                break;
            case "right":
                endX = origin.x - distance;
                break;
        }
        // Clamp to display bounds
        endX = Math.max(0, Math.min(deviceSize.width, endX));
        endY = Math.max(0, Math.min(deviceSize.height, endY));
        await swipe(origin.x, origin.y, endX, endY, 300);
        return actionResult(`Scrolled ${args.direction} by ${(args.amount * 100).toFixed(0)}% from screenshot (${Math.round(ssPoint.x)}, ${Math.round(ssPoint.y)})`);
    });
});
export const androidUseDragTool = tool("android_use_drag", "Drag from one point to another on the Android device. Coordinates are pixel positions in the screenshot image. Suitable for drag-and-drop operations (moving icons, reordering list items).", {
    startPoint: pointSchema.describe("Pixel coordinates in the screenshot image (drag source)"),
    endPoint: pointSchema.describe("Pixel coordinates in the screenshot image (drop target)"),
    durationMs: z
        .number()
        .int()
        .positive()
        .optional()
        .default(800)
        .describe("Drag duration in milliseconds (default: 800, longer than swipe for drag-and-drop)"),
}, async (args) => {
    return withDeviceCheck("android_use_drag", async () => {
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        const start = screenshotToDevice(args.startPoint, ssSize, deviceSize);
        const end = screenshotToDevice(args.endPoint, ssSize, deviceSize);
        await swipe(start.x, start.y, end.x, end.y, args.durationMs);
        return actionResult(`Dragged from screenshot (${Math.round(args.startPoint.x)}, ${Math.round(args.startPoint.y)}) to (${Math.round(args.endPoint.x)}, ${Math.round(args.endPoint.y)}) over ${args.durationMs}ms`);
    });
});
export const androidUsePressTool = tool("android_use_press", "Press a key or button on the Android device. Supports standard keys: enter, tab, escape, backspace, delete, space, arrow keys (up/down/left/right), mobile keys (home, back, menu, app_switch, volume_up, volume_down, power), and function keys (f1-f12).", {
    key: z
        .string()
        .describe("Key to press. Standard keys: enter, tab, escape, backspace, delete, space, up, down, left, right, home, back, menu, app_switch, volume_up, volume_down, power, f1-f12. Also accepts raw Android KEYCODE_* values."),
}, async (args) => {
    return withDeviceCheck("android_use_press", async () => {
        const normalized = normalizeKey(args.key);
        await pressKey(normalized);
        return actionResult(`Pressed: ${normalized}${normalized !== args.key ? ` (normalized from "${args.key}")` : ""}`);
    });
});
export const androidUseScreenshotTool = tool("android_use_screenshot", "Capture a screenshot of the connected Android device screen. Returns the image as base64 PNG. The image is resized for vision model processing. Use the pixel coordinates from this image when calling click, long_press, scroll, or drag tools.", {}, async (_args) => {
    const err = await requireDevice();
    if (err)
        return err;
    try {
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        const { base64, filePath } = await captureScreen();
        logger.screenshot(base64);
        return {
            content: [
                {
                    type: "text",
                    text: `Android screenshot saved to ${filePath}\nScreenshot image size: ${ssSize.width}x${ssSize.height}px | Device resolution: ${deviceSize.width}x${deviceSize.height}px\nUse pixel coordinates from this image when calling click/long_press/scroll/drag tools.`,
                },
                {
                    type: "image",
                    data: base64,
                    mimeType: "image/png",
                },
            ],
        };
    }
    catch (e) {
        return {
            content: [
                {
                    type: "text",
                    text: `Android screenshot failed: ${e instanceof Error ? e.message : String(e)}`,
                },
            ],
            isError: true,
        };
    }
});
export const androidUseOpenTool = tool("android_use_open", "Open (launch) an app on the Android device by package name (e.g. 'com.android.settings', 'com.tencent.mm').", {
    packageName: z
        .string()
        .describe("Android package name of the app to launch"),
}, async (args) => {
    return withDeviceCheck("android_use_open", async () => {
        await launchApp(args.packageName);
        await new Promise((resolve) => setTimeout(resolve, 2000)); // apps take longer to launch
        await ensureDisplaySize();
        const deviceSize = getDisplaySize();
        const ssSize = getScreenshotSize(deviceSize);
        const { base64: resultScreenshot } = await captureScreen();
        logger.screenshot(resultScreenshot);
        let activity = "";
        try {
            activity = await getCurrentActivity();
        }
        catch {
            /* noop */
        }
        return {
            content: [
                {
                    type: "text",
                    text: `Opened ${args.packageName}${activity ? `\nCurrent activity: ${activity}` : ""}\nScreenshot image size: ${ssSize.width}x${ssSize.height}px | Device resolution: ${deviceSize.width}x${deviceSize.height}px`,
                },
                {
                    type: "image",
                    data: resultScreenshot,
                    mimeType: "image/png",
                },
            ],
        };
    });
});
//# sourceMappingURL=android-use.js.map