import process from "process";
import path from "path";
import console from "console";
import { createRequire } from "module";
import { builtinLlamaCppGitHubRepo, builtinLlamaCppRelease, defaultLlamaCppCudaSupport, defaultLlamaCppDebugLogs, defaultLlamaCppGitHubRepo, defaultLlamaCppMetalSupport, defaultLlamaCppRelease, defaultSkipDownload, llamaLocalBuildBinsDirectory } from "../config.js";
import { getConsoleLogPrefix } from "../utils/getConsoleLogPrefix.js";
import { waitForLockfileRelease } from "../utils/waitForLockfileRelease.js";
import { isGithubReleaseNeedsResolving, resolveGithubRelease } from "../utils/resolveGithubRelease.js";
import { compileLlamaCpp, getLocalBuildBinaryBuildMetadata, getLocalBuildBinaryPath, getPrebuiltBinaryBuildMetadata, getPrebuiltBinaryPath } from "./utils/compileLLamaCpp.js";
import { getLastBuildInfo } from "./utils/lastBuildInfo.js";
import { getClonedLlamaCppRepoReleaseInfo, isLlamaCppRepoCloned } from "./utils/cloneLlamaCppRepo.js";
import { getPlatform } from "./utils/getPlatform.js";
import { getBuildFolderNameForBuildOptions } from "./utils/getBuildFolderNameForBuildOptions.js";
import { resolveCustomCmakeOptions } from "./utils/resolveCustomCmakeOptions.js";
import { getCanUsePrebuiltBinaries } from "./utils/getCanUsePrebuiltBinaries.js";
import { NoBinaryFoundError } from "./utils/NoBinaryFoundError.js";
import { Llama } from "./Llama.js";
const require = createRequire(import.meta.url);
export const getLlamaFunctionName = "getLlama";
export async function getLlama(options, lastBuildOptions) {
    if (options === "lastBuild") {
        const lastBuildInfo = await getLastBuildInfo();
        const getLlamaOptions = {
            logLevel: lastBuildOptions?.logLevel ?? defaultLlamaCppDebugLogs,
            logger: lastBuildOptions?.logger ?? Llama.defaultConsoleLogger
        };
        if (lastBuildInfo == null)
            return getLlamaForOptions(getLlamaOptions);
        const localBuildFolder = path.join(llamaLocalBuildBinsDirectory, lastBuildInfo.folderName);
        const localBuildBinPath = await getLocalBuildBinaryPath(lastBuildInfo.folderName);
        await waitForLockfileRelease({ resourcePath: localBuildFolder });
        if (localBuildBinPath != null) {
            try {
                const binding = loadBindingModule(localBuildBinPath);
                const buildMetadata = await getLocalBuildBinaryBuildMetadata(lastBuildInfo.folderName);
                return await Llama._create({
                    bindings: binding,
                    buildType: "localBuild",
                    buildMetadata,
                    logger: lastBuildOptions?.logger ?? Llama.defaultConsoleLogger,
                    logLevel: lastBuildOptions?.logLevel ?? defaultLlamaCppDebugLogs
                });
            }
            catch (err) {
                console.error(getConsoleLogPrefix() + "Failed to load last build. Error:", err);
                console.info(getConsoleLogPrefix() + "Falling back to default binaries");
            }
        }
        return getLlamaForOptions(getLlamaOptions);
    }
    return getLlamaForOptions(options ?? {});
}
async function getLlamaForOptions({ metal = defaultLlamaCppMetalSupport, cuda = defaultLlamaCppCudaSupport, logLevel = defaultLlamaCppDebugLogs, logger = Llama.defaultConsoleLogger, build = "auto", cmakeOptions = {}, existingPrebuiltBinaryMustMatchBuildOptions = false, usePrebuiltBinaries = true, progressLogs = true, skipDownload = defaultSkipDownload }) {
    const platform = getPlatform();
    const arch = process.arch;
    if (platform !== "mac")
        metal = false;
    else if (platform === "mac" && arch === "arm64")
        cuda = false;
    if (logLevel == null)
        logLevel = defaultLlamaCppDebugLogs;
    if (logger == null)
        logger = Llama.defaultConsoleLogger;
    if (build == null)
        build = "auto";
    if (cmakeOptions == null)
        cmakeOptions = {};
    if (existingPrebuiltBinaryMustMatchBuildOptions == null)
        existingPrebuiltBinaryMustMatchBuildOptions = false;
    if (usePrebuiltBinaries == null)
        usePrebuiltBinaries = true;
    if (progressLogs == null)
        progressLogs = true;
    if (skipDownload == null)
        skipDownload = defaultSkipDownload;
    const clonedLlamaCppRepoReleaseInfo = await getClonedLlamaCppRepoReleaseInfo();
    const buildOptions = {
        customCmakeOptions: resolveCustomCmakeOptions(cmakeOptions),
        progressLogs,
        platform,
        arch,
        computeLayers: {
            metal,
            cuda
        },
        llamaCpp: {
            repo: clonedLlamaCppRepoReleaseInfo?.llamaCppGithubRepo ?? builtinLlamaCppGitHubRepo,
            release: clonedLlamaCppRepoReleaseInfo?.tag ?? builtinLlamaCppRelease
        }
    };
    const canUsePrebuiltBinaries = (build === "forceRebuild" || !usePrebuiltBinaries)
        ? false
        : await getCanUsePrebuiltBinaries();
    let buildFolderName = await getBuildFolderNameForBuildOptions(buildOptions);
    if (build === "auto" || build === "never") {
        const localBuildFolder = path.join(llamaLocalBuildBinsDirectory, buildFolderName.withCustomCmakeOptions);
        const localBuildBinPath = await getLocalBuildBinaryPath(buildFolderName.withCustomCmakeOptions);
        await waitForLockfileRelease({ resourcePath: localBuildFolder });
        if (localBuildBinPath != null) {
            try {
                const binding = loadBindingModule(localBuildBinPath);
                const buildMetadata = await getLocalBuildBinaryBuildMetadata(buildFolderName.withCustomCmakeOptions);
                return await Llama._create({
                    bindings: binding,
                    buildType: "localBuild",
                    buildMetadata,
                    logLevel,
                    logger
                });
            }
            catch (err) {
                const binaryDescription = describeBinary(buildOptions);
                console.error(getConsoleLogPrefix() + `Failed to load a local build ${binaryDescription}. Error:`, err);
                console.info(getConsoleLogPrefix() + "Falling back to prebuilt binaries");
            }
        }
        if (canUsePrebuiltBinaries) {
            const prebuiltBinPath = await getPrebuiltBinaryPath(existingPrebuiltBinaryMustMatchBuildOptions
                ? buildFolderName.withCustomCmakeOptions
                : buildFolderName.withoutCustomCmakeOptions);
            if (prebuiltBinPath != null) {
                try {
                    const binding = loadBindingModule(prebuiltBinPath);
                    const buildMetadata = await getPrebuiltBinaryBuildMetadata(buildFolderName.withCustomCmakeOptions);
                    return await Llama._create({
                        bindings: binding,
                        buildType: "prebuilt",
                        buildMetadata,
                        logLevel,
                        logger
                    });
                }
                catch (err) {
                    const binaryDescription = describeBinary({
                        ...buildOptions,
                        customCmakeOptions: existingPrebuiltBinaryMustMatchBuildOptions
                            ? buildOptions.customCmakeOptions
                            : new Map()
                    });
                    console.error(getConsoleLogPrefix() + `Failed to load a prebuilt ${binaryDescription}` + (build !== "never"
                        ? ", falling back to building from source"
                        : "") + ". Error:", err);
                }
            }
            else if (progressLogs)
                console.warn(getConsoleLogPrefix() + "A prebuilt binary was not found" + (build !== "never"
                    ? ", falling back to building from source"
                    : ""));
        }
    }
    if (build === "never")
        throw new NoBinaryFoundError();
    const llamaCppRepoCloned = await isLlamaCppRepoCloned();
    if (!llamaCppRepoCloned) {
        if (skipDownload)
            throw new NoBinaryFoundError("No prebuilt binaries found, no llama.cpp source found and `skipDownload` or NODE_LLAMA_CPP_SKIP_DOWNLOAD env var is set to true, so llama.cpp cannot be built from source");
        buildOptions.llamaCpp.repo = defaultLlamaCppGitHubRepo;
        buildOptions.llamaCpp.release = defaultLlamaCppRelease;
        if (isGithubReleaseNeedsResolving(buildOptions.llamaCpp.release)) {
            const [owner, name] = defaultLlamaCppGitHubRepo.split("/");
            buildOptions.llamaCpp.release = await resolveGithubRelease(owner, name, buildOptions.llamaCpp.release);
            buildFolderName = await getBuildFolderNameForBuildOptions(buildOptions);
        }
    }
    await compileLlamaCpp(buildOptions, {
        ensureLlamaCppRepoIsCloned: !skipDownload,
        downloadCmakeIfNeeded: true,
        updateLastBuildInfo: false
    });
    const localBuildFolder = path.join(llamaLocalBuildBinsDirectory, buildFolderName.withCustomCmakeOptions);
    await waitForLockfileRelease({ resourcePath: localBuildFolder });
    const localBuildBinPath = await getLocalBuildBinaryPath(buildFolderName.withCustomCmakeOptions);
    if (localBuildBinPath == null) {
        throw new Error("Failed to build llama.cpp");
    }
    const binding = loadBindingModule(localBuildBinPath);
    const buildMetadata = await getLocalBuildBinaryBuildMetadata(buildFolderName.withCustomCmakeOptions);
    return await Llama._create({
        bindings: binding,
        buildType: "localBuild",
        buildMetadata,
        logLevel,
        logger
    });
}
function describeBinary(binaryOptions) {
    let res = `binary for platform "${binaryOptions.platform}" "${binaryOptions.arch}"`;
    const additions = [];
    if (binaryOptions.computeLayers.metal)
        additions.push("with Metal support");
    if (binaryOptions.computeLayers.cuda)
        additions.push("with CUDA support");
    if (binaryOptions.customCmakeOptions.size > 0)
        additions.push("with custom build options");
    res += additions
        .map((addition, index) => {
        if (index === 0)
            return " " + addition;
        if (additions.length === 2)
            return " and " + addition;
        if (index === additions.length - 1)
            return " and " + addition;
        return ", " + addition;
    })
        .join("");
    return res;
}
function loadBindingModule(bindingModulePath) {
    // each llama instance has its own settings, such as a different logger, so we have to make sure we load a new instance every time
    try {
        delete require.cache[require.resolve(bindingModulePath)];
    }
    catch (err) { }
    try {
        const binding = require(bindingModulePath);
        return binding;
    }
    finally {
        try {
            delete require.cache[require.resolve(bindingModulePath)];
        }
        catch (err) { }
    }
}
//# sourceMappingURL=getLlama.js.map