"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = void 0;
exports.createReplContext = createReplContext;
exports.disposeReplContext = disposeReplContext;
exports.evaluateReplCode = evaluateReplCode;
exports.runLocalAppRepl = runLocalAppRepl;
exports.localReplUsage = localReplUsage;
exports.tryGet = tryGet;
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const node_repl_1 = require("node:repl");
const node_util_1 = require("node:util");
const database_1 = require("../database");
const di_1 = require("../di");
const replContextCleanups = new WeakMap();
const replGlobalOwners = [];
let originalDollarDescriptor;
let originalDoubleDollarDescriptor;
function createReplContext(app) {
    const resolve = (token) => app.get(token);
    const db = tryGet(app, database_1.BaseDatabase);
    const { tokens, instances } = createProviderNamespaces(app, db);
    const context = {
        app,
        container: app.container,
        config: app.config,
        db,
        resolve,
        r: resolve,
        $: tokens,
        $$: instances,
        process,
        Buffer,
        inspect: node_util_1.inspect
    };
    replContextCleanups.set(context, installReplGlobals(tokens, instances));
    return context;
}
/** Releases the global namespace installation associated with a REPL context. */
function disposeReplContext(context) {
    const cleanup = replContextCleanups.get(context);
    if (!cleanup)
        return;
    replContextCleanups.delete(context);
    cleanup();
}
/**
 * Builds the DK-style provider namespaces without resolving providers. `$` exposes
 * their tokens while `$$` resolves on first property access from the provider's
 * owning module, preserving module-local dependency lookup.
 */
