"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.build = exports.clean = void 0;
const promises_1 = require("fs/promises");
const child_process_1 = require("child_process");
const utils_1 = require("./utils");
const path_1 = __importDefault(require("path"));
const util_1 = require("util");
const defaultBuildPath = path_1.default.join(process.cwd(), ".build");
// the name of the binary that the DLL loader *expects* to find
// (probably doesn't need to be changed)
const winHost = "node.exe";
function clean(config = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        yield (0, promises_1.rm)(config.buildPath || defaultBuildPath, { recursive: true, force: true });
    });
}
exports.clean = clean;
function getWinLib() {
    return __awaiter(this, void 0, void 0, function* () {
        // Use NODE_SWIFT_TARGET_ARCH env var if set, otherwise fall back to process.arch.
        // This is needed for cross-compilation scenarios like building ARM64 binaries
        // on Windows ARM64 when the build tool (e.g., Bun) runs under x64 emulation.
        const arch = process.env.NODE_SWIFT_TARGET_ARCH || process.arch;
        let filename;
        switch (arch) {
            case "x64":
            case "x86_64":
                filename = "node-win32-x64.lib";
                break;
            case "arm64":
            case "aarch64":
                filename = "node-win32-arm64.lib";
                break;
            default:
                throw new Error(`The arch ${arch} is currently unsupported by node-swift on Windows.`);
        }
        return path_1.default.join(__dirname, "..", "vendored", "node", "lib", filename);
    });
}
function getFlags(config, name) {
    const flags = config[name];
    if (typeof flags === "undefined") {
        return [];
    }
    else if (typeof flags === "string") {
        return flags.split(" ");
    }
    else if (Array.isArray(flags) && flags.every(t => (typeof t) === "string")) {
        return flags;
    }
    else {
        throw new Error(`Invalid value for ${name}`);
    }
}
function build(mode, config = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        let dumpFlags = getFlags(config, "dumpFlags");
        let cFlags = getFlags(config, "cFlags");
        let swiftFlags = getFlags(config, "swiftFlags");
        let cxxFlags = getFlags(config, "cxxFlags");
        let linkerFlags = getFlags(config, "linkerFlags");
        let isDynamic;
        if (typeof config.static === "boolean") {
            isDynamic = !config.static;
        }
        else if (typeof config.static === "undefined") {
            isDynamic = true;
        }
        else {
            throw new Error("Invalid value for static option");
        }
        let packagePath;
        if (typeof config.packagePath === "string") {
            packagePath = yield (0, promises_1.realpath)(config.packagePath);
        }
        else if (typeof config.packagePath === "undefined") {
            packagePath = process.cwd();
        }
        else {
            throw new Error("Invalid value for packagePath option.");
        }
        const buildDir = config.buildPath || defaultBuildPath;
        let napi = config.napi;
        if (typeof napi === "number") {
            cFlags.push(`-DNAPI_VERSION=${napi}`);
            swiftFlags.push("-DNAPI_VERSIONED");
            for (let i = 1; i <= napi; i++) {
                swiftFlags.push(`-DNAPI_GE_${i}`);
            }
        }
        else if (typeof napi === "string" && napi === "experimental") {
            cFlags.push("-DNAPI_EXPERIMENTAL");
            swiftFlags.push("-DNAPI_EXPERIMENTAL");
        }
        else if (typeof napi !== "undefined") {
            throw new Error("Invalid value for napi option.");
        }
        const nonSPMFlags = [
            ...cFlags.flatMap(f => ["-Xcc", f]),
            ...swiftFlags.flatMap(f => ["-Xswiftc", f]),
            ...cxxFlags.flatMap(f => ["-Xcxx", f]),
            ...linkerFlags.flatMap(f => ["-Xlinker", f]),
        ];
        process.stdout.write("[1/2] Initializing...");
        const dump = (0, child_process_1.spawnSync)("swift", [
            "package",
            "dump-package",
            "--package-path", packagePath,
            ...dumpFlags,
            ...nonSPMFlags,
        ], { stdio: ["inherit", "pipe", "inherit"] });
        if (dump.status !== 0) {
            throw new Error(`swift package dump-package exited with status ${dump.status}`);
        }
        const parsedPackage = JSON.parse(dump.stdout.toString());
        const products = parsedPackage.products;
        let dylib;
        if (typeof config.product === "undefined") {
            const dylibs = products.filter(p => (0, util_1.isDeepStrictEqual)(p.type, { library: ["dynamic"] }));
            if (dylibs.length === 0) {
                throw new Error("No .dynamic products found in Swift Package");
            }
            else if (dylibs.length === 1) {
                dylib = dylibs[0];
            }
            else {
                throw new Error("Found more than 1 dynamic library in the Swift Package. Consider " +
                    "specifying which product should be built via the swift.product " +
                    "field in package.json.");
            }
        }
        else if (typeof config.product === "string") {
            dylib = products.find(p => p.name === config.product);
            if (!dylib) {
                throw new Error(`Could not find a product named '${config.product}'`);
            }
            if (!(0, util_1.isDeepStrictEqual)(dylib.type, { library: ["dynamic"] })) {
                throw new Error(`Product '${config.product}' must be a .dynamic library`);
            }
        }
        else {
            throw new Error("The config product field should be of type string, if present");
        }
        const product = dylib.name;
        const targetNames = new Set(dylib.targets);
        const hasSupportLib = !!parsedPackage.targets.find((t) => {
            var _a;
            return (targetNames.has(t.name) &&
                ((_a = t.dependencies) === null || _a === void 0 ? void 0 : _a.find((d) => {
                    var _a;
                    return (0, util_1.isDeepStrictEqual)((_a = d === null || d === void 0 ? void 0 : d.product) === null || _a === void 0 ? void 0 : _a.slice(0, 2), ["NodeModuleSupport", "node-swift"]);
                })));
        });
        if (!hasSupportLib)
            throw new Error(`Product '${product}' must have '.product(name: "NodeModuleSupport", package: "node-swift")' as a dependency`);
        let libName;
        let ldflags;
        switch (process.platform) {
            case "darwin":
                libName = `lib${product}.dylib`;
                ldflags = [
                    "-Xlinker", "-undefined",
                    "-Xlinker", "dynamic_lookup",
                ];
                break;
            case "linux":
                libName = `lib${product}.so`;
                ldflags = [
                    "-Xlinker", "-undefined",
                ];
                break;
            case "win32":
                libName = `${product}.dll`;
                ldflags = [
                    "-Xlinker", yield getWinLib(),
                    "-Xlinker", "delayimp.lib",
                    "-Xlinker", `/DELAYLOAD:${winHost}`,
                ];
                break;
            default:
                throw new Error(`The platform ${process.platform} is currently unsupported by node-swift.`);
        }
        process.stdout.write("\r[2/2] Initializing...");
        console.log();
        const binaryDir = path_1.default.join(buildDir, mode);
        const binaryPath = path_1.default.join(binaryDir, `${product}.node`);
        if (config.builder === "xcode" || (typeof config.builder === "object" && config.builder.type === "xcode")) {
            const xcode = typeof config.builder === "object" ? config.builder : { type: "xcode" };
            const settings = getFlags(xcode, "settings");
            const destinations = xcode.destinations || ["generic/platform=macOS"];
            const derivedDataPath = path_1.default.join(buildDir, "DerivedData");
            if (isDynamic) {
                // add the framework's parent directory to the rpath
                ldflags.push("-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../..");
            }
            const originalBinary = path_1.default.join(binaryDir, `${product}.framework`, "Versions", "A", product);
            yield (0, promises_1.rm)(originalBinary, { force: true });
            const result = (0, child_process_1.spawnSync)("xcodebuild", [
                "install",
                "-configuration", mode === "debug" ? "Debug" : "Release",
                "-derivedDataPath", derivedDataPath,
                "-workspace", path_1.default.join(packagePath, ".swiftpm", "xcode", "package.xcworkspace"),
                "-scheme", product,
                ...destinations.flatMap(d => ["-destination", d]),
                "INSTALL_PATH=/",
                `DSTROOT=${binaryDir}`,
                // TODO: escape args
                `OTHER_LDFLAGS=$(inherited) ${[...linkerFlags, ...ldflags].join(" ")}`,
                `OTHER_CFLAGS=$(inherited) ${cFlags.join(" ")}`,
                `OTHER_SWIFT_FLAGS=$(inherited) ${swiftFlags.join(" ")}`,
                `OTHER_CPLUSPLUSFLAGS=$(inherited) ${cxxFlags.join(" ")}`,
                ...settings,
            ], {
                stdio: "inherit",
                env: Object.assign(Object.assign({}, process.env), { "NODE_SWIFT_BUILD_DYNAMIC": isDynamic ? "1" : "0" }),
            });
            if (result.status !== 0) {
                throw new Error(`xcodebuild exited with status ${result.status}`);
            }
            // the exec realpath must end with .node for it to be considered a native module.
            yield Promise.all([
                (0, promises_1.rename)(originalBinary, path_1.default.join(binaryDir, `${product}.framework`, "Versions", "A", `${product}.node`)),
                (0, promises_1.rm)(path_1.default.join(binaryDir, `${product}.framework`, `${product}`), { force: true }),
                (0, utils_1.forceSymlink)(path_1.default.join("Versions", "Current", `${product}.node`), path_1.default.join(binaryDir, `${product}.framework`, `${product}.node`)),
                (0, utils_1.forceSymlink)(path_1.default.join(`${product}.framework`, `${product}.node`), binaryPath),
                (0, child_process_1.spawn)("/usr/libexec/PlistBuddy", [
                    "-c",
                    `Set :CFBundleExecutable ${product}.node`,
                    path_1.default.join(binaryDir, `${product}.framework`, "Resources", "Info.plist"),
                ], {
                    stdio: "inherit",
                }),
            ]);
        }
        else {
            const swiftPM = typeof config.builder === "object" ? config.builder : {};
            const swiftPMFlags = getFlags(swiftPM, "settings");
            if (typeof swiftPM.triple === "string") {
                swiftPMFlags.push("--triple", swiftPM.triple);
            }
            const result = (0, child_process_1.spawnSync)("swift", [
                "build",
                "-c", mode,
                "--product", product,
                "--build-path", buildDir,
                "--package-path", packagePath,
                ...ldflags,
                ...swiftPMFlags,
                ...nonSPMFlags,
            ], {
                stdio: "inherit",
                env: Object.assign(Object.assign({}, process.env), { "NODE_SWIFT_BUILD_DYNAMIC": isDynamic ? "1" : "0" }),
            });
            if (result.status !== 0) {
                throw new Error(`swift build exited with status ${result.status}`);
            }
            yield (0, promises_1.rename)(path_1.default.join(buildDir, mode, libName), binaryPath);
            if (process.platform === "darwin") {
                (0, child_process_1.spawnSync)("codesign", ["-fs", "-", binaryPath], { stdio: "inherit" });
            }
        }
        yield (0, utils_1.forceSymlink)(path_1.default.join(mode, `${product}.node`), path_1.default.join(buildDir, `${product}.node`));
        return binaryPath;
    });
}
exports.build = build;
//# sourceMappingURL=builder.js.map