"use strict";

const { access, copyFile, mkdir } = require("node:fs/promises");
const { constants } = require("node:fs");
const path = require("node:path");
const { spawn } = require("node:child_process");

const RCEDIT_FILE_NAME = "rcedit-x64.exe";

function pathExists(targetPath) {
  return access(targetPath, constants.F_OK)
    .then(() => true)
    .catch(() => false);
}

async function firstExisting(paths) {
  for (const candidate of paths) {
    if (candidate && await pathExists(candidate)) {
      return candidate;
    }
  }

  return null;
}

async function findRceditUnder(rootDir) {
  if (!rootDir || !await pathExists(rootDir)) {
    return null;
  }

  const { readdir } = require("node:fs/promises");
  const entries = await readdir(rootDir, { withFileTypes: true });
  const candidates = entries
    .filter((entry) => entry.isDirectory())
    .map((entry) => path.join(rootDir, entry.name, RCEDIT_FILE_NAME));

  return firstExisting(candidates);
}

function toWindowsVersion(rawVersion) {
  const numericParts = String(rawVersion || "")
    .split(/[^0-9]+/)
    .filter(Boolean)
    .slice(0, 4)
    .map((part) => String(Number.parseInt(part, 10) || 0));

  while (numericParts.length < 4) {
    numericParts.push("0");
  }

  return numericParts.join(".");
}

async function ensureWorkspaceCachedRcedit(sourcePath, projectDir) {
  const cacheDir = path.join(projectDir, "..", "..", ".cache", "electron-builder", "tools");
  const cachedPath = path.join(cacheDir, RCEDIT_FILE_NAME);

  if (await pathExists(cachedPath)) {
    return cachedPath;
  }

  await mkdir(cacheDir, { recursive: true });
  await copyFile(sourcePath, cachedPath);
  return cachedPath;
}

async function resolveRceditPath(projectDir) {
  const envOverride = process.env.QJCLAW_RCEDIT_PATH?.trim();
  if (envOverride && await pathExists(envOverride)) {
    return envOverride;
  }

  const workspaceCached = path.join(projectDir, "..", "..", ".cache", "electron-builder", "tools", RCEDIT_FILE_NAME);
  if (await pathExists(workspaceCached)) {
    return workspaceCached;
  }

  const localAppData = process.env.LOCALAPPDATA?.trim();
  const localCacheRoot = localAppData
    ? path.join(localAppData, "electron-builder", "Cache", "winCodeSign")
    : null;
  const localCached = await findRceditUnder(localCacheRoot);
  if (localCached) {
    return ensureWorkspaceCachedRcedit(localCached, projectDir);
  }

  const workspaceCacheRoot = path.join(projectDir, "..", "..", ".cache", "electron-builder", "Cache", "winCodeSign");
  const workspaceCachedNested = await findRceditUnder(workspaceCacheRoot);
  if (workspaceCachedNested) {
    return workspaceCachedNested;
  }

  return null;
}

function runRcedit(executablePath, args) {
  return new Promise((resolve, reject) => {
    const child = spawn(executablePath, args, { stdio: "pipe" });
    let stderr = "";

    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });

    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) {
        resolve();
        return;
      }

      reject(new Error(`rcedit exited with code ${code}: ${stderr.trim()}`));
    });
  });
}

exports.default = async function afterPack(context) {
  if (context.electronPlatformName !== "win32") {
    return;
  }

  if (process.env.QJCLAW_SKIP_WINDOWS_BRANDING_PATCH === "1") {
    return;
  }

  const projectDir = context.packager.projectDir;
  const productFilename = context.packager.appInfo.productFilename;
  const executableName = productFilename.endsWith(".exe")
    ? productFilename
    : `${productFilename}.exe`;
  const executablePath = path.join(context.appOutDir, executableName);
  const iconPath = path.join(projectDir, "build", "icons", "brand-icon.ico");

  const [exeExists, iconExists] = await Promise.all([
    pathExists(executablePath),
    pathExists(iconPath)
  ]);

  if (!exeExists) {
    throw new Error(`Windows branding patch could not find packaged executable: ${executablePath}`);
  }
  if (!iconExists) {
    throw new Error(`Windows branding patch could not find icon: ${iconPath}`);
  }

  const rceditPath = await resolveRceditPath(projectDir);
  if (!rceditPath) {
    throw new Error(
      "Windows branding patch could not find rcedit-x64.exe. " +
      "Set QJCLAW_RCEDIT_PATH or install electron-builder winCodeSign cache once on this machine."
    );
  }

  const productName = context.packager.appInfo.productName;
  const version = toWindowsVersion(context.packager.appInfo.version);
  const companyName = productName;

  await runRcedit(rceditPath, [
    executablePath,
    "--set-version-string", "FileDescription", productName,
    "--set-version-string", "ProductName", productName,
    "--set-version-string", "CompanyName", companyName,
    "--set-version-string", "InternalName", productName,
    "--set-version-string", "OriginalFilename", executableName,
    "--set-file-version", version,
    "--set-product-version", version,
    "--set-icon", iconPath
  ]);
};