function createProviderNamespaces(app, db) {
    const tokens = Object.create(null);
    const instances = Object.create(null);
    for (const registered of app.container.listRegisteredProviders()) {
        // Request-scoped providers need a request context and therefore cannot be
        // resolved safely in the REPL. This also excludes HTTP controllers.
        // Transient providers remain available: resolving them is valid outside a
        // request and intentionally creates a fresh value per property access.
        if ((0, di_1.getProviderScope)(registered.provider) === 'request')
            continue;
        const name = getReplTokenName(registered.token);
        if (!name || Object.hasOwn(tokens, name))
            continue;
        // Keep the first registration, matching DK's provider-tree traversal. The
        // container order is deterministic, and using null-prototype objects makes
        // names such as `constructor` and `__proto__` safe REPL properties.
        Object.defineProperty(tokens, name, {
            enumerable: true,
            get: () => registered.token
        });
        Object.defineProperty(instances, name, {
            enumerable: true,
            get: () => app.container.resolve(registered.token, registered.moduleId)
        });
    }
    // Entities are classes rather than providers, so make them available only on
    // `$$`. Provider names retain precedence if a name collides.
    for (const entity of db?.entityRegistry ?? []) {
        if (!entity.name || Object.hasOwn(instances, entity.name))
            continue;
        Object.defineProperty(instances, entity.name, {
            enumerable: true,
            get: () => entity
        });
    }
    return { tokens, instances };
}
function getReplTokenName(token) {
    if (typeof token === 'string')
        return token;
    if (typeof token === 'function')
        return token.name || undefined;
    if (typeof token === 'number' || typeof token === 'bigint' || typeof token === 'boolean')
        return String(token);
    if (typeof token === 'symbol')
        return token.description || token.toString();
    return undefined;
}
function installReplGlobals(tokens, instances) {
    const globalContext = globalThis;
    if (replGlobalOwners.length === 0) {
        originalDollarDescriptor = Object.getOwnPropertyDescriptor(globalContext, '$');
        originalDoubleDollarDescriptor = Object.getOwnPropertyDescriptor(globalContext, '$$');
    }
    const owner = { tokens, instances };
    replGlobalOwners.push(owner);
    globalContext.$ = tokens;
    globalContext.$$ = instances;
    return () => {
        const index = replGlobalOwners.indexOf(owner);
        if (index < 0)
            return;
        const wasActive = index === replGlobalOwners.length - 1;
        replGlobalOwners.splice(index, 1);
        if (!wasActive)
            return;
        const previousOwner = replGlobalOwners.at(-1);
        restoreReplGlobal(globalContext, '$', owner.tokens, previousOwner?.tokens, originalDollarDescriptor);
        restoreReplGlobal(globalContext, '$$', owner.instances, previousOwner?.instances, originalDoubleDollarDescriptor);
        if (replGlobalOwners.length === 0) {
            originalDollarDescriptor = undefined;
            originalDoubleDollarDescriptor = undefined;
        }
    };
}
function restoreReplGlobal(globalContext, name, ownedValue, previousValue, originalDescriptor) {
    // Do not overwrite a global changed by application code after this REPL
    // context was installed.
    if (globalContext[name] !== ownedValue)
        return;
    if (previousValue) {
        globalContext[name] = previousValue;
        return;
    }
    if (originalDescriptor)
        Object.defineProperty(globalContext, name, originalDescriptor);
    else
        delete globalContext[name];
}
async function evaluateReplCode(context, code, colors = false) {
    const logs = [];
    const capture = (...args) => logs.push(args.map(item => (typeof item === 'string' ? item : (0, node_util_1.inspect)(item, { depth: 4, colors }))).join(' '));
    const localConsole = {
        log: capture,
        warn: capture,
        error: capture,
        info: capture,
        debug: capture
    };
    try {
        const fn = new Function('context', 'console', `with (context) { return eval(${JSON.stringify(code)}) }`);
        let result = fn(context, localConsole);
        if (result && typeof result === 'object' && typeof result.then === 'function')
            result = await result;
        const resultText = result === undefined ? '' : (0, node_util_1.inspect)(result, { depth: 4, colors });
        return { output: [...logs, resultText].filter(Boolean).join('\n') };
    }
    catch (error) {
        return {
            output: logs.join('\n'),
            error: error instanceof Error ? (error.stack ?? error.message) : String(error)
        };
    }
}
async function runLocalAppRepl(app, args) {
    if (takeHelpArgument(args)) {
        process.stdout.write(localReplUsage());
        return;
    }
    const evalCode = takeEvalArgument(args);
    if (args.length)
        throw new Error(`repl does not accept arguments: ${args.join(' ')}`);
    // A local REPL needs the application container, but it is not a running server.
    // Its dedicated mode suppresses operational services without changing normal DI startup.
    app.configureForRepl();
    try {
        await app.start();
        if (evalCode !== undefined) {
            const context = createReplContext(app);
            try {
                const result = await evaluateReplCode(context, evalCode, !!process.stdout.isTTY);
                if (result.output)
                    process.stdout.write(`${result.output}\n`);
                if (result.error) {
                    const error = new Error(result.error);
                    error.stack = result.error;
                    throw error;
                }
            }
            finally {
                disposeReplContext(context);
            }
            return;
        }
        await openLocalRepl(app);
    }
    finally {
        await app.stop();
    }
}
function openLocalRepl(app) {
    const server = (0, node_repl_1.start)({
        prompt: 'tsf> ',
        useColors: !!process.stdout.isTTY,
        ignoreUndefined: true
    });
    const context = createReplContext(app);
    Object.assign(server.context, context);
    if (process.stdin.isTTY && process.env.NODE_REPL_HISTORY !== '') {
        const historyPath = process.env.NODE_REPL_HISTORY || (0, node_path_1.join)((0, node_os_1.homedir)(), '.tsf_repl_history');
        server.setupHistory(historyPath, error => {
            if (error)
                process.stderr.write(`Could not load REPL history: ${error.message}\n`);
        });
    }
    return new Promise(resolve => server.once('exit', () => {
        disposeReplContext(context);
        resolve();
    }));
}
function takeEvalArgument(args) {
    for (let index = 0; index < args.length; index++) {
        const arg = args[index];
        if (arg === '--eval' || arg === '-e') {
            const value = args[index + 1];
            if (value === undefined)
                throw new Error(`${arg} requires JavaScript source`);
            args.splice(index, 2);
            return value;
        }
        if (arg.startsWith('--eval=')) {
            args.splice(index, 1);
            return arg.slice('--eval='.length);
        }
    }
    return undefined;
}
function takeHelpArgument(args) {
    return args.some(arg => arg === '--help' || arg === '-h');
}
function localReplUsage() {
    return `Usage: node <entrypoint> repl [options]\n\nStart a local application REPL without starting HTTP listeners or workers.\n\nOptions:\n  --eval <code>, -e <code>  Evaluate JavaScript once and exit\n  --help, -h                Show this help\n`;
}
function tryGet(app, token) {
    try {
        return app.get(token);
    }
    catch {
        return undefined;
    }
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"ReplEvaluation\":{\"kind\":18,\"typeName\":\"ReplEvaluation\",\"types\":[{\"kind\":20,\"name\":\"output\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"error\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true}]}}]")[1];
//# sourceMappingURL=repl.js.map