/**
 * Low-level Windows desktop executor using PowerShell + .NET P/Invoke.
 * Used by computer_use MCP tools to perform mouse, keyboard, and screenshot actions.
 *
 * All input injection is done through a single reusable PowerShell helper script
 * that accepts JSON commands on stdin. The script uses Add-Type to import user32.dll
 * P/Invoke signatures for SetCursorPos, mouse_event, and keybd_event.
 */
import { exec } from "node:child_process";
import { promisify } from "node:util";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { takeScreenshot } from "./screenshot.js";
import { logger } from "../logger.js";
import { getConfigDir } from "../config/index.js";
const execAsync = promisify(exec);
const WINDOWS_POWERSHELL_OPTIONS = { timeout: 15_000, windowsHide: true };
const HELPER_VERSION = "3";
/** Capture the screen. Returns base64 PNG and the saved file path. */
export async function captureScreen() {
    return takeScreenshot();
}
/** Cached display size, set once at agent startup. */
let cachedDisplaySize = null;
/**
 * Initialize display size cache. Throws if detection fails — computer_use
 * coordinate math would be silently wrong with a guessed fallback, so we
 * surface the failure to the caller which can disable computer_use and
 * notify the owner.
 */
export async function initDisplaySize() {
    cachedDisplaySize = await fetchDisplaySize();
}
/** Whether `initDisplaySize()` has successfully populated the cache. */
export function isDisplaySizeInitialized() {
    return cachedDisplaySize !== null;
}
/**
 * Return cached display size. Throws if uninitialized — callers must gate
 * on `isDisplaySizeInitialized()` first.
 */
