"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Delegate = exports.tryCreateDelegate = void 0;
const util_1 = require("util");
const fs = require("fs");
const path = require("path");
const portfinder = require("portfinder");
const semver = require("semver");
const spawn = require("cross-spawn");
const node_fetch_1 = require("node-fetch");
const error_1 = require("../../../../error");
const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
const logger_1 = require("../../../../logger");
const utils_1 = require("../../../../utils");
const discovery = require("../discovery");
const validate = require("./validate");
const versioning = require("./versioning");
const parseTriggers = require("./parseTriggers");
const MIN_FUNCTIONS_SDK_VERSION = "3.20.0";
async function tryCreateDelegate(context) {
    const packageJsonPath = path.join(context.sourceDir, "package.json");
    if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
        logger_1.logger.debug("Customer code is not Node");
        return undefined;
    }
    const runtime = (0, parseRuntimeAndValidateSDK_1.getRuntimeChoice)(context.sourceDir, context.runtime);
    if (!runtime.startsWith("nodejs")) {
        logger_1.logger.debug("Customer has a package.json but did not get a nodejs runtime. This should not happen");
        throw new error_1.FirebaseError(`Unexpected runtime ${runtime}`);
    }
    return new Delegate(context.projectId, context.projectDir, context.sourceDir, runtime);
}
exports.tryCreateDelegate = tryCreateDelegate;
class Delegate {
    constructor(projectId, projectDir, sourceDir, runtime) {
        this.projectId = projectId;
        this.projectDir = projectDir;
        this.sourceDir = sourceDir;
        this.runtime = runtime;
        this.name = "nodejs";
        this._sdkVersion = undefined;
    }
    get sdkVersion() {
        if (this._sdkVersion === undefined) {
            this._sdkVersion = versioning.getFunctionsSDKVersion(this.sourceDir) || "";
        }
        return this._sdkVersion;
    }
    validate() {
        versioning.checkFunctionsSDKVersion(this.sdkVersion);
        const relativeDir = path.relative(this.projectDir, this.sourceDir);
        validate.packageJsonIsValid(relativeDir, this.sourceDir, this.projectDir);
        return Promise.resolve();
    }
    async build() {
    }
    watch() {
        return Promise.resolve(() => Promise.resolve());
    }
    serve(port, config, envs) {
        var _a;
        const env = Object.assign(Object.assign({}, envs), { PORT: port.toString(), FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH, NODE_ENV: process.env.NODE_ENV });
        if (Object.keys(config || {}).length) {
            env.CLOUD_RUNTIME_CONFIG = JSON.stringify(config);
        }
        const sdkPath = require.resolve("firebase-functions", { paths: [this.sourceDir] });
        const binPath = sdkPath.substring(0, sdkPath.lastIndexOf("node_modules") + 12);
        const childProcess = spawn(path.join(binPath, ".bin", "firebase-functions"), [this.sourceDir], {
            env,
            cwd: this.sourceDir,
            stdio: ["ignore", "pipe", "inherit"],
        });
        (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
            logger_1.logger.debug(chunk.toString());
        });
        return Promise.resolve(async () => {
            const p = new Promise((resolve, reject) => {
                childProcess.once("exit", resolve);
                childProcess.once("error", reject);
            });
            await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
            setTimeout(() => {
                if (!childProcess.killed) {
                    childProcess.kill("SIGKILL");
                }
            }, 10000);
            return p;
        });
    }
    async discoverBuild(config, env) {
        if (!semver.valid(this.sdkVersion)) {
            logger_1.logger.debug(`Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.`);
            return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env);
        }
        if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
            (0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
                `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
            return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env);
        }
        let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
        if (!discovered) {
            const getPort = (0, util_1.promisify)(portfinder.getPort);
            const port = await getPort();
            const kill = await this.serve(port, config, env);
            try {
                discovered = await discovery.detectFromPort(port, this.projectId, this.runtime);
            }
            finally {
                await kill();
            }
        }
        return discovered;
    }
}
exports.Delegate = Delegate;