// src/utils/debug.ts
import createDebug from "debug";
var debug = createDebug("apple-kit");
var debugTask = createDebug("apple-kit:task");
var debugWarning = createDebug("apple-kit:warning");
var debugError = createDebug("apple-kit:error");
function logInfo(message) {
  debug(message);
}
function logTask(message) {
  debugTask(message);
}

// src/logic/actions/device.ts
import { exec as execCallback } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { promisify } from "node:util";

// src/logic/dataParser.ts
function parsePlistOutput(output) {
  const result = {};
  const lines = output.split("\n");
  for (const line of lines) {
    const colonIndex = line.indexOf(":");
    if (colonIndex > 0) {
      const key = line.substring(0, colonIndex).trim();
      const value = line.substring(colonIndex + 1).trim();
      result[key] = value;
    }
  }
  return result;
}
function parseAppList(output) {
  const apps = [];
  const lines = output.trim().split("\n");
  for (const line of lines) {
    const match = line.match(/^([^,]+),\s*([^,]+),\s*"?([^"]+)"?/);
    if (match) {
      apps.push({
        bundleId: match[1].trim(),
        version: match[2].trim(),
        displayName: match[3].trim(),
        bundleVersion: ""
      });
    }
  }
  return apps;
}

// src/logic/actions/device.ts
var execAsync = promisify(execCallback);
async function getDeviceInfo(udid) {
  logTask(`Getting device info for ${udid}`);
  const result = await runIDeviceTool("ideviceinfo", ["-u", udid]);
  if (!result) {
    throw new Error("Failed to get device info");
  }
  const props = parsePlistOutput(result.stdout);
  return {
    deviceName: props.DeviceName || "",
    productType: props.ProductType || "",
    productVersion: props.ProductVersion || "",
    buildVersion: props.BuildVersion || "",
    serialNumber: props.SerialNumber || "",
    udid: props.UniqueDeviceID || udid,
    wifiAddress: props.WiFiAddress || "",
    bluetoothAddress: props.BluetoothAddress || "",
    phoneNumber: props.PhoneNumber || "",
    cpuArchitecture: props.CPUArchitecture || "",
    hardwareModel: props.HardwareModel || "",
    modelNumber: props.ModelNumber || "",
    regionInfo: props.RegionInfo || "",
    timeZone: props.TimeZone || "",
    uniqueChipID: props.UniqueChipID || "",
    isPaired: true
    // If we can get info, device is paired
  };
}
async function runIDeviceTool(toolName, args = [], options = {}) {
  const command = `"${toolName}${process.platform === "win32" ? ".exe" : ""}" ${args.map((a) => `"${a}"`).join(" ")}`;
  return execIDevice(command, options);
}
async function execIDevice(command, options = {}) {
  const binPath = getIDeviceBinPath();
  options.cwd = binPath;
  const result = await execAsync(command, {
    ...options,
    env: process.env,
    windowsHide: true,
    encoding: "utf8"
  });
  return {
    stdout: result.stdout.toString(),
    stderr: result.stderr.toString()
  };
}
function getResourcesBinPath() {
  const binPath = process.env.IDeviceBinPath;
  if (!binPath) {
    throw new Error("IDeviceBinPath is not set");
  }
  return binPath;
}
function getIDeviceBinPath() {
  return getResourcesBinPath();
}

// src/logic/actions/activation.ts
async function getActivationState(udid) {
  logTask(`Getting activation state for device ${udid}`);
  try {
    const result = await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "ActivationState"]);
    if (!result) {
      return {
        isActivated: false,
        activationState: "Unknown"
      };
    }
    const { stdout } = result;
    const state = stdout.trim();
    return {
      isActivated: state === "Activated",
      activationState: state
    };
  } catch {
    return {
      isActivated: false,
      activationState: "Unknown"
    };
  }
}
async function activate(udid) {
  logTask(`Activating device ${udid}`);
  try {
    const result = await runIDeviceTool("ideviceactivation", ["-u", udid, "activate"]);
    if (!result) {
      return false;
    }
    return result.stdout.toLowerCase().includes("success") || result.stdout.toLowerCase().includes("activated");
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    throw new Error(`Activation failed: ${errorMsg}`);
  }
}

