import { CLIError, runToolsCLI } from "@midscene/shared/cli";
import { getMidsceneLocationSchema, z } from "@midscene/core";
import { getDebug } from "@midscene/shared/logger";
import { BaseMidsceneTools } from "@midscene/shared/mcp";
import { Agent } from "@midscene/core/agent";
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
import node_assert from "node:assert";
import node_fs, { accessSync, constants } from "node:fs";
import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
import { getTmpFile, sleep } from "@midscene/core/utils";
import { createImgBase64ByFormat } from "@midscene/shared/img";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const defaultAppNameMapping = {
    设置: 'com.huawei.hmos.settings',
    Settings: 'com.huawei.hmos.settings',
    相机: 'com.huawei.hmos.camera',
    Camera: 'com.huawei.hmos.camera',
    图库: 'com.huawei.hmos.photos',
    Gallery: 'com.huawei.hmos.photos',
    日历: 'com.huawei.hmos.calendar',
    Calendar: 'com.huawei.hmos.calendar',
    时钟: 'com.huawei.hmos.clock',
    Clock: 'com.huawei.hmos.clock',
    计算器: 'com.huawei.hmos.calculator',
    Calculator: 'com.huawei.hmos.calculator',
    文件管理: 'com.huawei.hmos.filemanager',
    备忘录: 'com.huawei.hmos.notepad',
    联系人: 'com.huawei.hmos.contacts',
    电话: 'com.huawei.hmos.phone',
    信息: 'com.huawei.hmos.message',
    邮件: 'com.huawei.hmos.email',
    浏览器: 'com.huawei.hmos.browser',
    Browser: 'com.huawei.hmos.browser',
    应用市场: 'com.huawei.appmarket',
    AppGallery: 'com.huawei.appmarket',
    华为音乐: 'com.huawei.hmos.music',
    华为视频: 'com.huawei.hmos.video',
    天气: 'com.huawei.hmos.weather',
    Weather: 'com.huawei.hmos.weather',
    抖音: 'com.ss.hm.ugc.aweme',
    支付宝: 'com.alipay.mobile.client',
    高德地图: 'com.amap.hmapp',
    百度: 'com.baidu.baiduapp',
    携程: 'com.ctrip.harmonynext'
};
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const execFileAsync = promisify(execFile);
const debugHdc = getDebug('harmony:hdc');
function resolveHdcPath(hdcPath) {
    if (hdcPath) return hdcPath;
    if (process.env.HDC_HOME) {
        const envPath = `${process.env.HDC_HOME}/hdc`;
        debugHdc(`Using HDC from HDC_HOME: ${envPath}`);
        return envPath;
    }
    const homeDir = process.env.HOME || process.env.USERPROFILE || '';
    const commonPaths = [
        `${homeDir}/Library/HarmonyOS/next/command-line-tools/sdk/default/openharmony/toolchains/hdc`,
        `${homeDir}/Library/HarmonyOS/sdk/hmscore/3.1.0/toolchains/hdc`
    ];
    for (const p of commonPaths)try {
        accessSync(p, constants.X_OK);
        debugHdc(`Found HDC at: ${p}`);
        return p;
    } catch  {}
    return 'hdc';
}
class HdcClient {
    buildArgs(args) {
        if (this.deviceId) return [
            '-t',
            this.deviceId,
            ...args
        ];
        return args;
    }
    async exec(...args) {
        let release;
        const prev = this.execMutex;
        this.execMutex = new Promise((r)=>{
            release = r;
        });
        await prev;
        const fullArgs = this.buildArgs(args);
        debugHdc(`hdc ${fullArgs.join(' ')}`);
        try {
            const { stdout, stderr } = await execFileAsync(this.hdcPath, fullArgs, {
                timeout: this.timeout,
                maxBuffer: 52428800
            });
            if (stderr?.trim()) debugHdc(`hdc stderr: ${stderr.trim()}`);
            debugHdc(`hdc ${fullArgs.join(' ')} end`);
            return stdout;
        } catch (error) {
            if (error.killed && error.stdout?.trim()) {
                debugHdc('hdc process was killed but stdout is available, treating as success');
                return error.stdout;
            }
            debugHdc(`hdc error: ${error.message}`);
            throw new Error(`HDC command failed: hdc ${fullArgs.join(' ')}: ${error.message}`, {
                cause: error
            });
        } finally{
            release();
        }
    }
    async shell(command) {
        return this.exec('shell', command);
    }
    async fileSend(localPath, remotePath) {
        await this.exec('file', 'send', localPath, remotePath);
    }
    async fileRecv(remotePath, localPath) {
        await this.exec('file', 'recv', remotePath, localPath);
    }
    async screenshot(remotePath) {
        return await this.shell(`snapshot_display -f ${remotePath}`);
    }
    async click(x, y) {
        await this.shell(`uitest uiInput click ${Math.round(x)} ${Math.round(y)}`);
    }
    async doubleClick(x, y) {
        await this.shell(`uitest uiInput doubleClick ${Math.round(x)} ${Math.round(y)}`);
    }
    async longClick(x, y) {
        await this.shell(`uitest uiInput longClick ${Math.round(x)} ${Math.round(y)}`);
    }
    async swipe(fromX, fromY, toX, toY, speed) {
        const args = [
            Math.round(fromX),
            Math.round(fromY),
            Math.round(toX),
            Math.round(toY)
        ];
        if (void 0 !== speed) args.push(Math.round(speed));
        await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
    }
    async fling(fromX, fromY, toX, toY, speed) {
        const args = [
            Math.round(fromX),
            Math.round(fromY),
            Math.round(toX),
            Math.round(toY)
        ];
        if (void 0 !== speed) args.push(Math.round(speed));
        await this.shell(`uitest uiInput fling ${args.join(' ')}`);
    }
    async drag(fromX, fromY, toX, toY, speed) {
        const args = [
            Math.round(fromX),
            Math.round(fromY),
            Math.round(toX),
            Math.round(toY)
        ];
        if (void 0 !== speed) args.push(Math.round(speed));
        await this.shell(`uitest uiInput drag ${args.join(' ')}`);
    }
    async inputText(x, y, text) {
        const escapedText = text.replace(/'/g, "'\\''");
        await this.shell(`uitest uiInput inputText ${Math.round(x)} ${Math.round(y)} '${escapedText}'`);
    }
    async keyEvent(...keys) {
        await this.shell(`uitest uiInput keyEvent ${keys.join(' ')}`);
    }
    async clearTextField(length = 100) {
        const keys = [];
        for(let i = 0; i < length; i++)keys.push('2055', '2071');
        await this.keyEvent(...keys);
    }
    async startAbility(bundleName, abilityName) {
        const output = await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
        if (output.includes('error:')) throw new Error(`Failed to start ${bundleName}/${abilityName}: ${output.trim()}`);
    }
    async queryMainAbility(bundleName) {
        const output = await this.shell(`bm dump -n ${bundleName}`);
        const names = [];
        for (const match of output.matchAll(/"name"\s*:\s*"([^"]+)"/g))names.push(match[1]);
        for (const candidate of [
            'EntryAbility',
            'MainAbility',
            `${bundleName}.MainAbility`
        ])if (names.includes(candidate)) return candidate;
        return names.find((n)=>n !== bundleName && n.endsWith('Ability') && !n.includes('Extension') && !n.includes('Service') && !n.includes('Form') && !n.includes('Dialog'));
    }
    async forceStop(bundleName) {
        await this.shell(`aa force-stop ${bundleName}`);
    }
    async getScreenInfo() {
        const stdout = await this.shell('hidumper -s RenderService -a screen');
        const activeFoldMatch = stdout.match(/foldScreenId:(\d+),\s*isConnected:\d+,\s*isPowerOn:1/);
        if (activeFoldMatch) {
            const activeId = activeFoldMatch[1];
            const screenRegex = new RegExp(`screen\\[\\d+\\]:\\s*id=${activeId},.*?render size:\\s*(\\d{3,5})x(\\d{3,5})`);
            const screenMatch = stdout.match(screenRegex);
            if (screenMatch) {
                debugHdc(`Foldable screen detected, active screen id=${activeId}: ${screenMatch[1]}x${screenMatch[2]}`);
                return {
                    width: Number.parseInt(screenMatch[1], 10),
                    height: Number.parseInt(screenMatch[2], 10)
                };
            }
        }
        const renderSizeMatch = stdout.match(/render size:\s*(\d{3,5})x(\d{3,5})/);
        if (renderSizeMatch) return {
            width: Number.parseInt(renderSizeMatch[1], 10),
            height: Number.parseInt(renderSizeMatch[2], 10)
        };
        const displayStdout = await this.shell('hidumper -s DisplayManagerService -a -a');
        const displayMatch = displayStdout.match(/activeModes.*?(\d{3,5}),\s*(\d{3,5})/);
        if (displayMatch) return {
            width: Number.parseInt(displayMatch[1], 10),
            height: Number.parseInt(displayMatch[2], 10)
        };
        throw new Error(`Failed to get screen size from HDC. RenderService output: ${stdout}`);
    }
    async listTargets() {
        const stdout = await this.exec('list', 'targets');
        return stdout.trim().split('\n').map((line)=>line.trim()).filter((line)=>line.length > 0 && !line.startsWith('['));
    }
    constructor(options){
        _define_property(this, "hdcPath", void 0);
        _define_property(this, "deviceId", void 0);
        _define_property(this, "timeout", void 0);
        _define_property(this, "execMutex", Promise.resolve());
        this.hdcPath = resolveHdcPath(options.hdcPath);
        this.deviceId = options.deviceId ?? '';
        this.timeout = options.timeout ?? 60000;
    }
}
function device_define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const defaultScrollUntilTimes = 10;
const defaultSwipeSpeed = 600;
const defaultFastSwipeSpeed = 2000;
const maxScrollDistance = 9999999;
const scrollQuadrantDivisions = 4;
const debugDevice = getDebug('harmony:device');
const harmonyKeyCodeMap = {
    Enter: '2054',
    Backspace: '2055',
    Tab: '2049',
    Escape: '2070',
    Home: 'Home',
    ArrowUp: '2012',
    ArrowDown: '2013',
    ArrowLeft: '2014',
    ArrowRight: '2015',
    Space: '2050',
    Delete: '2071'
};
const keyNameAliasMap = {
    enter: 'Enter',
    backspace: 'Backspace',
    tab: 'Tab',
    escape: 'Escape',
    esc: 'Escape',
    home: 'Home',
    space: 'Space',
    delete: 'Delete',
    arrowup: 'ArrowUp',
    arrowdown: 'ArrowDown',
    arrowleft: 'ArrowLeft',
    arrowright: 'ArrowRight',
    up: 'ArrowUp',
    down: 'ArrowDown',
    left: 'ArrowLeft',
    right: 'ArrowRight'
};
class HarmonyDevice {
    actionSpace() {
        const defaultActions = [
            defineActionTap(async (param)=>{
                const element = param.locate;
                node_assert(element, 'Element not found, cannot tap');
                await this.tap(element.center[0], element.center[1]);
            }),
            defineActionDoubleClick(async (param)=>{
                const element = param.locate;
                node_assert(element, 'Element not found, cannot double click');
                await this.doubleTap(element.center[0], element.center[1]);
            }),
            defineAction({
                name: 'Input',
                description: 'Input text into the input field',
                interfaceAlias: 'aiInput',
                paramSchema: z.object({
                    value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
                    mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
                        'replace',
                        'clear',
                        'typeOnly'
                    ]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - attempt to clear the field (limited support on HarmonyOS).')),
                    locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
                }),
                call: async (param)=>{
                    const element = param.locate;
                    if ('clear' === param.mode) return void await this.clearInput(element);
                    if (!param || !param.value) return;
                    const shouldReplace = 'typeOnly' !== param.mode;
                    await this.inputText(param.value, element, shouldReplace);
                }
            }),
            defineActionScroll(async (param)=>{
                const element = param.locate;
                const startingPoint = element ? {
                    left: element.center[0],
                    top: element.center[1]
                } : void 0;
                const scrollToEventName = param?.scrollType;
                if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
                else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
                else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
                else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
                else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
                else {
                    if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
                    else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
                    else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
                    else throw new Error(`Unknown scroll direction: ${param.direction}`);
                    else await this.scrollDown(param?.distance ?? void 0, startingPoint);
                    await sleep(500);
                }
            }),
            defineActionDragAndDrop(async (param)=>{
                const from = param.from;
                const to = param.to;
                node_assert(from, 'missing "from" param for drag and drop');
                node_assert(to, 'missing "to" param for drag and drop');
                const hdc = await this.getHdc();
                await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
            }),
            defineActionSwipe(async (param)=>{
                const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
                const hdc = await this.getHdc();
                for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
            }),
            defineActionKeyboardPress(async (param)=>{
                await this.keyboardPress(param.keyName);
            }),
            defineActionCursorMove(async (param)=>{
                const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
                const times = param.times ?? 1;
                for(let i = 0; i < times; i++){
                    await this.keyboardPress(arrowKey);
                    await sleep(100);
                }
            }),
            defineAction({
                name: 'LongPress',
                description: 'Trigger a long press on the screen at specified element',
                paramSchema: z.object({
                    duration: z.number().optional().describe('The duration of the long press in milliseconds'),
                    locate: getMidsceneLocationSchema().describe('The element to be long pressed')
                }),
                call: async (param)=>{
                    const element = param.locate;
                    if (!element) throw new Error('LongPress requires an element to be located');
                    await this.longPress(element.center[0], element.center[1]);
                }
            }),
            defineActionClearInput(async (param)=>{
                await this.clearInput(param.locate);
            })
        ];
        const platformSpecificActions = Object.values(createPlatformActions(this));
        const customActions = this.customActions ?? [];
        return [
            ...defaultActions,
            ...platformSpecificActions,
            ...customActions
        ];
    }
    describe() {
        return this.descriptionText || `DeviceId: ${this.deviceId}`;
    }
    async connect() {
        const hdc = await this.getHdc();
        return hdc;
    }
    async getHdc() {
        if (this.destroyed) throw new Error(`HarmonyDevice ${this.deviceId} has been destroyed and cannot execute HDC commands`);
        if (this.hdc) return this.hdc;
        if (this.connecting) return this.connecting;
        this.connecting = (async ()=>{
            debugDevice(`Initializing HDC with device ID: ${this.deviceId}`);
            try {
                this.hdc = new HdcClient({
                    hdcPath: this.options?.hdcPath,
                    deviceId: this.deviceId
                });
                const screenInfo = await this.hdc.getScreenInfo();
                this.cachedScreenSize = screenInfo;
                this.descriptionText = `DeviceId: ${this.deviceId}\nScreenSize: ${screenInfo.width}x${screenInfo.height}`;
                debugDevice('HDC initialized successfully', this.descriptionText);
                return this.hdc;
            } catch (e) {
                debugDevice(`Failed to initialize HDC: ${e}`);
                throw new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
            } finally{
                this.connecting = null;
            }
        })();
        return this.connecting;
    }
    setAppNameMapping(mapping) {
        this.appNameMapping = mapping;
    }
    resolvePackageName(appName) {
        const normalizedAppName = normalizeForComparison(appName);
        return this.appNameMapping[normalizedAppName];
    }
    async launch(uri) {
        const hdc = await this.getHdc();
        this.uri = uri;
        try {
            debugDevice(`Launching app: ${uri}`);
            if (uri.startsWith('http://') || uri.startsWith('https://') || uri.includes('://')) {
                const sanitizedUri = uri.replace(/[`$\\;"'|&<>(){}]/g, '');
                await hdc.shell(`aa start -U ${sanitizedUri}`);
            } else if (uri.includes('/')) {
                const [bundleName, abilityName] = uri.split('/');
                await hdc.startAbility(bundleName, abilityName);
            } else {
                const bundleName = this.resolvePackageName(uri) ?? uri;
                try {
                    await hdc.startAbility(bundleName, 'EntryAbility');
                } catch (e) {
                    if (!e.message?.includes('resolve ability')) throw e;
                    const mainAbility = await hdc.queryMainAbility(bundleName);
                    if (!mainAbility) throw new Error(`Cannot find a launchable ability for ${bundleName}`);
                    debugDevice(`EntryAbility not found, using discovered ability: ${mainAbility}`);
                    await hdc.startAbility(bundleName, mainAbility);
                }
            }
            debugDevice(`Successfully launched: ${uri}`);
        } catch (error) {
            debugDevice(`Error launching ${uri}: ${error}`);
            throw new Error(`Failed to launch ${uri}: ${error.message}`, {
                cause: error
            });
        }
        return this;
    }
    async getScreenSize() {
        if (this.cachedScreenSize) return this.cachedScreenSize;
        const hdc = await this.getHdc();
        const screenInfo = await hdc.getScreenInfo();
        this.cachedScreenSize = screenInfo;
        return screenInfo;
    }
    async size() {
        const screenInfo = await this.getScreenSize();
        const scale = this.options?.screenshotResizeScale ?? 1;
        const logicalWidth = Math.round(screenInfo.width * scale);
        const logicalHeight = Math.round(screenInfo.height * scale);
        return {
            width: logicalWidth,
            height: logicalHeight
        };
    }
    async screenshotBase64() {
        debugDevice('screenshotBase64 begin');
        const hdc = await this.getHdc();
        if (!this.localScreenshotPath) this.localScreenshotPath = getTmpFile('jpeg');
        const maxAttempts = 2;
        for(let attempt = 1; attempt <= maxAttempts; attempt++){
            const snapshotOutput = await hdc.screenshot(this.remoteScreenshotPath);
            const dimMatch = snapshotOutput.match(/width\s+(\d+),\s*height\s+(\d+)/);
            if (dimMatch) {
                const w = Number.parseInt(dimMatch[1], 10);
                const h = Number.parseInt(dimMatch[2], 10);
                if (this.cachedScreenSize && (this.cachedScreenSize.width !== w || this.cachedScreenSize.height !== h)) {
                    debugDevice(`Screen size changed: ${this.cachedScreenSize.width}x${this.cachedScreenSize.height} -> ${w}x${h}`);
                    this.cachedScreenSize = {
                        width: w,
                        height: h
                    };
                }
            }
            await hdc.fileRecv(this.remoteScreenshotPath, this.localScreenshotPath);
            const screenshotBuffer = await node_fs.promises.readFile(this.localScreenshotPath);
            if (screenshotBuffer && screenshotBuffer.length > 0) {
                debugDevice(`Screenshot captured: ${screenshotBuffer.length} bytes`);
                return createImgBase64ByFormat('jpeg', screenshotBuffer.toString('base64'));
            }
            debugDevice(`Screenshot buffer empty (attempt ${attempt}/${maxAttempts})`);
            if (attempt < maxAttempts) await sleep(200);
        }
        throw new Error('Screenshot buffer is empty after retries');
    }
    async tap(x, y) {
        this.lastTapPosition = {
            x,
            y
        };
        const hdc = await this.getHdc();
        await hdc.click(x, y);
    }
    async doubleTap(x, y) {
        const hdc = await this.getHdc();
        await hdc.doubleClick(x, y);
    }
    async longPress(x, y) {
        const hdc = await this.getHdc();
        await hdc.longClick(x, y);
    }
    async inputText(text, element, shouldReplace) {
        if (!text) return;
        const hdc = await this.getHdc();
        let x;
        let y;
        if (element) [x, y] = element.center;
        else if (this.lastTapPosition) {
            x = this.lastTapPosition.x;
            y = this.lastTapPosition.y;
        } else {
            const { width, height } = await this.size();
            x = Math.round(width / 2);
            y = Math.round(height / 2);
        }
        if (shouldReplace) {
            await hdc.click(x, y);
            await sleep(100);
            await hdc.clearTextField(100);
            await sleep(100);
        }
        await hdc.inputText(x, y, text);
        if (this.options?.autoDismissKeyboard) await this.hideKeyboard();
    }
    async clearInput(element) {
        const hdc = await this.getHdc();
        if (element) {
            await hdc.click(element.center[0], element.center[1]);
            await sleep(100);
        }
        await hdc.clearTextField(100);
    }
    async keyboardPress(key) {
        const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
        const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
        const hdc = await this.getHdc();
        await hdc.keyEvent(harmonyKey);
    }
    async scroll(deltaX, deltaY, speed) {
        if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
        const { width, height } = await this.size();
        const n = scrollQuadrantDivisions;
        const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
        const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
        const maxPositiveDeltaX = startX;
        const maxNegativeDeltaX = width - startX;
        const maxPositiveDeltaY = startY;
        const maxNegativeDeltaY = height - startY;
        deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
        deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
        const endX = Math.round(startX - deltaX);
        const endY = Math.round(startY - deltaY);
        const hdc = await this.getHdc();
        await hdc.swipe(startX, startY, endX, endY, speed ?? defaultSwipeSpeed);
    }
    async scrollInDirection(direction, distance, startPoint) {
        const { width, height } = await this.size();
        const isVertical = 'up' === direction || 'down' === direction;
        const scrollDistance = Math.round(distance ?? (isVertical ? height : width));
        if (startPoint) {
            const hdc = await this.getHdc();
            const sx = Math.round(startPoint.left);
            const sy = Math.round(startPoint.top);
            const endPoints = {
                down: {
                    x: sx,
                    y: Math.max(0, sy - scrollDistance)
                },
                up: {
                    x: sx,
                    y: Math.min(height, sy + scrollDistance)
                },
                left: {
                    x: Math.min(width, sx + scrollDistance),
                    y: sy
                },
                right: {
                    x: Math.max(0, sx - scrollDistance),
                    y: sy
                }
            };
            const end = endPoints[direction];
            await hdc.swipe(sx, sy, end.x, end.y);
            return;
        }
        const deltas = {
            down: [
                0,
                scrollDistance
            ],
            up: [
                0,
                -scrollDistance
            ],
            left: [
                -scrollDistance,
                0
            ],
            right: [
                scrollDistance,
                0
            ]
        };
        const [dx, dy] = deltas[direction];
        await this.scroll(dx, dy);
    }
    async scrollDown(distance, startPoint) {
        await this.scrollInDirection('down', distance, startPoint);
    }
    async scrollUp(distance, startPoint) {
        await this.scrollInDirection('up', distance, startPoint);
    }
    async scrollLeft(distance, startPoint) {
        await this.scrollInDirection('left', distance, startPoint);
    }
    async scrollRight(distance, startPoint) {
        await this.scrollInDirection('right', distance, startPoint);
    }
    async scrollUntilEdge(direction, startPoint) {
        if (startPoint) {
            const { width, height } = await this.size();
            const hdc = await this.getHdc();
            const sx = Math.round(startPoint.left);
            const sy = Math.round(startPoint.top);
            const flingTargets = {
                up: {
                    x: sx,
                    y: Math.round(height)
                },
                down: {
                    x: sx,
                    y: 0
                },
                left: {
                    x: Math.round(width),
                    y: sy
                },
                right: {
                    x: 0,
                    y: sy
                }
            };
            const target = flingTargets[direction];
            await repeat(defaultScrollUntilTimes, ()=>hdc.fling(sx, sy, target.x, target.y, defaultFastSwipeSpeed));
            await sleep(1000);
            return;
        }
        const deltas = {
            up: [
                0,
                -maxScrollDistance
            ],
            down: [
                0,
                maxScrollDistance
            ],
            left: [
                -maxScrollDistance,
                0
            ],
            right: [
                maxScrollDistance,
                0
            ]
        };
        const [dx, dy] = deltas[direction];
        await repeat(defaultScrollUntilTimes, ()=>this.scroll(dx, dy, defaultFastSwipeSpeed));
        await sleep(1000);
    }
    async scrollUntilTop(startPoint) {
        await this.scrollUntilEdge('up', startPoint);
    }
    async scrollUntilBottom(startPoint) {
        await this.scrollUntilEdge('down', startPoint);
    }
    async scrollUntilLeft(startPoint) {
        await this.scrollUntilEdge('left', startPoint);
    }
    async scrollUntilRight(startPoint) {
        await this.scrollUntilEdge('right', startPoint);
    }
    async back() {
        const hdc = await this.getHdc();
        await hdc.keyEvent('Back');
    }
    async home() {
        const hdc = await this.getHdc();
        await hdc.keyEvent('Home');
    }
    async recentApps() {
        const hdc = await this.getHdc();
        await hdc.keyEvent('RecentApps');
    }
    async hideKeyboard() {
        const hdc = await this.getHdc();
        await hdc.keyEvent('Back');
    }
    async getTimestamp() {
        const hdc = await this.getHdc();
        try {
            const stdout = await hdc.shell('date +%s%3N');
            const timestamp = Number.parseInt(stdout.trim(), 10);
            if (Number.isNaN(timestamp)) throw new Error(`Invalid timestamp format: ${stdout}`);
            debugDevice(`Got device time: ${timestamp}`);
            return timestamp;
        } catch (error) {
            debugDevice(`Failed to get device time: ${error}`);
            throw new Error(`Failed to get device time: ${error}`);
        }
    }
    async destroy() {
        if (this.destroyed) return;
        this.destroyed = true;
        this.cachedScreenSize = null;
        this.hdc = null;
        this.connecting = null;
    }
    constructor(deviceId, options){
        device_define_property(this, "deviceId", void 0);
        device_define_property(this, "hdc", null);
        device_define_property(this, "connecting", null);
        device_define_property(this, "destroyed", false);
        device_define_property(this, "descriptionText", void 0);
        device_define_property(this, "customActions", void 0);
        device_define_property(this, "cachedScreenSize", null);
        device_define_property(this, "appNameMapping", {});
        device_define_property(this, "lastTapPosition", null);
        device_define_property(this, "interfaceType", 'harmony');
        device_define_property(this, "uri", void 0);
        device_define_property(this, "options", void 0);
        device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
        device_define_property(this, "localScreenshotPath", null);
        node_assert(deviceId, 'deviceId is required for HarmonyDevice');
        this.deviceId = deviceId;
        this.options = options;
        this.customActions = options?.customActions;
    }
}
const runHdcShellParamSchema = z.object({
    command: z.string().describe('HDC shell command to execute')
});
const launchParamSchema = z.object({
    uri: z.string().describe('App name, bundle name, or URL to launch. Prioritize using the exact bundle name or URL the user has provided. If none provided, use the accurate app name.')
});
const createPlatformActions = (device)=>({
        RunHdcShell: defineAction({
            name: 'RunHdcShell',
            description: 'Execute HDC shell command on HarmonyOS device',
            interfaceAlias: 'runHdcShell',
            paramSchema: runHdcShellParamSchema,
            call: async (param)=>{
                if (!param.command || '' === param.command.trim()) throw new Error('RunHdcShell requires a non-empty command parameter');
                const hdc = await device.getHdc();
                return await hdc.shell(param.command);
            }
        }),
        Launch: defineAction({
            name: 'Launch',
            description: 'Launch a HarmonyOS app or URL',
            interfaceAlias: 'launch',
            paramSchema: launchParamSchema,
            call: async (param)=>{
                if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
                await device.launch(param.uri);
            }
        }),
        HarmonyBackButton: defineAction({
            name: 'HarmonyBackButton',
            description: 'Trigger the system "back" operation on HarmonyOS devices',
            call: async ()=>{
                await device.back();
            }
        }),
        HarmonyHomeButton: defineAction({
            name: 'HarmonyHomeButton',
            description: 'Trigger the system "home" operation on HarmonyOS devices',
            call: async ()=>{
                await device.home();
            }
        }),
        HarmonyRecentAppsButton: defineAction({
            name: 'HarmonyRecentAppsButton',
            description: 'Trigger the system "recent apps" operation on HarmonyOS devices',
            call: async ()=>{
                await device.recentApps();
            }
        })
    });
const debugUtils = getDebug('harmony:utils');
async function getConnectedDevices(hdcPath) {
    try {
        const hdc = new HdcClient({
            hdcPath
        });
        const targets = await hdc.listTargets();
        const devices = targets.map((deviceId)=>({
                deviceId
            }));
        debugUtils(`Found ${devices.length} connected devices: `, devices);
        return devices;
    } catch (error) {
        console.error('Failed to get device list:', error);
        throw new Error(`Unable to get connected HarmonyOS device list, please ensure HDC is properly configured: ${error.message}`, {
            cause: error
        });
    }
}
function agent_define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const debugAgent = getDebug('harmony:agent');
class HarmonyAgent extends Agent {
    async launch(uri) {
        const action = this.wrapActionInActionSpace('Launch');
        return action({
            uri
        });
    }
    async runHdcShell(command) {
        const action = this.wrapActionInActionSpace('RunHdcShell');
        return action({
            command
        });
    }
    createActionWrapper(name) {
        const action = this.wrapActionInActionSpace(name);
        return (...args)=>action(args[0]);
    }
    constructor(device, opts){
        super(device, opts), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0), agent_define_property(this, "appNameMapping", void 0);
        this.appNameMapping = mergeAndNormalizeAppNameMapping(defaultAppNameMapping, opts?.appNameMapping);
        device.setAppNameMapping(this.appNameMapping);
        this.back = this.createActionWrapper('HarmonyBackButton');
        this.home = this.createActionWrapper('HarmonyHomeButton');
        this.recentApps = this.createActionWrapper('HarmonyRecentAppsButton');
    }
}
async function agentFromHdcDevice(deviceId, opts) {
    if (!deviceId) {
        const devices = await getConnectedDevices(opts?.hdcPath);
        if (0 === devices.length) throw new Error('No HarmonyOS devices found. Please connect a HarmonyOS device and ensure HDC is properly configured. Run `hdc list targets` to verify device connection.');
        deviceId = devices[0].deviceId;
        debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
    }
    const device = new HarmonyDevice(deviceId, opts || {});
    await device.connect();
    return new HarmonyAgent(device, opts);
}
const debug = getDebug('mcp:harmony-tools');
class HarmonyMidsceneTools extends BaseMidsceneTools {
    createTemporaryDevice() {
        return new HarmonyDevice('temp-for-action-space', {});
    }
    async ensureAgent(deviceId) {
        if (this.agent && deviceId) {
            try {
                await this.agent.destroy?.();
            } catch (error) {
                debug('Failed to destroy agent during cleanup:', error);
            }
            this.agent = void 0;
        }
        if (this.agent) return this.agent;
        debug('Creating Harmony agent with deviceId:', deviceId || 'auto-detect');
        const agent = await agentFromHdcDevice(deviceId, {
            autoDismissKeyboard: false
        });
        this.agent = agent;
        return agent;
    }
    preparePlatformTools() {
        return [
            {
                name: 'harmony_connect',
                description: 'Connect to HarmonyOS device via HDC. If deviceId not provided, uses the first available device.',
                schema: {
                    deviceId: z.string().optional().describe('HarmonyOS device ID (from hdc list targets)')
                },
                handler: async ({ deviceId })=>{
                    const agent = await this.ensureAgent(deviceId);
                    const screenshot = await agent.page.screenshotBase64();
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `Connected to HarmonyOS device${deviceId ? `: ${deviceId}` : ' (auto-detected)'}`
                            },
                            ...this.buildScreenshotContent(screenshot)
                        ],
                        isError: false
                    };
                }
            },
            {
                name: 'harmony_disconnect',
                description: 'Disconnect from current HarmonyOS device and release HDC resources',
                schema: {},
                handler: this.createDisconnectHandler('HarmonyOS device')
            }
        ];
    }
}
const tools = new HarmonyMidsceneTools();
runToolsCLI(tools, 'midscene-harmony', {
    stripPrefix: 'harmony_'
}).catch((e)=>{
    if (!(e instanceof CLIError)) console.error(e);
    process.exit(e instanceof CLIError ? e.exitCode : 1);
});
