"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.connect = exports.start = exports.stop = void 0;
const morgan = require("morgan");
const net_1 = require("net");
const superstatic_1 = require("superstatic");
const clc = require("colorette");
const detectProjectRoot_1 = require("../detectProjectRoot");
const error_1 = require("../error");
const implicitInit_1 = require("../hosting/implicitInit");
const initMiddleware_1 = require("../hosting/initMiddleware");
const config = require("../hosting/config");
const cloudRunProxy_1 = require("../hosting/cloudRunProxy");
const functionsProxy_1 = require("../hosting/functionsProxy");
const stream_1 = require("stream");
const emulatorLogger_1 = require("../emulator/emulatorLogger");
const types_1 = require("../emulator/types");
const utils_1 = require("../utils");
const requireHostingSite_1 = require("../requireHostingSite");
const projectUtils_1 = require("../projectUtils");
const portUtils_1 = require("../emulator/portUtils");
let destroyServer = undefined;
const logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.HOSTING);
function startServer(options, config, port, init) {
    const firebaseMiddleware = (0, initMiddleware_1.initMiddleware)(init);
    const morganStream = new stream_1.Writable();
    morganStream._write = (chunk, encoding, callback) => {
        if (chunk instanceof Buffer) {
            logger.logLabeled("BULLET", "hosting", chunk.toString().trim());
        }
        callback();
    };
    const morganMiddleware = morgan("combined", {
        stream: morganStream,
    });
    const after = options.frameworksDevModeHandle && {
        files: options.frameworksDevModeHandle,
    };
    const server = (0, superstatic_1.server)({
        debug: false,
        port: port,
        hostname: options.host,
        config: config,
        compression: true,
        cwd: (0, detectProjectRoot_1.detectProjectRoot)(options) || undefined,
        stack: "strict",
        before: {
            files: (req, res, next) => {
                morganMiddleware(req, res, () => null);
                firebaseMiddleware(req, res, next);
            },
        },
        after,
        rewriters: {
            function: (0, functionsProxy_1.functionsProxy)(options),
            run: (0, cloudRunProxy_1.default)(options),
        },
    }).listen(() => {
        const siteName = config.target || config.site;
        const label = siteName ? "hosting[" + siteName + "]" : "hosting";
        if (config.public && config.public !== ".") {
            logger.logLabeled("BULLET", label, "Serving hosting files from: " + clc.bold(config.public));
        }
        logger.logLabeled("SUCCESS", label, "Local server: " + clc.underline(clc.bold("http://" + options.host + ":" + port)));
    });
    destroyServer = (0, utils_1.createDestroyer)(server);
    server.on("error", (err) => {
        logger.log("DEBUG", `Error from superstatic server: ${err.stack || ""}`);
        throw new error_1.FirebaseError(`An error occurred while starting the hosting development server:\n\n${err.message}`);
    });
}
function stop() {
    return destroyServer ? destroyServer() : Promise.resolve();
}
exports.stop = stop;
async function start(options) {
    const init = await (0, implicitInit_1.implicitInit)(options);
    if (!options.site) {
        try {
            await (0, requireHostingSite_1.requireHostingSite)(options);
        }
        catch (_a) {
            if (init.json) {
                options.site = JSON.parse(init.json).projectId;
            }
            else {
                options.site = (0, projectUtils_1.getProjectId)(options) || "site";
            }
        }
    }
    const configs = config.hostingConfig(options);
    const assignedPorts = new Set([5001]);
    for (let i = 0; i < configs.length; i++) {
        let port = i === 0 ? options.port : options.port + 4 + i;
        while (assignedPorts.has(port) || !(await availablePort(options.host, port))) {
            port += 1;
        }
        assignedPorts.add(port);
        startServer(options, configs[i], port, init);
    }
    assignedPorts.delete(5001);
    return { ports: Array.from(assignedPorts) };
}
exports.start = start;
async function connect() {
    await Promise.resolve();
}
exports.connect = connect;
function availablePort(host, port) {
    return (0, portUtils_1.checkListenable)({
        address: host,
        port,
        family: (0, net_1.isIPv4)(host) ? "IPv4" : "IPv6",
    });
}