// src/logic/actions/pair.ts
async function isPaired(udid) {
  logTask(`Checking pairing status for ${udid}`);
  try {
    const result = await runIDeviceTool("idevicepair", [
      "-u",
      udid,
      "validate"
    ]);
    if (!result) {
      return false;
    }
    return result.stdout.toLowerCase().includes("success");
  } catch {
    return false;
  }
}
async function trustDevice(udid, timeout = 6e4, onWaitingForTrust) {
  logTask(`Trusting device ${udid}`);
  if (await isPaired(udid)) {
    logInfo(`Device ${udid} is already trusted`);
    return true;
  }
  logInfo(`Initiating pairing for device ${udid}`);
  const pairResult = await pair(udid);
  if (pairResult) {
    logInfo(`Device ${udid} paired successfully`);
    return true;
  }
  logInfo("Please accept the trust dialog on the device...");
  onWaitingForTrust?.();
  try {
    await waitForPairing(udid, timeout, 1e3);
    logInfo(`Device ${udid} is now trusted`);
    return true;
  } catch {
    logInfo(`Timeout waiting for trust acceptance on device ${udid}`);
    return false;
  }
}
async function waitForPairing(udid, timeout = 12e4, pollInterval = 1e3) {
  logTask(`Waiting for pairing on device ${udid}`);
  const startTime = Date.now();
  while (Date.now() - startTime < timeout) {
    if (await isPaired(udid)) {
      logInfo(`Device ${udid} is now paired`);
      return true;
    }
    await new Promise((resolve) => setTimeout(resolve, pollInterval));
  }
  throw new Error(`Timeout waiting for device pairing after ${timeout}ms`);
}
async function pair(udid) {
  logTask(`Initiating pairing for device ${udid}`);
  try {
    const result = await runIDeviceTool("idevicepair", ["-u", udid, "pair"]);
    if (!result) {
      return false;
    }
    return result.stdout.toLowerCase().includes("success");
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    if (errorMsg.includes("Please accept the trust dialog")) {
      return false;
    }
    throw error;
  }
}
async function unpair(udid) {
  logTask(`Un-pairing device ${udid}`);
  try {
    const result = await runIDeviceTool("idevicepair", [
      "-u",
      udid,
      "unpair"
    ]);
    if (!result) {
      return false;
    }
    return result.stdout.toLowerCase().includes("success");
  } catch {
    return false;
  }
}

