"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorWithCode = exports.makeAuthClusterCookieName = exports.UNEXPECTED_PIPE_AXIOS_ERROR = exports.UNEXPECTED_PIPE_AXIOS_RESPONSE = void 0;
exports.isProductionEnv = isProductionEnv;
exports.isLocalModeByEnvironment = isLocalModeByEnvironment;
exports.getInterfaceVersion = getInterfaceVersion;
exports.prepareErrorToSend = prepareErrorToSend;
exports.sendResponse = sendResponse;
exports.sendError = sendError;
exports.pipeAxiosErrorOrFalse = pipeAxiosErrorOrFalse;
exports.pipeAxiosResponseTyped = pipeAxiosResponseTyped;
exports.pipeAxiosResponse = pipeAxiosResponse;
exports.pipeResponse = pipeResponse;
exports.pipeReadableToWriteable = pipeReadableToWriteable;
exports.sendAndLogError = sendAndLogError;
const stream = __importStar(require("stream"));
const path_1 = __importDefault(require("path"));
const forEach_1 = __importDefault(require("lodash/forEach"));
const pick_1 = __importDefault(require("lodash/pick"));
const toString_1 = __importDefault(require("lodash/toString"));
const utils_1 = require("../../shared/utils");
const ServerFactory_1 = require("../ServerFactory");
const constants_1 = require("../../shared/constants");
const error_1 = require("../../shared/utils/error");
function isProductionEnv() {
    return (0, ServerFactory_1.getApp)().config.appEnv !== 'development';
}
function isLocalModeByEnvironment() {
    return process.env.APP_ENV === 'local';
}
function getInterfaceVersion() {
    if (isProductionEnv()) {
        const { INIT_CWD } = process.env;
        const packageJsonPath = INIT_CWD
            ? path_1.default.resolve(INIT_CWD, 'package.json')
            : path_1.default.resolve('./package.json');
        try {
            /* eslint-disable-next-line global-require, security/detect-non-literal-require */
            return require(packageJsonPath).version;
        }
        catch (e) {
            (0, ServerFactory_1.getApp)().nodekit.ctx.logError(`Failed to get version from ${packageJsonPath}`, e, {
                INIT_CWD,
                packageJsonPath,
            });
            return 'unkonwn';
        }
    }
    else {
        return 'local';
    }
}
function prepareErrorToSend(e) {
    const res = {
        message: '',
    };
    if (e instanceof Error) {
        res.message = (0, toString_1.default)(e);
    }
    else if ((0, utils_1.isYTError)(e)) {
        return e;
    }
    else {
        res.message = JSON.stringify(e);
    }
    return res;
}
function sendResponse(res, data) {
    return res.set('content-type', 'application/json').status(200).send(JSON.stringify(data));
}
function sendError(res, error, errCode = 500) {
    const errData = prepareErrorToSend(error);
    return res.set('content-type', 'application/json').status(errCode).send(errData);
}
function asAxiosError(e) {
    return e.isAxiosError ? e : undefined;
}
exports.UNEXPECTED_PIPE_AXIOS_RESPONSE = 'Unexpected behavior: response.data should be an instance of stream.Readable';
exports.UNEXPECTED_PIPE_AXIOS_ERROR = 'Unexpected behavior: e.response.data should be an instance of stream.Readable';
async function pipeAxiosErrorOrFalse(ctx, dst, e) {
    return await pipeAxiosResponse(ctx, dst, e.response, '[Send error]');
}
const LOG_HEADERS = ['x-yt-proxy', 'x-yt-request-id', 'x-yt-trace-id', 'content-length'];
function pipeAxiosResponseTyped(ctx, dst, src, logMsgPrefix, transformHeaders) {
    return pipeAxiosResponse(ctx, dst, src, logMsgPrefix, transformHeaders);
}
/**
 * pipeAxiosResponse is a helper function to forward stream-content to the browser
 *
 * `ResponseDataT` just allows to declare response type, it affects nothing,
 * but allows link highlight connection between modules from src/ui and src/server
 */
async function pipeAxiosResponse(ctx, dst, src, logMsgPrefix = '', transformHeaders = (v) => v) {
    if (src?.data instanceof stream.Readable) {
        return await pipeResponse(logMsgPrefix, ctx, dst, src.data, src.headers, src.status, transformHeaders);
    }
    else {
        return Promise.resolve(undefined);
    }
}
async function pipeResponse(logMsgPrefix, ctx, dst, data, headers, status, transformHeaders = (v) => v) {
    dst.status(status ?? 500);
    (0, forEach_1.default)(transformHeaders(headers), (value, key) => {
        if (key !== 'content-length' && key !== 'vary' && key !== 'www-authenticate') {
            dst.setHeader(key, value);
        }
    });
    const headersToLog = (0, pick_1.default)(headers, LOG_HEADERS);
    ctx.log(logMsgPrefix + ' Headers', headersToLog);
    const pipedDataSize = await pipeReadableToWriteable(ctx, dst, data, logMsgPrefix);
    return { headerContentLength: headers['content-length'], pipedDataSize };
}
async function pipeReadableToWriteable(ctx, dst, src, logMsgPrefix) {
    let firstChunk = true;
    let pipedDataSize = 0;
    const closePromise = new Promise((resolve, reject) => {
        src.on('data', (chunk) => {
            if (firstChunk) {
                ctx.log(logMsgPrefix + ' TTFB');
                firstChunk = false;
            }
            pipedDataSize += chunk?.length;
        });
        src.on('error', (e) => {
            resolve = () => { };
            reject(e);
        });
        src.on('close', () => {
            ctx.log(logMsgPrefix + ' Response stream closed', {
                pipedDataSize,
            });
            resolve();
        });
    });
    src.pipe(dst);
    await closePromise;
    return pipedDataSize;
}
async function sendAndLogError(ctx, res, status, e, extra = {}) {
    const ae = asAxiosError(e);
    if (ae) {
        ctx.logError('AxiosError', ae, extra);
        if (await pipeAxiosErrorOrFalse(ctx, res, ae)) {
            return;
        }
        if (ae.response?.status) {
            (0, forEach_1.default)(ae.response.headers, (value, key) => {
                res.append(key, value);
            });
            return res.status(ae.response.status).send(ae.response.data || ae.response.statusText);
        }
        return res.status(504).send({ message: (0, toString_1.default)(e) });
    }
    ctx.logError('Error', e, extra);
    if (e instanceof ErrorWithCode) {
        return res.status(e.httpStatusCode).send(e);
    }
    if (e instanceof Error) {
        return res.status(status || 500).send({ message: (0, toString_1.default)(e) });
    }
    if (typeof e === 'string') {
        return res.status(status || 500).send({ mesasge: e });
    }
    if ((0, utils_1.isYTError)(e)) {
        return res.status(status || 500).send(e);
    }
    return res.status(status || 500).send({ message: JSON.stringify(e) });
}
const makeAuthClusterCookieName = (ytAuthCluster) => {
    return `${ytAuthCluster}_${constants_1.YT_CYPRESS_COOKIE_NAME}`;
};
exports.makeAuthClusterCookieName = makeAuthClusterCookieName;
class ErrorWithCode extends error_1.UIBatchError {
    constructor(httpStatusCode, error) {
        super(error);
        this.httpStatusCode = httpStatusCode;
    }
}
exports.ErrorWithCode = ErrorWithCode;