export function getDisplaySize() {
    if (!cachedDisplaySize) {
        throw new Error("Display size not initialized — call initDisplaySize() first, or gate computer_use on isDisplaySizeInitialized()");
    }
    return cachedDisplaySize;
}
async function fetchDisplaySize() {
    const ps1 = [
        `$pinvoke = @'`,
        `using System;`,
        `using System.Runtime.InteropServices;`,
        `public class DpiHelper {`,
        `  [DllImport("user32.dll")] public static extern bool SetProcessDPIAware();`,
        `  [DllImport("user32.dll")] public static extern int GetSystemMetrics(int nIndex);`,
        `}`,
        `'@`,
        `Add-Type -TypeDefinition $pinvoke -Language CSharp`,
        `[DpiHelper]::SetProcessDPIAware() | Out-Null`,
        `$w = [DpiHelper]::GetSystemMetrics(0)`,
        `$h = [DpiHelper]::GetSystemMetrics(1)`,
        `Write-Output "$($w)x$($h)"`,
    ].join("\n");
    const ps1File = path.join(os.tmpdir(), `visionclaw-displaysize-${Date.now()}.ps1`);
    fs.writeFileSync(ps1File, ps1, "utf-8");
    try {
        const { stdout } = await execAsync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${ps1File}"`, WINDOWS_POWERSHELL_OPTIONS);
        const m = /^(\d+)x(\d+)/.exec(stdout.trim());
        if (!m?.[1] || !m[2]) {
            throw new Error(`PowerShell returned unparseable display size: ${JSON.stringify(stdout.trim())}`);
        }
        const size = { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
        logger.debug(`getDisplaySize: ${size.width}x${size.height}`, size);
        return size;
    }
    finally {
        try {
            fs.unlinkSync(ps1File);
        }
        catch { /* noop */ }
    }
}
// ── PowerShell helper script management ─────────────────────────────────
function getHelperPath() {
    return path.join(getConfigDir(), "visionclaw-input-helper.ps1");
}
function helperNeedsRegeneration(helperPath) {
    if (!fs.existsSync(helperPath))
        return true;
    try {
        const content = fs.readFileSync(helperPath, "utf-8");
        return !content.includes(`# HELPER_VERSION=${HELPER_VERSION}`);
    }
    catch {
        return true;
    }
}
function ensureHelper() {
    const helperPath = getHelperPath();
    if (!helperNeedsRegeneration(helperPath))
        return helperPath;
    const dir = path.dirname(helperPath);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(helperPath, HELPER_SCRIPT, "utf-8");
    logger.debug(`Windows input helper written to ${helperPath}`);
    return helperPath;
}
async function runHelper(command) {
    const helperPath = ensureHelper();
    const json = JSON.stringify(command);
    const commandBase64 = Buffer.from(json, "utf8").toString("base64");
    const escapedBase64 = commandBase64.replace(/"/g, '\\"');
    const { stdout } = await execAsync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${helperPath}" -CommandBase64 "${escapedBase64}"`, WINDOWS_POWERSHELL_OPTIONS);
    return stdout.trim();
}
// ── Desktop automation functions ────────────────────────────────────────
export async function click(x, y) {
    await runHelper({ action: "click", x: Math.round(x), y: Math.round(y) });
}
export async function doubleClick(x, y) {
    await runHelper({ action: "doubleClick", x: Math.round(x), y: Math.round(y) });
}
export async function rightClick(x, y) {
    await runHelper({ action: "rightClick", x: Math.round(x), y: Math.round(y) });
}
export async function moveTo(x, y) {
    await runHelper({ action: "moveTo", x: Math.round(x), y: Math.round(y) });
}
export async function drag(x1, y1, x2, y2) {
    await runHelper({
        action: "drag",
        x1: Math.round(x1),
        y1: Math.round(y1),
        x2: Math.round(x2),
        y2: Math.round(y2),
    });
}
/**
 * Type text into the currently focused element.
 *
 * Uses the clipboard (Set-Clipboard + Ctrl-V) for speed and full Unicode support.
 * The previous clipboard content is saved and restored (text only — non-text
 * clipboard data such as images cannot be preserved and will be logged as a warning).
 */
export async function typeText(text) {
    await runHelper({ action: "typeText", text });
}
/**
 * Windows virtual key codes for common special keys.
 * Maps key names (lowercase) to VK_* hex values.
 */
const WIN_KEY_CODES = {
    enter: 0x0D,
    return: 0x0D,
    tab: 0x09,
    escape: 0x1B,
    esc: 0x1B,
    space: 0x20,
    backspace: 0x08,
    delete: 0x2E,
    up: 0x26,
    down: 0x28,
    left: 0x25,
    right: 0x27,
    home: 0x24,
    end: 0x23,
    pageup: 0x21,
    pagedown: 0x22,
    f1: 0x70, f2: 0x71, f3: 0x72, f4: 0x73, f5: 0x74, f6: 0x75,
    f7: 0x76, f8: 0x77, f9: 0x78, f10: 0x79, f11: 0x7A, f12: 0x7B,
};
/**
 * Windows modifier key VK codes.
 * cmd/meta map to Ctrl on Windows (macOS Cmd+key == Windows Ctrl+key).
 */
const WIN_MODIFIER_CODES = {
    cmd: 0x11, // VK_CONTROL
    meta: 0x11, // VK_CONTROL
    ctrl: 0x11, // VK_CONTROL
    alt: 0x12, // VK_MENU
    option: 0x12, // VK_MENU
    shift: 0x10, // VK_SHIFT
};
/**
 * Press a key or key combination (e.g. "enter", "escape", "cmd+s", "ctrl+shift+t").
 * Modifiers cmd/meta are mapped to Ctrl on Windows.
 */
export async function pressKey(key) {
    const parts = key.toLowerCase().split("+");
    const keyName = parts[parts.length - 1] ?? key;
    const modParts = parts.slice(0, -1);
    await new Promise((r) => setTimeout(r, 100));
    const modifiers = modParts
        .map((m) => WIN_MODIFIER_CODES[m])
        .filter((v) => v !== undefined);
    const vk = WIN_KEY_CODES[keyName];
    await runHelper({
        action: "pressKey",
        keyName,
        vk: vk ?? null,
        modifiers,
        isSingleChar: vk === undefined && keyName.length === 1,
    });
}
/**
 * Scroll at (x, y) in direction.
 * Uses mouse_event with MOUSEEVENTF_WHEEL via the helper script.
 */
export async function scroll(x, y, direction, amount = 3) {
    await moveTo(x, y);
    await new Promise((r) => setTimeout(r, 100));
    // WHEEL_DELTA = 120 per notch. Positive = up, negative = down.
    const vertical = direction === "up" ? amount * 120 : direction === "down" ? -(amount * 120) : 0;
    const horizontal = direction === "left" ? amount * 120 : direction === "right" ? -(amount * 120) : 0;
    await runHelper({
        action: "scroll",
        vertical,
        horizontal,
    });
}
// ── PowerShell helper script content ────────────────────────────────────
const HELPER_SCRIPT = `# HELPER_VERSION=${HELPER_VERSION}
# VisionClaw Windows Input Helper
# Accepts a UTF-8 JSON command encoded as base64 and performs the requested action.

param(
    [string]$CommandBase64 = ""
)

Add-Type -AssemblyName System.Windows.Forms

$pinvoke = @"
using System;
using System.Runtime.InteropServices;
public class InputHelper {
    [DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
    [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
    [DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, int dx, int dy, int dwData, IntPtr dwExtraInfo);
    [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo);
    [DllImport("user32.dll")] public static extern short VkKeyScan(char ch);

    public const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
    public const uint MOUSEEVENTF_LEFTUP = 0x0004;
    public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
    public const uint MOUSEEVENTF_RIGHTUP = 0x0010;
    public const uint MOUSEEVENTF_WHEEL = 0x0800;
    public const uint MOUSEEVENTF_HWHEEL = 0x1000;
    public const uint KEYEVENTF_KEYUP = 0x0002;
}
"@
Add-Type -TypeDefinition $pinvoke -Language CSharp
[InputHelper]::SetProcessDPIAware() | Out-Null

$json = if ($CommandBase64) {
    [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($CommandBase64))
} else {
    # Backward-compat fallback for older callers.
    [Console]::In.ReadToEnd()
}
$cmd = $json | ConvertFrom-Json

function Move-CursorTo([int]$x, [int]$y) {
    [InputHelper]::SetCursorPos($x, $y) | Out-Null
    Start-Sleep -Milliseconds 10
}

function Send-Click([int]$x, [int]$y) {
    Move-CursorTo $x $y
    [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, [IntPtr]::Zero)
    [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTUP, 0, 0, 0, [IntPtr]::Zero)
}

switch ($cmd.action) {
    "click" {
        Move-CursorTo $cmd.x $cmd.y
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, [IntPtr]::Zero)
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTUP, 0, 0, 0, [IntPtr]::Zero)
    }
    "doubleClick" {
        Move-CursorTo $cmd.x $cmd.y
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, [IntPtr]::Zero)
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTUP, 0, 0, 0, [IntPtr]::Zero)
        Start-Sleep -Milliseconds 50
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, [IntPtr]::Zero)
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTUP, 0, 0, 0, [IntPtr]::Zero)
    }
    "rightClick" {
        Move-CursorTo $cmd.x $cmd.y
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, [IntPtr]::Zero)
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_RIGHTUP, 0, 0, 0, [IntPtr]::Zero)
    }
    "moveTo" {
        Move-CursorTo $cmd.x $cmd.y
    }
    "drag" {
        Move-CursorTo $cmd.x1 $cmd.y1
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, [IntPtr]::Zero)
        Start-Sleep -Milliseconds 100
        Move-CursorTo $cmd.x2 $cmd.y2
        Start-Sleep -Milliseconds 100
        [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_LEFTUP, 0, 0, 0, [IntPtr]::Zero)
    }
    "typeText" {
        # Save clipboard, paste text, restore clipboard
        $prevClip = $null
        try { $prevClip = Get-Clipboard -Raw -ErrorAction SilentlyContinue } catch {}

        Set-Clipboard -Value $cmd.text
        Start-Sleep -Milliseconds 50
        [InputHelper]::keybd_event(0x11, 0, 0, [IntPtr]::Zero)  # Ctrl down
        [InputHelper]::keybd_event(0x56, 0, 0, [IntPtr]::Zero)  # V down
        [InputHelper]::keybd_event(0x56, 0, [InputHelper]::KEYEVENTF_KEYUP, [IntPtr]::Zero)  # V up
        [InputHelper]::keybd_event(0x11, 0, [InputHelper]::KEYEVENTF_KEYUP, [IntPtr]::Zero)  # Ctrl up
        Start-Sleep -Milliseconds 50

        # Restore previous clipboard
        if ($null -ne $prevClip) {
            try { Set-Clipboard -Value $prevClip } catch {}
        } else {
            try { Set-Clipboard -Value $null } catch {}
        }
    }
    "pressKey" {
        # Press modifiers down
        foreach ($mod in $cmd.modifiers) {
            [InputHelper]::keybd_event([byte]$mod, 0, 0, [IntPtr]::Zero)
        }

        if ($null -ne $cmd.vk) {
            # Known special key
            [InputHelper]::keybd_event([byte]$cmd.vk, 0, 0, [IntPtr]::Zero)
            [InputHelper]::keybd_event([byte]$cmd.vk, 0, [InputHelper]::KEYEVENTF_KEYUP, [IntPtr]::Zero)
        } elseif ($cmd.isSingleChar) {
            # Single character — use VkKeyScan to get correct VK code
            $vk = [InputHelper]::VkKeyScan([char]$cmd.keyName)
            $lo = $vk -band 0xFF
            if ($lo -ne 0xFF) {
                [InputHelper]::keybd_event([byte]$lo, 0, 0, [IntPtr]::Zero)
                [InputHelper]::keybd_event([byte]$lo, 0, [InputHelper]::KEYEVENTF_KEYUP, [IntPtr]::Zero)
            } else {
                [System.Windows.Forms.SendKeys]::SendWait($cmd.keyName)
            }
        } else {
            # Unknown key — fallback to SendKeys
            [System.Windows.Forms.SendKeys]::SendWait($cmd.keyName)
        }

        # Release modifiers
        $reversed = @($cmd.modifiers)
        [Array]::Reverse($reversed)
        foreach ($mod in $reversed) {
            [InputHelper]::keybd_event([byte]$mod, 0, [InputHelper]::KEYEVENTF_KEYUP, [IntPtr]::Zero)
        }
    }
    "scroll" {
        if ($cmd.vertical -ne 0) {
            [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_WHEEL, 0, 0, $cmd.vertical, [IntPtr]::Zero)
        }
        if ($cmd.horizontal -ne 0) {
            [InputHelper]::mouse_event([InputHelper]::MOUSEEVENTF_HWHEEL, 0, 0, $cmd.horizontal, [IntPtr]::Zero)
        }
    }
}

Write-Output "ok"
`;
//# sourceMappingURL=desktop-executor-windows.js.map