// src/logic/actions/install.ts
async function installApp(ipaPath, udid) {
  logTask(`Installing app ${ipaPath} on device ${udid}`);
  if (!await isPaired(udid)) {
    await waitForPairing(udid, 1e4);
  }
  await runIDeviceTool("ideviceinstaller", ["-u", udid, "-i", ipaPath]);
}
async function uninstallApp(bundleId, udid) {
  logTask(`Uninstalling app ${bundleId} from device ${udid}`);
  if (!await isPaired(udid)) {
    await waitForPairing(udid, 1e4);
  }
  await runIDeviceTool("ideviceinstaller", ["-u", udid, "-U", bundleId]);
}
async function listApps(udid) {
  logTask(`Listing apps on device ${udid}`);
  if (!await isPaired(udid)) {
    await waitForPairing(udid, 1e4);
  }
  try {
    const result = await runIDeviceTool("ideviceinstaller", ["-u", udid, "-l"]);
    if (!result) {
      return [];
    }
    const { stdout } = result;
    return parseAppList(stdout);
  } catch {
    return [];
  }
}
async function isAppInstalled(bundleId, udid) {
  logTask(`Checking if app ${bundleId} is installed on device ${udid}`);
  const apps = await listApps(udid);
  return apps.some((app) => app.bundleId === bundleId);
}
async function wakeDevice(udid) {
  try {
    logInfo("Attempting to wake device screen...");
    await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "DeviceName"]);
    try {
      await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "ActivationState"]);
    } catch {
    }
    try {
      await runIDeviceTool("idevicepair", ["-u", udid, "validate"]);
    } catch {
    }
    await new Promise((resolve) => setTimeout(resolve, 1e3));
    logInfo("Device wake attempt completed");
  } catch (error) {
    logInfo(
      `Device wake attempt failed (non-critical): ${error instanceof Error ? error.message : String(error)}`
    );
  }
}
async function launchApp(bundleId, args, udid) {
  logTask(`Launching app ${bundleId} on device ${udid}`);
  if (!await isPaired(udid)) {
    await waitForPairing(udid, 1e4);
  }
  await wakeDevice(udid);
  try {
    logInfo(`Attempting to launch ${bundleId} using idevicedebug...`);
    const result = await runIDeviceTool("idevicedebug", ["-u", udid, "run", bundleId, ...args]);
    const output = (result?.stdout ?? "") + (result?.stderr ?? "");
    if (output.trim()) {
      logInfo(`idevicedebug output: ${output.substring(0, 200)}`);
    }
    if (output.toLowerCase().includes("could not start") && output.toLowerCase().includes("debugserver")) {
      logInfo("idevicedebug requires debugserver, falling back to pymobiledevice3...");
      throw new Error("debugserver_not_available");
    }
    logInfo(`App ${bundleId} launched successfully using idevicedebug`);
    await new Promise((resolve) => setTimeout(resolve, 1e3));
    return;
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    if (errorMsg.includes("debugserver_not_available") || errorMsg.toLowerCase().includes("could not start") || errorMsg.toLowerCase().includes("debugserver")) {
      logInfo("idevicedebug failed, trying pymobiledevice3 for iOS 17+...");
      await launchAppWithPymobiledevice3(bundleId, args, udid);
      return;
    }
    throw error;
  }
}
async function launchAppWithPymobiledevice3(bundleId, args, udid) {
  logTask(`Launching app ${bundleId} using pymobiledevice3`);
  const { exec } = await import("node:child_process");
  const { promisify: promisify2 } = await import("node:util");
  const execAsync2 = promisify2(exec);
  try {
    let cmdArgs = [
      "-m",
      "pymobiledevice3",
      "developer",
      "dvt",
      "launch",
      "--udid",
      udid,
      "--kill-existing",
      // Kill existing instance to bring app to foreground
      bundleId,
      ...args
    ];
    let command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
    logInfo(`Executing: ${command}`);
    const result = await execAsync2(command, {
      windowsHide: true,
      encoding: "utf8",
      timeout: 3e4
      // 30 second timeout
    });
    const output = result.stdout.toString() + result.stderr.toString();
    if (output.trim()) {
      logInfo(`pymobiledevice3 output: ${output.substring(0, 200)}`);
    }
    if (output.toLowerCase().includes("developer mode") && output.toLowerCase().includes("not enabled")) {
      throw new Error(
        "Developer Mode is not enabled on the device.\nPlease enable it: Settings \u2192 Privacy & Security \u2192 Developer Mode \u2192 Enable"
      );
    }
    if (output.toLowerCase().includes("tunneld") && (output.toLowerCase().includes("unable to connect") || output.toLowerCase().includes("invalidserviceerror"))) {
      logInfo("Tunnel required, retrying with tunnel option...");
      cmdArgs = [
        "-m",
        "pymobiledevice3",
        "developer",
        "dvt",
        "launch",
        "--udid",
        udid,
        "--tunnel",
        udid,
        // Use UDID for tunnel
        "--kill-existing",
        bundleId,
        ...args
      ];
      command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
      logInfo(`Retrying with tunnel: ${command}`);
      try {
        const retryResult = await execAsync2(command, {
          windowsHide: true,
          encoding: "utf8",
          timeout: 3e4
        });
        const retryOutput = retryResult.stdout.toString() + retryResult.stderr.toString();
        if (retryOutput.trim()) {
          logInfo(`pymobiledevice3 retry output: ${retryOutput.substring(0, 200)}`);
        }
        if (retryOutput.toLowerCase().includes("tunneld") && retryOutput.toLowerCase().includes("unable to connect")) {
          throw new Error(
            "Tunnel connection failed. For iOS 17+, you may need to start tunneld:\n  python -m pymobiledevice3 remote tunneld\nOr ensure Developer Mode is enabled and device is unlocked."
          );
        }
        logInfo(`App ${bundleId} launched successfully using pymobiledevice3 with tunnel`);
        await new Promise((resolve) => setTimeout(resolve, 500));
        return;
      } catch {
        throw new Error(
          "Tunnel connection failed. For iOS 17+, you may need to start tunneld:\n  python -m pymobiledevice3 remote tunneld\nOr ensure Developer Mode is enabled and device is unlocked."
        );
      }
    }
    if (output.toLowerCase().includes("not found") || output.toLowerCase().includes("command not found") || output.toLowerCase().includes("cannot find")) {
      throw new Error(
        "pymobiledevice3 is not installed.\nInstall it with: python -m pip install --user pymobiledevice3"
      );
    }
    if (output.toLowerCase().includes("error") && !output.toLowerCase().includes("warning")) {
      logInfo(`Warning: pymobiledevice3 reported errors: ${output.substring(0, 300)}`);
    }
    logInfo(`App ${bundleId} launched successfully using pymobiledevice3`);
    await new Promise((resolve) => setTimeout(resolve, 1e3));
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    if (errorMsg.includes("not found") || errorMsg.includes("command not found") || errorMsg.includes("cannot find") || errorMsg.includes("No module named")) {
      throw new Error(
        "pymobiledevice3 is not installed or Python is not available.\nInstall it with: python -m pip install --user pymobiledevice3\nFor iOS 17+, Developer Mode must also be enabled on the device."
      );
    }
    throw error;
  }
}

