"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
0 && (module.exports = {
    handleClientFileLogs: null,
    handleLog: null,
    receiveBrowserLogsTurbopack: null,
    receiveBrowserLogsWebpack: null,
    restoreUndefined: null,
    stripFormatSpecifiers: null
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: all[name]
    });
}
_export(exports, {
    handleClientFileLogs: function() {
        return handleClientFileLogs;
    },
    handleLog: function() {
        return handleLog;
    },
    receiveBrowserLogsTurbopack: function() {
        return receiveBrowserLogsTurbopack;
    },
    receiveBrowserLogsWebpack: function() {
        return receiveBrowserLogsWebpack;
    },
    restoreUndefined: function() {
        return restoreUndefined;
    },
    stripFormatSpecifiers: function() {
        return stripFormatSpecifiers;
    }
});
const _picocolors = require("../../../lib/picocolors");
const _util = /*#__PURE__*/ _interop_require_default(require("util"));
const _sourcemap = require("./source-map");
const _forwardlogsshared = require("../../../next-devtools/shared/forward-logs-shared");
const _console = require("../../../client/lib/console");
const _filelogger = require("./file-logger");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function restoreUndefined(x) {
    if (x === _forwardlogsshared.UNDEFINED_MARKER) return undefined;
    if (Array.isArray(x)) return x.map(restoreUndefined);
    if (x && typeof x === 'object') {
        for(let k in x){
            x[k] = restoreUndefined(x[k]);
        }
    }
    return x;
}
function cleanConsoleArgsForFileLogging(args) {
    /**
   * Use formatConsoleArgs to strip out background and color format specifiers
   * and keep only the original string content for file logging
   */ try {
        return (0, _console.formatConsoleArgs)(args);
    } catch  {
        // Fallback to simple string conversion if formatting fails
        return args.map((arg)=>typeof arg === 'string' ? arg : _util.default.inspect(arg, {
                depth: 2
            })).join(' ');
    }
}
const methods = [
    'log',
    'info',
    'warn',
    'debug',
    'table',
    'error',
    'assert',
    'dir',
    'dirxml',
    'group',
    'groupCollapsed',
    'groupEnd'
];
const methodsToSkipInspect = new Set([
    'table',
    'dir',
    'dirxml',
    'group',
    'groupCollapsed',
    'groupEnd'
]);
// we aren't overriding console, we're just making a (slightly convoluted) helper for replaying user console methods
const forwardConsole = {
    ...console,
    ...Object.fromEntries(methods.map((method)=>[
            method,
            (...args)=>console[method](...args.map((arg)=>methodsToSkipInspect.has(method) || typeof arg !== 'object' || arg === null ? arg : _util.default.inspect(arg, {
                        depth: Infinity,
                        colors: true
                    })))
        ]))
};
async function deserializeArgData(arg) {
    try {
        // we want undefined to be represented as it would be in the browser from the user's perspective (otherwise it would be stripped away/shown as null)
        if (arg === _forwardlogsshared.UNDEFINED_MARKER) {
            return restoreUndefined(arg);
        }
        return restoreUndefined(JSON.parse(arg));
    } catch  {
        return arg;
    }
}
const colorError = (mapped, config)=>{
    const colorFn = (config == null ? void 0 : config.applyColor) === undefined || config.applyColor ? _picocolors.red : (x)=>x;
    switch(mapped.kind){
        case 'mapped-stack':
        case 'stack':
            {
                return ((config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '') + `\n${colorFn(mapped.stack)}`;
            }
        case 'with-frame-code':
            {
                return ((config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '') + `\n${colorFn(mapped.stack)}\n${mapped.frameCode}`;
            }
        // a more sophisticated version of this allows the user to config if they want ignored frames (but we need to be sure to source map them)
        case 'all-ignored':
            {
                return (config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '';
            }
        default:
            {}
    }
    mapped;
};
function processConsoleFormatStrings(args) {
    /**
   * this handles the case formatting is applied to the console log
   * otherwise we will see the format specifier directly in the terminal output
   */ if (args.length > 0 && typeof args[0] === 'string') {
        const formatString = args[0];
        if (formatString.includes('%s') || formatString.includes('%d') || formatString.includes('%i') || formatString.includes('%f') || formatString.includes('%o') || formatString.includes('%O') || formatString.includes('%c')) {
            try {
                const formatted = _util.default.format(...args);
                return [
                    formatted
                ];
            } catch  {
                return args;
            }
        }
    }
    return args;
}
function stripFormatSpecifiers(args) {
    if (args.length === 0 || typeof args[0] !== 'string') return args;
    const fmtIn = String(args[0]);
    const rest = args.slice(1);
    if (!fmtIn.includes('%')) return args;
    let fmtOut = '';
    let argPtr = 0;
    for(let i = 0; i < fmtIn.length; i++){
        if (fmtIn[i] !== '%') {
            fmtOut += fmtIn[i];
            continue;
        }
        if (fmtIn[i + 1] === '%') {
            fmtOut += '%';
            i++;
            continue;
        }
        const token = fmtIn[++i];
        if (!token) {
            fmtOut += '%';
            continue;
        }
        if ('csdifoOj'.includes(token) || token === 'O') {
            if (argPtr < rest.length) {
                if (token === 'c') {
                    argPtr++;
                } else if (token === 'o' || token === 'O' || token === 'j') {
                    const obj = rest[argPtr++];
                    fmtOut += _util.default.inspect(obj, {
                        depth: 2,
                        colors: false
                    });
                } else {
                    // string(...) is safe for remaining specifiers
                    fmtOut += String(rest[argPtr++]);
                }
            }
            continue;
        }
        fmtOut += '%' + token;
    }
    const result = [
        fmtOut
    ];
    if (argPtr < rest.length) {
        result.push(...rest.slice(argPtr));
    }
    return result;
}
async function prepareFormattedErrorArgs(entry, ctx, distDir) {
    const mapped = await (0, _sourcemap.getSourceMappedStackFrames)(entry.stack, ctx, distDir);
    return [
        colorError(mapped, {
            prefix: entry.prefix
        })
    ];
}
async function prepareConsoleArgs(entry, ctx, distDir) {
    const deserialized = await Promise.all(entry.args.map(async (arg)=>{
        if (arg.kind === 'arg') {
            const data = await deserializeArgData(arg.data);
            if (entry.method === 'warn' && typeof data === 'string') {
                return (0, _picocolors.yellow)(data);
            }
            return data;
        }
        if (!arg.stack) return (0, _picocolors.red)(arg.prefix);
        const mapped = await (0, _sourcemap.getSourceMappedStackFrames)(arg.stack, ctx, distDir);
        return colorError(mapped, {
            prefix: arg.prefix,
            applyColor: false
        });
    }));
    return processConsoleFormatStrings(deserialized);
}
async function prepareConsoleErrorArgs(entry, ctx, distDir) {
    const deserialized = await Promise.all(entry.args.map(async (arg)=>{
        if (arg.kind === 'arg') {
            if (arg.isRejectionMessage) return (0, _picocolors.red)(arg.data);
            return deserializeArgData(arg.data);
        }
        if (!arg.stack) return (0, _picocolors.red)(arg.prefix);
        const mapped = await (0, _sourcemap.getSourceMappedStackFrames)(arg.stack, ctx, distDir);
        return colorError(mapped, {
            prefix: arg.prefix
        });
    }));
    const mappedStack = await (0, _sourcemap.getSourceMappedStackFrames)(entry.consoleErrorStack, ctx, distDir);
    /**
   * don't show the stack + codeblock when there are errors present, since:
   * - it will look overwhelming to see 2 stacks and 2 code blocks
   * - the user already knows where the console.error is at because we append the location
   */ const location = (0, _sourcemap.getConsoleLocation)(mappedStack);
    if (entry.args.some((a)=>a.kind === 'formatted-error-arg')) {
        const result = stripFormatSpecifiers(deserialized);
        if (location) {
            result.push((0, _picocolors.dim)(`(${location})`));
        }
        return result;
    }
    const result = [
        ...processConsoleFormatStrings(deserialized),
        colorError(mappedStack)
    ];
    if (location) {
        result.push((0, _picocolors.dim)(`(${location})`));
    }
    return result;
}
async function handleTable(entry, browserPrefix, ctx, distDir) {
    const deserializedArgs = await Promise.all(entry.args.map(async (arg)=>{
        if (arg.kind === 'formatted-error-arg') {
            return {
                stack: arg.stack
            };
        }
        return deserializeArgData(arg.data);
    }));
    const location = await (async ()=>{
        if (!entry.consoleMethodStack) {
            return;
        }
        const frames = await (0, _sourcemap.getSourceMappedStackFrames)(entry.consoleMethodStack, ctx, distDir);
        return (0, _sourcemap.getConsoleLocation)(frames);
    })();
    // we can't inline pass browser prefix, but it looks better multiline for table anyways
    forwardConsole.log(browserPrefix);
    forwardConsole.table(...deserializedArgs);
    if (location) {
        forwardConsole.log((0, _picocolors.dim)(`(${location})`));
    }
}
async function handleTrace(entry, browserPrefix, ctx, distDir) {
    const deserializedArgs = await Promise.all(entry.args.map(async (arg)=>{
        if (arg.kind === 'formatted-error-arg') {
            if (!arg.stack) return (0, _picocolors.red)(arg.prefix);
            const mapped = await (0, _sourcemap.getSourceMappedStackFrames)(arg.stack, ctx, distDir);
            return colorError(mapped, {
                prefix: arg.prefix
            });
        }
        return deserializeArgData(arg.data);
    }));
    if (!entry.consoleMethodStack) {
        forwardConsole.log(browserPrefix, ...deserializedArgs, '[Trace unavailable]');
        return;
    }
    // TODO(rob): refactor so we can re-use result and not re-run the entire source map to avoid trivial post processing
    const [mapped, mappedIgnored] = await Promise.all([
        (0, _sourcemap.getSourceMappedStackFrames)(entry.consoleMethodStack, ctx, distDir, false),
        (0, _sourcemap.getSourceMappedStackFrames)(entry.consoleMethodStack, ctx, distDir)
    ]);
    const location = (0, _sourcemap.getConsoleLocation)(mappedIgnored);
    forwardConsole.log(browserPrefix, ...deserializedArgs, `\n${mapped.stack}`, ...location ? [
        `\n${(0, _picocolors.dim)(`(${location})`)}`
    ] : []);
}
async function handleDir(entry, browserPrefix, ctx, distDir) {
    const loggableEntry = await prepareConsoleArgs(entry, ctx, distDir);
    const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
    if (entry.consoleMethodStack) {
        const mapped = await (0, _sourcemap.getSourceMappedStackFrames)(entry.consoleMethodStack, ctx, distDir);
        const location = (0, _picocolors.dim)(`(${(0, _sourcemap.getConsoleLocation)(mapped)})`);
        const originalWrite = process.stdout.write.bind(process.stdout);
        let captured = '';
        process.stdout.write = (chunk)=>{
            captured += chunk;
            return true;
        };
        try {
            consoleMethod(...loggableEntry);
        } finally{
            process.stdout.write = originalWrite;
        }
        const preserved = captured.replace(/\r?\n$/, '');
        originalWrite(`${browserPrefix}${preserved} ${location}\n`);
        return;
    }
    consoleMethod(browserPrefix, ...loggableEntry);
}
async function handleDefaultConsole(entry, browserPrefix, ctx, distDir, config, isServerLog) {
    const consoleArgs = await prepareConsoleArgs(entry, ctx, distDir);
    const withStackEntry = await (0, _sourcemap.withLocation)({
        original: consoleArgs,
        stack: entry.consoleMethodStack || null
    }, ctx, distDir, config);
    const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
    consoleMethod(browserPrefix, ...withStackEntry);
    // Process enqueued logs and write to file
    // Log to file with correct source based on context
    const fileLogger = (0, _filelogger.getFileLogger)();
    // Use cleaned console args to strip out background and color format specifiers
    const message = cleanConsoleArgsForFileLogging(consoleArgs);
    if (isServerLog) {
        fileLogger.logServer(entry.method.toUpperCase(), message);
    } else {
        fileLogger.logBrowser(entry.method.toUpperCase(), message);
    }
}
async function handleLog(entries, ctx, distDir, config) {
    // Determine the source based on the context
    const isServerLog = ctx.isServer || ctx.isEdgeServer;
    const browserPrefix = isServerLog ? (0, _picocolors.cyan)('[server]') : (0, _picocolors.cyan)('[browser]');
    const fileLogger = (0, _filelogger.getFileLogger)();
    for (const entry of entries){
        try {
            switch(entry.kind){
                case 'console':
                    {
                        switch(entry.method){
                            case 'table':
                                {
                                    // timeout based abort on source mapping result
                                    await handleTable(entry, browserPrefix, ctx, distDir);
                                    break;
                                }
                            // ignore frames
                            case 'trace':
                                {
                                    await handleTrace(entry, browserPrefix, ctx, distDir);
                                    break;
                                }
                            case 'dir':
                                {
                                    await handleDir(entry, browserPrefix, ctx, distDir);
                                    break;
                                }
                            case 'dirxml':
                                {
                                // xml log thing maybe needs an impl
                                // fallthrough
                                }
                            case 'group':
                            case 'groupCollapsed':
                            case 'groupEnd':
                                {
                                // [browser] undefined (app/page.tsx:8:11) console.group
                                // fallthrough
                                }
                            case 'assert':
                                {
                                // check console assert
                                // fallthrough
                                }
                            case 'log':
                            case 'info':
                            case 'debug':
                            case 'error':
                            case 'warn':
                                {
                                    await handleDefaultConsole(entry, browserPrefix, ctx, distDir, config, isServerLog);
                                    break;
                                }
                            default:
                                {
                                    entry;
                                }
                        }
                        break;
                    }
                // any logged errors are anything that are logged as "red" in the browser but aren't only an Error (console.error, Promise.reject(100))
                case 'any-logged-error':
                    {
                        const consoleArgs = await prepareConsoleErrorArgs(entry, ctx, distDir);
                        forwardConsole.error(browserPrefix, ...consoleArgs);
                        // Process enqueued logs and write to file
                        fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
                        break;
                    }
                // formatted error is an explicit error event (rejections, uncaught errors)
                case 'formatted-error':
                    {
                        const formattedArgs = await prepareFormattedErrorArgs(entry, ctx, distDir);
                        forwardConsole.error(browserPrefix, ...formattedArgs);
                        // Process enqueued logs and write to file
                        fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(formattedArgs));
                        break;
                    }
                default:
                    {}
            }
        } catch  {
            switch(entry.kind){
                case 'any-logged-error':
                    {
                        const consoleArgs = await prepareConsoleErrorArgs(entry, ctx, distDir);
                        forwardConsole.error(browserPrefix, ...consoleArgs);
                        // Process enqueued logs and write to file
                        fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
                        break;
                    }
                case 'console':
                    {
                        const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
                        const consoleArgs = await prepareConsoleArgs(entry, ctx, distDir);
                        consoleMethod(browserPrefix, ...consoleArgs);
                        // Process enqueued logs and write to file
                        fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
                        break;
                    }
                case 'formatted-error':
                    {
                        forwardConsole.error(browserPrefix, `${entry.prefix}\n`, entry.stack);
                        // Process enqueued logs and write to file
                        fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging([
                            `${entry.prefix}\n${entry.stack}`
                        ]));
                        break;
                    }
                default:
                    {}
            }
        }
    }
}
async function receiveBrowserLogsWebpack(opts) {
    const { entries, router, sourceType, clientStats, serverStats, edgeServerStats, rootDirectory, distDir } = opts;
    const isAppDirectory = router === 'app';
    const isServer = sourceType === 'server';
    const isEdgeServer = sourceType === 'edge-server';
    const ctx = {
        bundler: 'webpack',
        isServer,
        isEdgeServer,
        isAppDirectory,
        clientStats,
        serverStats,
        edgeServerStats,
        rootDirectory
    };
    await handleLog(entries, ctx, distDir, opts.config);
}
async function receiveBrowserLogsTurbopack(opts) {
    const { entries, router, sourceType, project, projectPath, distDir } = opts;
    const isAppDirectory = router === 'app';
    const isServer = sourceType === 'server';
    const isEdgeServer = sourceType === 'edge-server';
    const ctx = {
        bundler: 'turbopack',
        project,
        projectPath,
        isServer,
        isEdgeServer,
        isAppDirectory
    };
    await handleLog(entries, ctx, distDir, opts.config);
}
async function handleClientFileLogs(logs) {
    const fileLogger = (0, _filelogger.getFileLogger)();
    for (const log of logs){
        fileLogger.logBrowser(log.level, log.message);
    }
}

//# sourceMappingURL=receive-logs.js.map