// src/page/index.ts
import assert from "assert";
import fs from "fs";
import path from "path";
import { getTmpFile } from "@midscene/core/utils";
import { resizeImg } from "@midscene/shared/img";
import { getDebug } from "@midscene/shared/logger";
import { ADB } from "appium-adb";
var androidScreenshotPath = "/data/local/tmp/midscene_screenshot.png";
var debugPage = getDebug("android:device");
var AndroidDevice = class {
  constructor(deviceId) {
    this.screenSize = null;
    this.yadbPushed = false;
    this.deviceRatio = 1;
    this.adb = null;
    this.connectingAdb = null;
    this.pageType = "android";
    assert(deviceId, "deviceId is required for AndroidDevice");
    this.deviceId = deviceId;
  }
  async connect() {
    return this.getAdb();
  }
  async getAdb() {
    if (this.adb) {
      return this.adb;
    }
    if (this.connectingAdb) {
      return this.connectingAdb;
    }
    this.connectingAdb = (async () => {
      let error = null;
      debugPage(`Initializing ADB with device ID: ${this.deviceId}`);
      try {
        this.adb = await ADB.createADB({
          udid: this.deviceId,
          adbExecTimeout: 6e4
        });
        debugPage("ADB initialized successfully");
        return this.adb;
      } catch (e) {
        debugPage(`Failed to initialize ADB: ${e}`);
        error = new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
      } finally {
        this.connectingAdb = null;
      }
      if (error) {
        throw error;
      }
      throw new Error("ADB initialization failed unexpectedly");
    })();
    return this.connectingAdb;
  }
  async launch(uri) {
    const adb = await this.getAdb();
    try {
      if (uri.startsWith("http://") || uri.startsWith("https://") || uri.includes("://")) {
        await adb.startUri(uri);
      } else if (uri.includes("/")) {
        const [appPackage, appActivity] = uri.split("/");
        await adb.startApp({
          pkg: appPackage,
          activity: appActivity
        });
      } else {
        await adb.startApp({
          pkg: uri
        });
      }
      debugPage(`Successfully launched: ${uri}`);
    } catch (error) {
      debugPage(`Error launching ${uri}: ${error}`);
      throw new Error(`Failed to launch ${uri}: ${error}`, { cause: error });
    }
    return this;
  }
  async execYadb(keyboardContent) {
    await this.ensureYadb();
    const adb = await this.getAdb();
    await adb.shell(
      `app_process -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`
    );
  }
  async getElementsInfo() {
    return [];
  }
  async getElementsNodeTree() {
    return {
      node: null,
      children: []
    };
  }
  async size() {
    if (this.screenSize) {
      return this.screenSize;
    }
    const adb = await this.getAdb();
    const screenSize = await adb.getScreenSize();
    let width;
    let height;
    if (typeof screenSize === "string") {
      const match = screenSize.match(/(\d+)x(\d+)/);
      if (!match || match.length < 3) {
        throw new Error(`Unable to parse screen size: ${screenSize}`);
      }
      width = Number.parseInt(match[1], 10);
      height = Number.parseInt(match[2], 10);
    } else if (typeof screenSize === "object" && screenSize !== null) {
      const sizeObj = screenSize;
      if ("width" in sizeObj && "height" in sizeObj) {
        width = Number(sizeObj.width);
        height = Number(sizeObj.height);
      } else {
        throw new Error(
          `Invalid screen size object: ${JSON.stringify(screenSize)}`
        );
      }
    } else {
      throw new Error(`Invalid screen size format: ${screenSize}`);
    }
    const densityNum = await adb.getScreenDensity();
    this.deviceRatio = Number(densityNum) / 160;
    const { x: logicalWidth, y: logicalHeight } = this.reverseAdjustCoordinates(
      width,
      height
    );
    this.screenSize = {
      width: logicalWidth,
      height: logicalHeight
    };
    return this.screenSize;
  }
  /**
   * Convert logical coordinates to physical coordinates, handling device ratio
   * @param x Logical X coordinate
   * @param y Logical Y coordinate
   * @returns Physical coordinate point
   */
  adjustCoordinates(x, y) {
    const ratio = this.deviceRatio;
    return {
      x: Math.round(x * ratio),
      y: Math.round(y * ratio)
    };
  }
  /**
   * Convert physical coordinates to logical coordinates, handling device ratio
   * @param x Physical X coordinate
   * @param y Physical Y coordinate
   * @returns Logical coordinate point
   */
  reverseAdjustCoordinates(x, y) {
    const ratio = this.deviceRatio;
    return {
      x: Math.round(x / ratio),
      y: Math.round(y / ratio)
    };
  }
  async screenshotBase64() {
    debugPage("screenshotBase64 begin");
    const { width, height } = await this.size();
    const adb = await this.getAdb();
    let screenshotBuffer;
    try {
      screenshotBuffer = await adb.takeScreenshot(null);
    } catch (error) {
      const screenshotPath = getTmpFile("png");
      try {
        await adb.shell(`screencap -p ${androidScreenshotPath}`);
      } catch (error2) {
        await this.forceScreenshot(androidScreenshotPath);
      }
      await adb.pull(androidScreenshotPath, screenshotPath);
      screenshotBuffer = await fs.promises.readFile(screenshotPath);
    }
    const resizedScreenshotBuffer = await resizeImg(screenshotBuffer, {
      width,
      height
    });
    const result = `data:image/jpeg;base64,${resizedScreenshotBuffer.toString("base64")}`;
    debugPage("screenshotBase64 end");
    return result;
  }
  get mouse() {
    return {
      click: (x, y) => this.mouseClick(x, y),
      wheel: (deltaX, deltaY) => this.mouseWheel(deltaX, deltaY),
      move: (x, y) => this.mouseMove(x, y),
      drag: (from, to) => this.mouseDrag(from, to)
    };
  }
  get keyboard() {
    return {
      type: (text) => this.keyboardType(text),
      press: (action) => this.keyboardPressAction(action)
    };
  }
  async clearInput(element) {
    if (!element) {
      return;
    }
    await this.ensureYadb();
    const adb = await this.getAdb();
    await this.mouse.click(element.center[0], element.center[1]);
    await adb.shell(
      'app_process -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "~CLEAR~"'
    );
    await this.mouse.click(element.center[0], element.center[1]);
  }
  async forceScreenshot(path2) {
    await this.ensureYadb();
    const adb = await this.getAdb();
    await adb.shell(
      `app_process -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -screenshot ${path2}`
    );
  }
  async url() {
    const adb = await this.getAdb();
    const { appPackage, appActivity } = await adb.getFocusedPackageAndActivity();
    return `${appPackage}/${appActivity}`;
  }
  async scrollUntilTop(startPoint) {
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const end = { x: start.x, y: 0 };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(0, 9999999, 100);
  }
  async scrollUntilBottom(startPoint) {
    if (startPoint) {
      const { height } = await this.size();
      const start = { x: startPoint.left, y: startPoint.top };
      const end = { x: start.x, y: height };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(0, -9999999, 100);
  }
  async scrollUntilLeft(startPoint) {
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const end = { x: 0, y: start.y };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(9999999, 0, 100);
  }
  async scrollUntilRight(startPoint) {
    if (startPoint) {
      const { width } = await this.size();
      const start = { x: startPoint.left, y: startPoint.top };
      const end = { x: width, y: start.y };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(-9999999, 0, 100);
  }
  async scrollUp(distance, startPoint) {
    const { height } = await this.size();
    const scrollDistance = distance || height;
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const endY = Math.max(0, start.y - scrollDistance);
      const end = { x: start.x, y: endY };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(0, scrollDistance, 1e3);
  }
  async scrollDown(distance, startPoint) {
    const { height } = await this.size();
    const scrollDistance = distance || height;
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const endY = Math.min(height, start.y + scrollDistance);
      const end = { x: start.x, y: endY };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(0, -scrollDistance, 1e3);
  }
  async scrollLeft(distance, startPoint) {
    const { width } = await this.size();
    const scrollDistance = distance || width;
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const endX = Math.max(0, start.x - scrollDistance);
      const end = { x: endX, y: start.y };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(scrollDistance, 0, 1e3);
  }
  async scrollRight(distance, startPoint) {
    const { width } = await this.size();
    const scrollDistance = distance || width;
    if (startPoint) {
      const start = { x: startPoint.left, y: startPoint.top };
      const endX = Math.min(width, start.x + scrollDistance);
      const end = { x: endX, y: start.y };
      await this.mouseDrag(start, end);
      return;
    }
    await this.mouseWheel(-scrollDistance, 0, 1e3);
  }
  async ensureYadb() {
    if (!this.yadbPushed) {
      const adb = await this.getAdb();
      const yadbBin = path.join(__dirname, "../../bin/yadb");
      await adb.push(yadbBin, "/data/local/tmp");
      this.yadbPushed = true;
    }
  }
  async keyboardType(text) {
    if (!text)
      return;
    const adb = await this.getAdb();
    const isChinese = /[\p{Script=Han}\p{sc=Hani}]/u.test(text);
    if (!isChinese) {
      await adb.inputText(text);
      return;
    }
    await this.execYadb(text);
  }
  async keyboardPress(key) {
    const keyCodeMap = {
      Enter: 66,
      Backspace: 67,
      Tab: 61,
      ArrowUp: 19,
      ArrowDown: 20,
      ArrowLeft: 21,
      ArrowRight: 22,
      Escape: 111,
      Home: 3,
      End: 123
    };
    const adb = await this.getAdb();
    const keyCode = keyCodeMap[key];
    if (keyCode !== void 0) {
      await adb.keyevent(keyCode);
    } else {
      if (key.length === 1) {
        const asciiCode = key.toUpperCase().charCodeAt(0);
        if (asciiCode >= 65 && asciiCode <= 90) {
          await adb.keyevent(asciiCode - 36);
        }
      }
    }
  }
  async keyboardPressAction(action) {
    if (Array.isArray(action)) {
      for (const act of action) {
        await this.keyboardPress(act.key);
      }
    } else {
      await this.keyboardPress(action.key);
    }
  }
  async mouseClick(x, y) {
    const adb = await this.getAdb();
    const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
    await adb.shell(`input tap ${adjustedX} ${adjustedY}`);
  }
  async mouseMove(x, y) {
    return Promise.resolve();
  }
  async mouseDrag(from, to) {
    const adb = await this.getAdb();
    const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
    const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
    await adb.shell(`input swipe ${fromX} ${fromY} ${toX} ${toY} 300`);
  }
  async mouseWheel(deltaX, deltaY, duration = 1e3) {
    const { width, height } = await this.size();
    const n = 4;
    const startX = deltaX < 0 ? (n - 1) * (width / n) : width / n;
    const startY = deltaY < 0 ? (n - 1) * (height / n) : height / n;
    const maxNegativeDeltaX = startX;
    const maxPositiveDeltaX = (n - 1) * (width / n);
    const maxNegativeDeltaY = startY;
    const maxPositiveDeltaY = (n - 1) * (height / n);
    deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
    deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
    const endX = startX + deltaX;
    const endY = startY + deltaY;
    const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(
      startX,
      startY
    );
    const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(
      endX,
      endY
    );
    const adb = await this.getAdb();
    await adb.shell(
      `input swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${duration}`
    );
  }
  async destroy() {
    try {
      const adb = await this.getAdb();
      await adb.shell(`rm -f ${androidScreenshotPath}`);
    } catch (error) {
      console.error("Error during cleanup:", error);
    }
  }
};

// src/agent/index.ts
import { PageAgent } from "@midscene/web/agent";

// src/utils/index.ts
import { ADB as ADB2 } from "appium-adb";
async function getConnectedDevices() {
  try {
    const adb = await ADB2.createADB({
      adbExecTimeout: 6e4
    });
    const devices = await adb.getConnectedDevices();
    debugPage(`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 Android device list");
  }
}

// src/agent/index.ts
var AndroidAgent = class extends PageAgent {
  async launch(uri) {
    const device = this.page;
    await device.launch(uri);
  }
};
async function agentFromAdbDevice(deviceId, opts) {
  if (!deviceId) {
    const devices = await getConnectedDevices();
    deviceId = devices[0].udid;
    debugPage(
      "deviceId not specified, will use the first device (id = %s)",
      deviceId
    );
  }
  const page = new AndroidDevice(deviceId);
  await page.connect();
  return new AndroidAgent(page, opts);
}
export {
  AndroidAgent,
  AndroidDevice,
  agentFromAdbDevice,
  getConnectedDevices
};