// src/logic/actions/proxy.ts
async function startPortForward(localPort, devicePort, udid) {
  logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${udid}`);
  const result = await runIDeviceTool("iproxy", [
    localPort.toString(),
    devicePort.toString(),
    "-u",
    udid
  ]);
  return {
    localPort,
    devicePort,
    ...result
  };
}
async function startPortForwardAsync(localPort, devicePort, udid, _timeout = 5e3) {
  const result = await startPortForward(localPort, devicePort, udid);
  await new Promise((resolve) => setTimeout(resolve, 500));
  if (result.stdout.includes("error") || result.stderr.includes("error")) {
    throw new Error("Port forwarding failed to start");
  }
  return result;
}
async function closePortForward(udid) {
  logTask(`Closing port forward for device ${udid}`);
  await runIDeviceTool("iproxy", ["-u", udid, "-c"]);
}

// src/logic/appleDeviceKit.ts
var AppleDeviceKit = class {
  constructor(udid, port) {
    this.port = port;
    this.deviceId = udid;
    logInfo(`AppleDeviceKit initialized for device: ${this.deviceId}`);
  }
  deviceId;
  /**
   * Get detailed device information
   */
  async getDeviceInfo() {
    return getDeviceInfo(this.deviceId);
  }
  /**
   * Check if device is paired/trusted with this computer
   */
  async isPaired() {
    return isPaired(this.deviceId);
  }
  /**
   * Wait for device to be paired
   * Polls the pairing status until successful or timeout
   *
   * @param timeout Timeout in milliseconds (default: 120000)
   * @param pollInterval Poll interval in milliseconds (default: 1000)
   */
  async waitForPairing(timeout = 12e4, pollInterval = 1e3) {
    return waitForPairing(this.deviceId, timeout, pollInterval);
  }
  /**
   * Attempt to pair/trust the device
   * User must accept the trust dialog on the device
   */
  async pair() {
    return pair(this.deviceId);
  }
  /**
   * Trust/pair the device - initiates pairing and waits for user to accept
   *
   * This is the recommended method for establishing trust with a device.
   * It will:
   * 1. Check if already paired
   * 2. If not, initiate pairing (shows "Trust This Computer?" on device)
   * 3. Wait for user to accept the trust dialog
   *
   * @param timeout Timeout in milliseconds to wait for user acceptance (default: 60000)
   * @param onWaitingForTrust Callback when waiting for user to accept trust dialog
   * @returns true if device is now trusted
   */
  async trustDevice(timeout = 6e4, onWaitingForTrust) {
    return trustDevice(this.deviceId, timeout, onWaitingForTrust);
  }
  /**
   * Unpair/untrust the device
   */
  async unpair() {
    return unpair(this.deviceId);
  }
  /**
   * Install an IPA file on the device (install agent)
   *
   * @param ipaPath Path to the IPA file
   */
  async installApp(ipaPath) {
    installApp(ipaPath, this.deviceId);
  }
  /**
   * Uninstall an app by bundle ID (uninstall agent)
   *
   * @param bundleId Application bundle identifier
   */
  async uninstallApp(bundleId) {
    uninstallApp(bundleId, this.deviceId);
  }
  /**
   * Check if an app is installed on the device
   *
   * @param bundleId Application bundle identifier
   */
  async isAppInstalled(bundleId) {
    return isAppInstalled(bundleId, this.deviceId);
  }
  /**
   * List all installed user applications
   */
  async listApps() {
    return listApps(this.deviceId);
  }
  /**
   * Launch an application on the device
   *
   * @param bundleId Application bundle identifier
   * @param args Application arguments
   */
  async launchApp(bundleId, args = []) {
    return launchApp(bundleId, args, this.deviceId);
  }
  /**
   * Start port forwarding and wait for it to be ready.
   * we need port forwarding to be able to connect to the device from the computer
   * and communicate with it using the local port.
   *
   * @param localPort Local port to listen on
   * @param devicePort Device port to forward to
   * @param _timeout Timeout in milliseconds (reserved for future use)
   */
  async startPortForwardAsync(localPort, devicePort, _timeout = 5e3) {
    return startPortForwardAsync(localPort, devicePort, this.deviceId);
  }
  /**
   * Get the activation state of the device
   */
  async getActivationState() {
    return getActivationState(this.deviceId);
  }
  /**
   * Activate the device (register in apple servers), an activated device belongs to someone (a user/company).
   * A device that is on hello screen cannot pass the wifi step unless it is activated.
   * the activate save us the need of the device to be connected to the internet to do the activation
   * and register in apple servers.
   *
   * Note: This requires a valid activation record or Apple server access
   *
   * precondition: the device must be paired and trusted
   */
  async activate() {
    return activate(this.deviceId);
  }
  /**
   * Get the device UDID
   */
  getDeviceId() {
    return this.deviceId;
  }
  /**
   * Get the logical port number
   */
  getPort() {
    return this.port;
  }
  async closePortForward() {
    return closePortForward(this.deviceId);
  }
};
export {
  AppleDeviceKit
};
//# sourceMappingURL=index.js.map
