"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DesktopInputReplayTool = void 0;
exports.sanitizeDesktopInputActions = sanitizeDesktopInputActions;
const fs = __importStar(require("fs/promises"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const capabilities_1 = require("../permissions/capabilities");
const desktopCapture = __importStar(require("./desktopTools"));
const MAX_ACTIONS = 10;
const MAX_TEXT_LENGTH = 500;
const MAX_KEY_LENGTH = 80;
const MAX_WAIT_MS = 2_000;
const MAX_AUDIT_ENTRIES = 50;
const MAX_SCREENSHOT_FILES = 50;
const SETTINGS_PATH = path.join('.harness', 'settings.json');
const DESKTOP_DIR = path.join('.harness', 'desktop');
const AUDIT_FILE = 'desktop-input-audit.jsonl';
const SCREENSHOT_PATTERN = /^desktop-input-(before|after)-[A-Za-z0-9_.-]+\.png$/;
exports.DesktopInputReplayTool = {
    name: 'desktop_input_replay',
    description: 'Preview or execute a bounded desktop input replay plan. Execution requires confirm=true, an active desktop-control grant, kill-switch checks before every input, and before/after screenshot evidence.',
    parameters: {
        type: 'object',
        properties: {
            confirm: { type: 'boolean', description: 'Must be true to execute. Omit or false to preview only.' },
            actions: {
                type: 'array',
                maxItems: MAX_ACTIONS,
                items: {
                    type: 'object',
                    properties: {
                        type: { type: 'string', enum: ['text', 'key', 'wait'] },
                        value: { type: 'string' },
                        ms: { type: 'number' },
                    },
                    required: ['type'],
                },
            },
        },
        required: ['actions'],
    },
    isReadOnly: false,
    riskLevel: 'high',
    permissionCategory: 'desktop',
    canDryRun: true,
    async execute(input) {
        const projectDir = process.cwd();
        const actions = sanitizeDesktopInputActions(input.actions);
        if (actions.length === 0)
            return { success: false, output: 'No valid desktop input actions were provided.', error: 'no valid actions' };
        const preview = renderDesktopInputPreview(actions);
        if (input.confirm !== true) {
            return { success: true, output: `Preview only. Set confirm=true with an active desktop-control grant to execute.\n${preview}` };
        }
        const policy = await readDesktopInputPolicyState(projectDir);
        const evaluation = (0, capabilities_1.evaluateCapabilityGrant)('desktop-control', policy.capabilityGrants, { killSwitchActive: policy.killSwitch.active });
        if (evaluation.decision !== 'allow') {
            await appendDesktopInputAudit(projectDir, { outcome: 'blocked', reason: evaluation.reason, actionCount: actions.length });
            return { success: false, output: `Desktop input blocked: ${evaluation.reason}`, error: evaluation.reason };
        }
        const evidenceDir = path.join(projectDir, DESKTOP_DIR);
        await fs.mkdir(evidenceDir, { recursive: true });
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const beforePath = path.join(evidenceDir, `desktop-input-before-${timestamp}.png`);
        const afterPath = path.join(evidenceDir, `desktop-input-after-${timestamp}.png`);
        try {
            await desktopCapture.captureDesktopScreenshot(beforePath, false);
            for (let i = 0; i < actions.length; i++) {
                const currentPolicy = await readDesktopInputPolicyState(projectDir);
                if (currentPolicy.killSwitch.active) {
                    await appendDesktopInputAudit(projectDir, { outcome: 'stopped', reason: currentPolicy.killSwitch.reason || 'kill switch active', actionCount: actions.length, stoppedBeforeAction: i + 1 });
                    return { success: false, output: `Desktop input stopped before action ${i + 1}: kill switch active (${currentPolicy.killSwitch.reason || 'no reason'}).`, error: 'kill switch active' };
                }
                await executeDesktopInputAction(actions[i]);
            }
            await desktopCapture.captureDesktopScreenshot(afterPath, false);
            await appendDesktopInputAudit(projectDir, { outcome: 'executed', actionCount: actions.length, before: path.relative(projectDir, beforePath), after: path.relative(projectDir, afterPath) });
            return {
                success: true,
                output: `Executed ${actions.length} desktop input action(s).\n${preview}\nEvidence before: ${path.relative(projectDir, beforePath)}\nEvidence after: ${path.relative(projectDir, afterPath)}`,
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            await appendDesktopInputAudit(projectDir, { outcome: 'failed', reason: message, actionCount: actions.length });
            return { success: false, output: `Desktop input replay failed: ${message}`, error: message };
        }
    },
};
function sanitizeDesktopInputActions(value) {
    if (!Array.isArray(value))
        return [];
    const actions = [];
    for (const item of value.slice(0, MAX_ACTIONS)) {
        if (!item || typeof item !== 'object')
            continue;
        const raw = item;
        const type = String(raw.type ?? '').trim().toLowerCase();
        if (type === 'text') {
            const text = String(raw.value ?? '').slice(0, MAX_TEXT_LENGTH);
            if (text)
                actions.push({ type, value: text });
        }
        else if (type === 'key') {
            const key = String(raw.value ?? '').trim().slice(0, MAX_KEY_LENGTH);
            if (key)
                actions.push({ type, value: key });
        }
        else if (type === 'wait') {
            const ms = Math.max(0, Math.min(MAX_WAIT_MS, Math.floor(Number(raw.ms ?? 0))));
            actions.push({ type, ms });
        }
    }
    return actions;
}
async function readDesktopInputPolicyState(projectDir) {
    try {
        const raw = await fs.readFile(path.join(projectDir, SETTINGS_PATH), 'utf-8');
        const parsed = JSON.parse(raw);
        const killSwitch = parsed.killSwitch && typeof parsed.killSwitch === 'object' ? parsed.killSwitch : {};
        return {
            killSwitch: {
                active: Boolean(killSwitch.active),
                reason: typeof killSwitch.reason === 'string' ? killSwitch.reason.slice(0, 500) : '',
            },
            capabilityGrants: (0, capabilities_1.sanitizeCapabilityGrants)(parsed.capabilityGrants),
        };
    }
    catch {
        return { killSwitch: { active: false, reason: '' }, capabilityGrants: [] };
    }
}
function renderDesktopInputPreview(actions) {
    return actions.map((action, index) => {
        if (action.type === 'wait')
            return `${index + 1}. wait ${action.ms}ms`;
        return `${index + 1}. ${action.type} ${JSON.stringify(action.value)}`;
    }).join('\n');
}
async function executeDesktopInputAction(action) {
    if (action.type === 'wait') {
        await new Promise((resolve) => setTimeout(resolve, action.ms));
        return;
    }
    const value = action.type === 'text' ? action.value : action.value;
    const platform = os.platform();
    if (platform === 'win32') {
        const psLiteral = `'${value.replace(/'/g, "''")}'`;
        const encoded = Buffer.from(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait(${psLiteral})`, 'utf16le').toString('base64');
        await desktopCapture.execPromise(`powershell -NoProfile -EncodedCommand ${encoded}`, 10_000);
    }
    else if (platform === 'darwin') {
        const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
        await desktopCapture.execPromise(`osascript -e "tell application \"System Events\" to keystroke \"${escaped}\""`, 10_000);
    }
    else {
        await desktopCapture.execPromise(`xdotool type --delay 0 ${shellQuote(value)}`, 10_000);
    }
}
function shellQuote(value) {
    return `'${value.replace(/'/g, `'\\''`)}'`;
}
async function appendDesktopInputAudit(projectDir, event) {
    const dir = path.join(projectDir, DESKTOP_DIR);
    await fs.mkdir(dir, { recursive: true });
    const entry = {
        timestamp: new Date().toISOString(),
        tool: exports.DesktopInputReplayTool.name,
        ...event,
    };
    await fs.appendFile(path.join(dir, AUDIT_FILE), `${JSON.stringify(entry)}\n`, 'utf-8');
    await pruneDesktopInputEvidence(projectDir);
}
async function pruneDesktopInputEvidence(projectDir) {
    const dir = path.join(projectDir, DESKTOP_DIR);
    const auditPath = path.join(dir, AUDIT_FILE);
    try {
        const auditRaw = await fs.readFile(auditPath, 'utf-8');
        const lines = auditRaw.split(/\r?\n/).filter((line) => line.trim());
        if (lines.length > MAX_AUDIT_ENTRIES) {
            await fs.writeFile(auditPath, `${lines.slice(-MAX_AUDIT_ENTRIES).join('\n')}\n`, 'utf-8');
        }
    }
    catch {
        // Missing audit files are fine; the next append will create one.
    }
    const files = await fs.readdir(dir).catch(() => []);
    const screenshots = files.filter((name) => SCREENSHOT_PATTERN.test(name)).sort();
    const staleScreenshots = screenshots.slice(0, Math.max(0, screenshots.length - MAX_SCREENSHOT_FILES));
    await Promise.all(staleScreenshots.map((name) => fs.rm(path.join(dir, name), { force: true })));
}
//# sourceMappingURL=desktopInputTools.js.map