"use strict";
var __webpack_require__ = {};
(()=>{
    __webpack_require__.n = (module)=>{
        var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
        __webpack_require__.d(getter, {
            a: getter
        });
        return getter;
    };
})();
(()=>{
    __webpack_require__.d = (exports1, definition)=>{
        for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
            enumerable: true,
            get: definition[key]
        });
    };
})();
(()=>{
    __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
    __webpack_require__.r = (exports1)=>{
        if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
            value: 'Module'
        });
        Object.defineProperty(exports1, '__esModule', {
            value: true
        });
    };
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
    androidPlaygroundPlatform: ()=>androidPlaygroundPlatform,
    ScrcpyServer: ()=>ScrcpyServer
});
const external_node_path_namespaceObject = require("node:path");
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
const android_namespaceObject = require("@midscene/android");
const playground_namespaceObject = require("@midscene/playground");
const constants_namespaceObject = require("@midscene/shared/constants");
const node_namespaceObject = require("@midscene/shared/node");
async function getAdbTargets() {
    const devices = await (0, android_namespaceObject.getConnectedDevicesWithDetails)();
    return devices.filter((device)=>'device' === device.state).map((device, index)=>({
            id: device.udid,
            label: device.udid,
            description: [
                device.model,
                device.resolution
            ].filter(Boolean).join(' · ') || device.state,
            status: device.state,
            isDefault: 0 === index
        }));
}
const androidPlaygroundPlatform = (0, playground_namespaceObject.definePlaygroundPlatform)({
    id: 'android',
    title: 'Midscene Android Playground',
    description: "Android playground platform descriptor",
    async prepare (options) {
        const staticDir = options?.staticDir || external_node_path_default().join(__dirname, '../../static');
        const [playgroundPort, resolvedScrcpyPort] = await Promise.all([
            (0, node_namespaceObject.findAvailablePort)(constants_namespaceObject.PLAYGROUND_SERVER_PORT),
            options?.scrcpyPort ? Promise.resolve(options.scrcpyPort) : (0, node_namespaceObject.findAvailablePort)(constants_namespaceObject.SCRCPY_SERVER_PORT)
        ]);
        const scrcpyPort = resolvedScrcpyPort;
        if (playgroundPort !== constants_namespaceObject.PLAYGROUND_SERVER_PORT) console.log(`⚠️  Port ${constants_namespaceObject.PLAYGROUND_SERVER_PORT} is busy, using port ${playgroundPort} instead`);
        if (scrcpyPort !== constants_namespaceObject.SCRCPY_SERVER_PORT) console.log(`⚠️  Port ${constants_namespaceObject.SCRCPY_SERVER_PORT} is busy, using port ${scrcpyPort} instead`);
        const sessionManager = {
            async getSetupSchema () {
                const targets = await getAdbTargets();
                return {
                    title: 'Connect Android device',
                    description: 'Select an available ADB device to create the current Android Agent.',
                    primaryActionLabel: 'Create Agent',
                    autoSubmitWhenReady: 1 === targets.length,
                    fields: [
                        {
                            key: 'deviceId',
                            label: 'ADB device',
                            type: 'select',
                            required: true,
                            options: targets.map((target)=>({
                                    label: target.label,
                                    value: target.id,
                                    description: target.description
                                })),
                            defaultValue: targets.find((target)=>target.isDefault)?.id,
                            placeholder: 'Select a connected Android device'
                        }
                    ],
                    targets
                };
            },
            listTargets: getAdbTargets,
            async createSession (input) {
                const targets = await getAdbTargets();
                const deviceId = 'string' == typeof input?.deviceId && input.deviceId ? input.deviceId : targets.find((target)=>target.isDefault)?.id;
                if (!deviceId) throw new Error('No Android devices found. Connect a device with USB debugging enabled and try again.');
                const connectAgent = async ()=>{
                    const device = new android_namespaceObject.AndroidDevice(deviceId);
                    await device.connect();
                    return new android_namespaceObject.AndroidAgent(device);
                };
                if (options?.scrcpyServer) options.scrcpyServer.currentDeviceId = deviceId;
                const agent = await connectAgent();
                return {
                    agent,
                    agentFactory: connectAgent,
                    preview: (0, playground_namespaceObject.createScrcpyPreviewDescriptor)({
                        scrcpyPort
                    }, {
                        title: 'Android device preview'
                    }),
                    displayName: deviceId,
                    metadata: {
                        deviceId,
                        scrcpyPort
                    }
                };
            }
        };
        return {
            platformId: 'android',
            title: 'Midscene Android Playground',
            sessionManager,
            sidecars: options?.scrcpyServer ? [
                {
                    id: 'android-scrcpy',
                    start: async ()=>{
                        await options.scrcpyServer?.launch(scrcpyPort);
                    },
                    stop: async ()=>{
                        options.scrcpyServer?.close();
                    }
                }
            ] : void 0,
            launchOptions: {
                port: playgroundPort,
                openBrowser: false,
                verbose: false,
                staticPath: staticDir,
                configureServer (server) {
                    server.scrcpyPort = scrcpyPort;
                }
            },
            preview: (0, playground_namespaceObject.createScrcpyPreviewDescriptor)({
                scrcpyPort
            }, {
                title: 'Android device preview'
            }),
            metadata: {
                scrcpyPort,
                sessionConnected: false,
                setupState: 'required'
            }
        };
    }
});
const external_node_child_process_namespaceObject = require("node:child_process");
const external_node_fs_namespaceObject = require("node:fs");
const external_node_http_namespaceObject = require("node:http");
require("node:url");
const external_node_util_namespaceObject = require("node:util");
const logger_namespaceObject = require("@midscene/shared/logger");
const external_cors_namespaceObject = require("cors");
var external_cors_default = /*#__PURE__*/ __webpack_require__.n(external_cors_namespaceObject);
const external_express_namespaceObject = require("express");
var external_express_default = /*#__PURE__*/ __webpack_require__.n(external_express_namespaceObject);
const external_socket_io_namespaceObject = require("socket.io");
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 debugPage = (0, logger_namespaceObject.getDebug)('android:playground');
const promiseExec = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.exec);
const LOOPBACK_HOSTS = new Set([
    'localhost',
    '127.0.0.1',
    '::1',
    '[::1]'
]);
function isPrivateIP(hostname) {
    return LOOPBACK_HOSTS.has(hostname) || /^10\./.test(hostname) || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname) || /^192\.168\./.test(hostname) || /^100\.(6[4-9]|[7-9]\d|1[0-2]\d)\./.test(hostname);
}
function isAllowedOrigin(origin) {
    if (!origin) return true;
    try {
        const url = new URL(origin);
        return isPrivateIP(url.hostname);
    } catch  {
        return false;
    }
}
class ScrcpyServer {
    setupApiRoutes() {
        this.app.get('/api/devices', async (req, res)=>{
            try {
                const devices = await this.getDevicesList();
                res.json({
                    devices,
                    currentDeviceId: this.currentDeviceId
                });
            } catch (error) {
                res.status(500).json({
                    error: error.message || 'Failed to get devices list'
                });
            }
        });
    }
    async getDevicesList() {
        try {
            debugPage('start to get devices list');
            const client = await this.getAdbClient();
            if (!client) {
                console.warn('failed to get adb client');
                return [];
            }
            debugPage('success to get adb client, start to request devices list');
            let devices;
            try {
                devices = await client.getDevices();
                debugPage('original devices list:', devices);
            } catch (error) {
                console.error('failed to get devices list:', error);
                return [];
            }
            if (!devices || 0 === devices.length) return [];
            const formattedDevices = devices.map((device)=>{
                const result = {
                    id: device.serial,
                    name: device.product || device.model || device.serial,
                    status: device.state || 'device'
                };
                return result;
            });
            return formattedDevices;
        } catch (error) {
            console.error('failed to get devices list:', error);
            return [];
        }
    }
    async getAdbClient() {
        const { AdbServerClient } = await import("@yume-chan/adb");
        const { AdbServerNodeTcpConnector } = await import("@yume-chan/adb-server-node-tcp");
        try {
            if (this.adbClient) debugPage('use existing adb client');
            else {
                await promiseExec('adb start-server');
                debugPage('adb server started');
                debugPage('initialize adb client');
                this.adbClient = new AdbServerClient(new AdbServerNodeTcpConnector({
                    host: '127.0.0.1',
                    port: 5037
                }));
                await debugPage('success to initialize adb client');
            }
            return this.adbClient;
        } catch (error) {
            console.error('failed to get adb client:', error);
            return null;
        }
    }
    async getAdb(deviceId) {
        const { Adb } = await import("@yume-chan/adb");
        try {
            const client = await this.getAdbClient();
            if (!client) return null;
            const targetDeviceId = deviceId || this.currentDeviceId;
            if (targetDeviceId) {
                this.currentDeviceId = targetDeviceId;
                return new Adb(await client.createTransport({
                    serial: targetDeviceId
                }));
            }
            const devices = await client.getDevices();
            if (0 === devices.length) return null;
            this.currentDeviceId = devices[0].serial;
            return new Adb(await client.createTransport(devices[0]));
        } catch (error) {
            console.error('failed to get adb client:', error);
            return null;
        }
    }
    async startScrcpy(adb, options = {}) {
        const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
        const { ReadableStream } = await import("@yume-chan/stream-extra");
        const { DefaultServerPath } = await import("@yume-chan/scrcpy");
        const currentDir = __dirname;
        const serverBinPath = external_node_path_default().resolve(currentDir, '../../bin/scrcpy-server');
        try {
            await AdbScrcpyClient.pushServer(adb, ReadableStream.from((0, external_node_fs_namespaceObject.createReadStream)(serverBinPath)));
            const scrcpyOptions = new AdbScrcpyOptions3_3_3({
                audio: false,
                control: true,
                maxSize: 1024,
                sendFrameMeta: true,
                videoBitRate: 2000000,
                ...options
            });
            return await AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
        } catch (error) {
            console.error('failed to start scrcpy:', error);
            throw error;
        }
    }
    setupSocketHandlers() {
        this.io.on('connection', async (socket)=>{
            debugPage('client connected, id: %s, client address: %s', socket.id, socket.handshake.address);
            let scrcpyClient = null;
            let adb = null;
            const sendDevicesList = async ()=>{
                try {
                    debugPage('Socket request to get devices list');
                    const devices = await this.getDevicesList();
                    debugPage('send devices list to client:', devices);
                    socket.emit('devices-list', {
                        devices,
                        currentDeviceId: this.currentDeviceId
                    });
                } catch (error) {
                    console.error('failed to send devices list:', error);
                    socket.emit('error', {
                        message: 'failed to get devices list'
                    });
                }
            };
            await sendDevicesList();
            socket.on('get-devices', async ()=>{
                debugPage('received client request to get devices list');
                await sendDevicesList();
            });
            socket.on('switch-device', async (deviceId)=>{
                debugPage('received client request to switch device:', deviceId);
                try {
                    if (scrcpyClient) {
                        await scrcpyClient.close();
                        scrcpyClient = null;
                    }
                    this.currentDeviceId = deviceId;
                    debugPage('device switched to:', deviceId);
                    socket.emit('device-switched', {
                        deviceId
                    });
                    this.io.emit('global-device-switched', {
                        deviceId,
                        timestamp: Date.now()
                    });
                } catch (error) {
                    console.error('failed to switch device:', error);
                    socket.emit('error', {
                        message: `Failed to switch device: ${error?.message || 'Unknown error'}`
                    });
                }
            });
            socket.on('connect-device', async (options)=>{
                const { ScrcpyVideoCodecId } = await import("@yume-chan/scrcpy");
                try {
                    debugPage('received device connection request, options: %s, client id: %s', options, socket.id);
                    adb = await this.getAdb(this.currentDeviceId || void 0);
                    if (!adb) {
                        console.error('no available device found');
                        socket.emit('error', {
                            message: 'No device found'
                        });
                        return;
                    }
                    debugPage('starting scrcpy service, device id: %s', this.currentDeviceId);
                    scrcpyClient = await this.startScrcpy(adb, options);
                    debugPage('scrcpy service started successfully');
                    debugPage('check scrcpyClient object structure: %s', Object.getOwnPropertyNames(scrcpyClient).map((name)=>{
                        const type = typeof scrcpyClient[name];
                        const isPromise = 'object' === type && scrcpyClient[name] && 'function' == typeof scrcpyClient[name].then;
                        return `${name}: ${type}${isPromise ? ' (Promise)' : ''}`;
                    }));
                    try {
                        if (scrcpyClient.videoStream) {
                            debugPage('videoStream exists, type: %s', typeof scrcpyClient.videoStream);
                            let videoStream;
                            if ('object' == typeof scrcpyClient.videoStream && 'function' == typeof scrcpyClient.videoStream.then) {
                                debugPage('videoStream is a Promise, waiting for resolution...');
                                videoStream = await scrcpyClient.videoStream;
                            } else {
                                debugPage('videoStream is not a Promise, directly use');
                                videoStream = scrcpyClient.videoStream;
                            }
                            debugPage('video stream fetched successfully, metadata: %s', videoStream.metadata);
                            const metadata = videoStream.metadata || {};
                            debugPage('original metadata: %s', metadata);
                            if (!metadata.codec) {
                                debugPage('metadata does not have codec field, use H264 by default');
                                metadata.codec = ScrcpyVideoCodecId.H264;
                            }
                            if (!metadata.width || !metadata.height) {
                                debugPage('metadata does not have width or height field, use default values');
                                metadata.width = metadata.width || 1080;
                                metadata.height = metadata.height || 1920;
                            }
                            debugPage('prepare to send video-metadata event to client, data: %s', JSON.stringify(metadata));
                            socket.emit('video-metadata', metadata);
                            debugPage('video-metadata event sent to client, id: %s', socket.id);
                            const { stream } = videoStream;
                            const reader = stream.getReader();
                            const processStream = async ()=>{
                                try {
                                    while(true){
                                        const { done, value } = await reader.read();
                                        if (done) break;
                                        const frameType = value.type || 'data';
                                        socket.emit('video-data', {
                                            data: Array.from(value.data),
                                            type: frameType,
                                            timestamp: Date.now(),
                                            keyFrame: value.keyFrame
                                        });
                                    }
                                } catch (error) {
                                    console.error('error processing video stream:', error);
                                    socket.emit('error', {
                                        message: 'video stream processing error'
                                    });
                                }
                            };
                            processStream();
                        } else {
                            console.error('scrcpyClient object does not have videoStream property');
                            socket.emit('error', {
                                message: 'Video stream not available in scrcpy client'
                            });
                        }
                    } catch (error) {
                        console.error('error processing video stream:', error);
                        socket.emit('error', {
                            message: `Video stream processing error: ${error.message}`
                        });
                    }
                    if (scrcpyClient?.controller) socket.emit('control-ready');
                } catch (error) {
                    console.error('failed to connect device:', error);
                    socket.emit('error', {
                        message: `Failed to connect device: ${error?.message || 'Unknown error'}`
                    });
                }
            });
            socket.on('disconnect', async (reason)=>{
                debugPage('client disconnected, id: %s, reason: %s', socket.id, reason);
                if (scrcpyClient) {
                    try {
                        debugPage('closing scrcpy client');
                        await scrcpyClient.close();
                    } catch (error) {
                        console.error('failed to close scrcpy client:', error);
                    }
                    scrcpyClient = null;
                }
            });
        });
    }
    async launch(port) {
        this.port = port || this.defaultPort;
        return new Promise((resolve)=>{
            const listenPort = this.port ?? this.defaultPort;
            this.httpServer.listen(listenPort, '0.0.0.0', ()=>{
                console.log(`Scrcpy server running at: http://0.0.0.0:${this.port}`);
                this.startDeviceMonitoring();
                resolve(this);
            });
        });
    }
    startDeviceMonitoring() {
        this.devicePollInterval = setInterval(async ()=>{
            try {
                const devices = await this.getDevicesList();
                const currentDevicesJson = JSON.stringify(devices);
                if (this.lastDeviceList !== currentDevicesJson) {
                    debugPage('devices list changed, push to all connected clients');
                    this.lastDeviceList = currentDevicesJson;
                    if (!this.currentDeviceId && devices.length > 0) {
                        const onlineDevices = devices.filter((device)=>'device' === device.status.toLowerCase());
                        if (onlineDevices.length > 0) {
                            this.currentDeviceId = onlineDevices[0].id;
                            debugPage('auto select the first online device:', this.currentDeviceId);
                        }
                    }
                    this.io.emit('devices-list', {
                        devices,
                        currentDeviceId: this.currentDeviceId
                    });
                }
            } catch (error) {
                console.error('device monitoring error:', error);
            }
        }, 3000);
    }
    close() {
        if (this.devicePollInterval) {
            clearInterval(this.devicePollInterval);
            this.devicePollInterval = null;
        }
        if (this.httpServer) return this.httpServer.close();
    }
    constructor(){
        _define_property(this, "app", void 0);
        _define_property(this, "httpServer", void 0);
        _define_property(this, "io", void 0);
        _define_property(this, "port", void 0);
        _define_property(this, "defaultPort", constants_namespaceObject.SCRCPY_SERVER_PORT);
        _define_property(this, "adbClient", null);
        _define_property(this, "currentDeviceId", null);
        _define_property(this, "devicePollInterval", null);
        _define_property(this, "lastDeviceList", '');
        this.app = external_express_default()();
        this.httpServer = (0, external_node_http_namespaceObject.createServer)(this.app);
        this.io = new external_socket_io_namespaceObject.Server(this.httpServer, {
            cors: {
                origin (origin, callback) {
                    callback(null, isAllowedOrigin(origin));
                },
                methods: [
                    'GET',
                    'POST'
                ],
                credentials: true
            }
        });
        this.app.use(external_cors_default()({
            origin (origin, callback) {
                callback(null, isAllowedOrigin(origin));
            },
            credentials: true
        }));
        this.setupSocketHandlers();
        this.setupApiRoutes();
    }
}
exports.ScrcpyServer = __webpack_exports__.ScrcpyServer;
exports.androidPlaygroundPlatform = __webpack_exports__.androidPlaygroundPlatform;
for(var __rspack_i in __webpack_exports__)if (-1 === [
    "ScrcpyServer",
    "androidPlaygroundPlatform"
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
Object.defineProperty(exports, '__esModule', {
    value: true
});
