import {
  cpSync,
  existsSync,
  mkdirSync,
  readdirSync,
  rmSync,
  statSync,
} from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const electrobunDir = path.join(rootDir, "node_modules/electrobun");

function findZstdDecompressCommand() {
  const hostArch = process.arch === "arm64" ? "arm64" : "x64";
  const zigCandidates = [
    path.join(electrobunDir, `dist-win-${hostArch}`, "zig-zstd.exe"),
    path.join(electrobunDir, "dist-win-x64", "zig-zstd.exe"),
    path.join(electrobunDir, `dist-macos-${hostArch}`, "zig-zstd"),
    path.join(electrobunDir, "dist-macos-x64", "zig-zstd"),
    path.join(electrobunDir, "dist-win-x64", "zig-zstd", "x64", "zig-zstd.exe"),
    path.join(electrobunDir, "dist-win-x64", "zig-zstd", "arm64", "zig-zstd.exe"),
    path.join(electrobunDir, "vendors", "zig-zstd", "x64", "zig-zstd.exe"),
    path.join(electrobunDir, "vendors", "zig-zstd", "arm64", "zig-zstd.exe"),
  ];

  for (const candidate of zigCandidates) {
    if (!existsSync(candidate)) {
      continue;
    }

    return {
      kind: "zig-zstd",
      command: candidate,
      args: (inputPath, outputPath) => [
        "decompress",
        "-i",
        inputPath,
        "-o",
        outputPath,
        "--no-timing",
      ],
    };
  }

  const systemZstd = spawnSync("zstd", ["--version"], {
    encoding: "utf8",
    shell: process.platform === "win32",
  });
  if (systemZstd.status === 0) {
    return {
      kind: "system-zstd",
      command: "zstd",
      args: (inputPath, outputPath) => [
        "-d",
        "-f",
        "-o",
        outputPath,
        inputPath,
      ],
    };
  }

  return null;
}

function decompressTarZst(inputPath, outputPath) {
  const zstd = findZstdDecompressCommand();
  if (!zstd) {
    throw new Error(
      "expand-windows-installer: zstd not found (install zstd or use electrobun dist binaries)",
    );
  }

  const resolvedInput = path.resolve(inputPath);
  const resolvedOutput = path.resolve(outputPath);
  const args = zstd.args(resolvedInput, resolvedOutput);

  console.log(
    `expand-windows-installer: decompressing with ${zstd.command} (${zstd.kind})`,
  );

  const result = spawnSync(zstd.command, args, {
    stdio: "inherit",
    shell: false,
    windowsHide: true,
  });

  if (result.status !== 0) {
    throw new Error(
      `expand-windows-installer: decompression failed (exit ${result.status ?? 1}) using ${zstd.command} ${args.join(" ")}`,
    );
  }
}

export function expandWindowsAppFromArchive(setupArchivePath, parentDir) {
  const resolvedArchive = path.resolve(setupArchivePath);
  if (!existsSync(resolvedArchive)) {
    throw new Error(`expand-windows-installer: archive not found: ${resolvedArchive}`);
  }

  const workDir = path.join(path.resolve(parentDir), ".expanded-app");
  const tarPath = path.join(workDir, "app.tar");

  rmSync(workDir, { recursive: true, force: true });
  mkdirSync(workDir, { recursive: true });

  const archiveForDecompress = path.join(workDir, "setup.tar.zst");
  cpSync(resolvedArchive, archiveForDecompress);
  decompressTarZst(archiveForDecompress, tarPath);

  const extractTar = spawnSync("tar", ["-xf", tarPath, "-C", workDir], {
    stdio: "inherit",
    shell: false,
    windowsHide: true,
  });

  if (extractTar.status !== 0) {
    rmSync(workDir, { recursive: true, force: true });
    throw new Error(
      `expand-windows-installer: tar extraction failed (exit ${extractTar.status ?? 1})`,
    );
  }

  const appDir = readdirSync(workDir)
    .filter((entry) => entry !== "app.tar" && entry !== "__MACOSX")
    .map((entry) => path.join(workDir, entry))
    .find((entryPath) => statSync(entryPath).isDirectory());

  if (!appDir) {
    rmSync(workDir, { recursive: true, force: true });
    throw new Error("expand-windows-installer: app folder not found in archive");
  }

  console.log(`expand-windows-installer: expanded ${appDir}`);
  return appDir;
}

export function cleanExpandedWindowsApp(parentDir) {
  const workDir = path.join(path.resolve(parentDir), ".expanded-app");
  rmSync(workDir, { recursive: true, force: true });
}
