#!/usr/bin/env node

import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const mobileRoot = resolve(__dirname, "..");
const derivedDataPath = resolve(mobileRoot, "ios", "build", "screenshots");
const appPath = resolve(
  derivedDataPath,
  "Build",
  "Products",
  "Release-iphonesimulator",
  "mobile.app",
);
const defaultOutputRoot = resolve(mobileRoot, "..", "ScreenShots");
const bundleId = "fr.negopoly.app";
const launchWaitMs = 4500;
const sceneWaitMs = 2200;

const targetDevices = [
  { matchName: "iPhone 17 Pro Max", outputFolder: "iPhone 17 Pro Max" },
  { matchName: "iPad Air 13-inch", outputFolder: "iPad Air 13-inch" },
];

const scenes = [
  { slug: "start", fileName: "Start.png" },
  { slug: "lobby", fileName: "Lobby.png" },
  { slug: "home", fileName: "Home.png" },
  { slug: "transfers", fileName: "Transfers.png" },
  { slug: "chat", fileName: "Chat.png" },
];

function parseArgs(argv) {
  const options = {
    skipBuild: false,
    outputRoot: defaultOutputRoot,
  };

  for (let index = 0; index < argv.length; index += 1) {
    const current = argv[index];
    if (current === "--skip-build") {
      options.skipBuild = true;
      continue;
    }
    if (current === "--output") {
      const nextValue = argv[index + 1];
      if (!nextValue) {
        throw new Error("Missing value after --output");
      }
      options.outputRoot = resolve(mobileRoot, nextValue);
      index += 1;
      continue;
    }
    throw new Error(`Unknown argument: ${current}`);
  }

  return options;
}

function run(command, args, options = {}) {
  const label = `${command} ${args.join(" ")}`;
  console.log(`\n> ${label}`);
  return execFileSync(command, args, {
    cwd: mobileRoot,
    encoding: "utf8",
    stdio: options.stdio ?? "pipe",
  });
}

function runQuiet(command, args) {
  try {
    return run(command, args);
  } catch {
    return "";
  }
}

function sleep(ms) {
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}

function listAvailableDevices() {
  const output = run("xcrun", ["simctl", "list", "devices", "available", "--json"]);
  const payload = JSON.parse(output);
  return Object.entries(payload.devices).flatMap(([runtime, devices]) =>
    devices.map((device) => ({
      runtime,
      name: device.name,
      udid: device.udid,
      state: device.state,
      isAvailable: device.isAvailable,
    })),
  );
}

function resolveDevice(matchName, devices) {
  const matches = devices
    .filter((device) => device.isAvailable)
    .filter((device) => device.name === matchName || device.name.startsWith(matchName))
    .sort((left, right) => {
      if (left.state === "Booted" && right.state !== "Booted") return -1;
      if (left.state !== "Booted" && right.state === "Booted") return 1;
      return right.runtime.localeCompare(left.runtime);
    });

  if (matches.length === 0) {
    throw new Error(`No available simulator found for "${matchName}"`);
  }

  return matches[0];
}

function ensureBooted(udid) {
  runQuiet("xcrun", ["simctl", "boot", udid]);
  run("xcrun", ["simctl", "bootstatus", udid, "-b"]);
}

function setSimulatorAppearance(udid) {
  runQuiet("xcrun", ["simctl", "ui", udid, "appearance", "light"]);
  runQuiet("xcrun", ["simctl", "status_bar", udid, "clear"]);
  runQuiet("xcrun", [
    "simctl",
    "status_bar",
    udid,
    "override",
    "--time",
    "9:41",
    "--dataNetwork",
    "wifi",
    "--wifiBars",
    "3",
    "--batteryState",
    "charged",
    "--batteryLevel",
    "100",
  ]);
}

function buildReleaseApp(buildDestinationUdid) {
  run(
    "xcodebuild",
    [
      "-workspace",
      "ios/mobile.xcworkspace",
      "-scheme",
      "mobile",
      "-configuration",
      "Release",
      "-destination",
      `platform=iOS Simulator,id=${buildDestinationUdid}`,
      "-derivedDataPath",
      derivedDataPath,
      "build",
    ],
    { stdio: "inherit" },
  );

  if (!existsSync(appPath)) {
    throw new Error(`Built app not found at ${appPath}`);
  }
}

function installApp(udid) {
  runQuiet("xcrun", ["simctl", "terminate", udid, bundleId]);
  runQuiet("xcrun", ["simctl", "uninstall", udid, bundleId]);
  run("xcrun", ["simctl", "install", udid, appPath]);
}

function openScene(udid, scene, waitMs) {
  run("xcrun", ["simctl", "openurl", udid, `negopoly://screenshot/${scene}`]);
  sleep(waitMs);
}

function captureScene(udid, destinationPath) {
  run("xcrun", ["simctl", "io", udid, "screenshot", destinationPath]);
}

function main() {
  const options = parseArgs(process.argv.slice(2));
  const devices = listAvailableDevices();
  const resolvedTargets = targetDevices.map((target) => ({
    ...target,
    simulator: resolveDevice(target.matchName, devices),
  }));

  if (!options.skipBuild) {
    buildReleaseApp(resolvedTargets[0].simulator.udid);
  } else if (!existsSync(appPath)) {
    throw new Error(`Cannot use --skip-build because ${appPath} does not exist`);
  }

  mkdirSync(options.outputRoot, { recursive: true });

  for (const target of resolvedTargets) {
    const { simulator } = target;
    const deviceOutputDir = join(options.outputRoot, target.outputFolder);

    console.log(`\n## ${target.outputFolder} (${simulator.name})`);
    mkdirSync(deviceOutputDir, { recursive: true });
    ensureBooted(simulator.udid);
    setSimulatorAppearance(simulator.udid);
    installApp(simulator.udid);

    for (const [index, scene] of scenes.entries()) {
      const targetPath = join(deviceOutputDir, scene.fileName);
      openScene(simulator.udid, scene.slug, index === 0 ? launchWaitMs : sceneWaitMs);
      captureScene(simulator.udid, targetPath);
      console.log(`Saved ${targetPath}`);
    }

    runQuiet("xcrun", ["simctl", "terminate", simulator.udid, bundleId]);
    runQuiet("xcrun", ["simctl", "status_bar", simulator.udid, "clear"]);
  }
}

try {
  main();
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);
  console.error(`\nScreenshot generation failed: ${message}`);
  process.exit(1);
}
