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

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const appRoot = path.resolve(__dirname, "..");
const repoRoot = path.resolve(appRoot, "..");

const sidecarProject = path.join(
  repoRoot,
  "Journal.Sidecar",
  "Journal.Sidecar.csproj",
);
const publishOutputDir = path.join(repoRoot, "output");
const tauriBinDir = path.join(appRoot, "src-tauri", "bin");

function runtimeForCurrentPlatform() {
  const arch = process.arch;
  if (process.platform === "win32") {
    if (arch === "arm64") return "win-arm64";
    return "win-x64";
  }
  if (process.platform === "linux") {
    if (arch === "arm64") return "linux-arm64";
    return "linux-x64";
  }
  if (process.platform === "darwin") {
    if (arch === "arm64") return "osx-arm64";
    return "osx-x64";
  }
  throw new Error(
    `Unsupported platform '${process.platform}' for sidecar publish.`,
  );
}

function sidecarFileName() {
  return process.platform === "win32"
    ? "Journal.Sidecar.exe"
    : "Journal.Sidecar";
}

function publishProject(projectPath, runtime) {
  const publishArgs = [
    "publish",
    projectPath,
    "-c",
    "Release",
    "-r",
    runtime,
    "--self-contained",
    "-p:PublishSingleFile=true",
    "-p:IncludeNativeLibrariesForSelfExtract=false",
    "-p:RestoreIgnoreFailedSources=true",
    "-p:NuGetAudit=false",
    "-p:ErrorOnDuplicatePublishOutputFiles=false",
    "-o",
    publishOutputDir,
  ];

  const publish = spawnSync("dotnet", publishArgs, {
    cwd: repoRoot,
    stdio: "inherit",
  });

  if (publish.error) {
    throw publish.error;
  }
  if (publish.status !== 0) {
    process.exit(publish.status ?? 1);
  }
}

function stageOutput(fileName) {
  const publishedBinary = path.join(publishOutputDir, fileName);

  if (!existsSync(publishedBinary)) {
    throw new Error(`Published binary not found: ${publishedBinary}`);
  }

  mkdirSync(tauriBinDir, { recursive: true });

  const skipExts = new Set([".pdb", ".metal"]);
  for (const entry of readdirSync(publishOutputDir)) {
    const ext = path.extname(entry).toLowerCase();
    if (skipExts.has(ext)) continue;
    const src = path.join(publishOutputDir, entry);
    const dest = path.join(tauriBinDir, entry);
    if (entry === "runtimes") {
      // Only copy the runtimes subdirectory matching the target platform
      stageRuntimes(src, dest, runtime);
    } else if (statSync(src).isDirectory()) {
      cpSync(src, dest, { recursive: true, force: true });
    } else {
      copyFileSync(src, dest);
    }
  }
  console.log(`Staged sidecar + native libs to: ${tauriBinDir}`);
}

function stageRuntimes(runtimesDir, destDir, rid) {
  // Only copy the subdirectory that matches our runtime identifier (e.g. win-x64)
  const ridDir = path.join(runtimesDir, rid);
  if (!existsSync(ridDir)) {
    console.warn(`No runtimes found for ${rid}, skipping runtimes/`);
    return;
  }
  // Remove stale runtimes from previous builds
  if (existsSync(destDir)) {
    rmSync(destDir, { recursive: true, force: true });
  }
  const destRidDir = path.join(destDir, rid);
  cpSync(ridDir, destRidDir, { recursive: true, force: true });
  console.log(`Copied runtimes/${rid} (skipped ${readdirSync(runtimesDir).length - 1} other platform(s))`);
}

const runtime = runtimeForCurrentPlatform();
const sidecarName = sidecarFileName();

console.log(
  `Publishing sidecar for ${process.platform}/${process.arch} (${runtime})...`,
);

console.log("Publishing Journal.Sidecar...");
publishProject(sidecarProject, runtime);
stageOutput(sidecarName);
