"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevConsoleSrpcServer = void 0;
exports.maskSecrets = maskSecrets;
exports.collectProperties = collectProperties;
const __ΩRecord = ['K', 'T', 'Record', 'l\'e#"Rb!b"Pde"!N#!w#y'];
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const util_1 = require("util");
const http_1 = require("@deepkit/http");
/*@ts-ignore*/
var { __ΩScopedLogger } = require('@deepkit/logger');
const type_1 = require("@deepkit/type");
/*@ts-ignore*/
var { __ΩUReplCompleteItem, __ΩDevConsoleClientMessage, __ΩDevConsoleServerMessage } = require('../../resources/proto/generated/devconsole/devconsole');
const devconsole_1 = require("../../resources/proto/generated/devconsole/devconsole");
const resolver_1 = require("../app/resolver");
const state_1 = require("../app/state");
const dialect_1 = require("../database/dialect");
const healthcheck_service_1 = require("../health/healthcheck.service");
const package_1 = require("../helpers/io/package");
const repl_context_1 = require("../services/cli/repl-context");
const entity_1 = require("../services/worker/entity");
const queue_1 = require("../services/worker/queue");
const SrpcServer_1 = require("../srpc/SrpcServer");
/*@ts-ignore*/
var { __ΩSrpcMeta } = require('../srpc/types');
const devconsole_middleware_1 = require("./devconsole.middleware");
const devconsole_store_1 = require("./devconsole.store");
const patches_1 = require("./patches");
const __ΩDCClientMsg = [() => __ΩDevConsoleClientMessage, 'DCClientMsg', 'n!w"y'];
const __ΩDCServerMsg = [() => __ΩDevConsoleServerMessage, 'DCServerMsg', 'n!w"y'];
const SECRET_MASK_PATTERNS = ['SECRET', 'PASSWORD', 'DSN', 'TOKEN', 'KEY'];
function maskSecrets(config) {
    const masked = {};
    for (const [key, value] of Object.entries(config)) {
        const upperKey = key.toUpperCase();
        if (SECRET_MASK_PATTERNS.some(__assignType(p => upperKey.includes(p), ['p', '', 'P"2!"/"'])) && value) {
            masked[key] = '****';
        }
        else {
            masked[key] = value;
        }
    }
    return masked;
}
maskSecrets.__type = [() => __ΩRecord, 'config', () => __ΩRecord, 'maskSecrets', 'P&#o!#2"&#o##/$'];
class DevConsoleSrpcServer {
    get store() {
        return devconsole_store_1.DevConsoleStore.get();
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    get app() {
        return state_1.globalState.currentApp;
    }
    constructor(logger) {
        this.replInitialized = false;
        this.server = (SrpcServer_1.SrpcServer.Ω = [[() => __ΩSrpcMeta, 'n!'], [() => __ΩDCClientMsg, 'n!'], [() => __ΩDCServerMsg, 'n!']], new SrpcServer_1.SrpcServer({
            logger,
            clientMessage: devconsole_1.DevConsoleClientMessage,
            serverMessage: devconsole_1.DevConsoleServerMessage,
            wsPath: '/_devconsole/ws',
            debug: false,
            logLevel: false
        }));
        (0, patches_1.skipDevConsoleObserver)(this.server);
        // Skip HMAC auth but restrict to direct localhost connections.
        // Rejects requests forwarded through a reverse proxy.
        this.server.setClientAuthorizer(__assignType(async (_meta, req) => {
            return (0, devconsole_middleware_1.isLocalhostDirect)(req);
        }, ['_meta', 'req', '', 'P"2!"2""/#']));
        this.registerHandlers();
    }
    registerHandlers() {
        const s = this.server;
        // REPL
        s.registerMessageHandler('uReplEval', __assignType(async (_stream, data) => {
            return this.handleReplEval(data.code);
        }, ['_stream', 'data', '', 'P"2!"2""/#']));
        s.registerMessageHandler('uReplComplete', __assignType(async (_stream, data) => {
            return this.handleReplComplete(data.code, data.cursorPos);
        }, ['_stream', 'data', '', 'P"2!"2""/#']));
        // Overview / Dashboard
        s.registerMessageHandler('uGetOverview', async () => {
            return {
                name: (0, package_1.getPackageName)() ?? '',
                version: (0, package_1.getPackageVersion)() ?? '',
                uptime: Date.now() - this.store.startedAt,
                env: process.env.APP_ENV ?? 'development',
                httpEntries: this.store.httpEntries.length,
                srpcMessages: this.store.srpcMessages.length,
                srpcActiveConnections: this.store.srpcConnections.size,
                srpcDisconnected: this.store.srpcDisconnected.length
            };
        });
        // Process info
        s.registerMessageHandler('uGetProcess', async () => {
            const mem = process.memoryUsage();
            const cpu = process.cpuUsage();
            return {
                pid: process.pid,
                rss: mem.rss,
                heapTotal: mem.heapTotal,
                heapUsed: mem.heapUsed,
                external: mem.external,
                arrayBuffers: mem.arrayBuffers,
                cpuUser: cpu.user,
                cpuSystem: cpu.system,
                uptime: process.uptime(),
                nodeVersion: process.version,
                platform: process.platform,
                arch: process.arch
            };
        });
        // Environment
        s.registerMessageHandler('uGetEnv', async () => {
            const config = (0, resolver_1.getAppConfig)();
            return { jsonData: JSON.stringify(maskSecrets(config)) };
        });
        // HTTP Requests
        s.registerMessageHandler('uGetRequests', async () => {
            return { jsonData: JSON.stringify(this.store.httpEntries.toArray().reverse()) };
        });
        // Routes
        s.registerMessageHandler('uGetRoutes', async () => {
            const router = this.app.get(http_1.HttpRouter);
            const routes = router
                .getRoutes()
                .filter(__assignType(route => !route.getFullPath().startsWith('/_devconsole'), ['route', '', 'P"2!"/"']))
                .map(__assignType(route => ({
                methods: route.httpMethods,
                path: route.getFullPath(),
                controller: route.action.type === 'controller' ? route.action.controller.name : undefined,
                methodName: route.action.type === 'controller' ? route.action.methodName : undefined
            }), ['route', '', 'P"2!"/"']));
            return { routes };
        });
        // sRPC connections
        s.registerMessageHandler('uGetSrpc', async () => {
            return {
                jsonData: JSON.stringify({
                    active: Array.from(this.store.srpcConnections.values()),
                    recentDisconnections: this.store.srpcDisconnected.toArray().reverse()
                })
            };
        });
        // sRPC messages
        s.registerMessageHandler('uGetSrpcMessages', __assignType(async (_stream, data) => {
            let messages = this.store.srpcMessages.toArray();
            if (data.streamId) {
                messages = messages.filter(__assignType(m => m.streamId === data.streamId, ['m', '', 'P"2!"/"']));
            }
            return { jsonData: JSON.stringify(messages.reverse()) };
        }, ['_stream', 'data', '', 'P"2!"2""/#']));
        // Workers
        s.registerMessageHandler('uGetWorkers', async () => {
            const result = {};
            for (const [name, queue] of queue_1.WorkerQueueRegistry.registry) {
                try {
                    result[name] = await queue.getJobCounts();
                }
                catch {
                    result[name] = { error: 'Failed to fetch job counts' };
                }
            }
            return { jsonData: JSON.stringify(result) };
        });
        // Workers jobs
        s.registerMessageHandler('uGetWorkersJobs', async () => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const live = [];
            for (const [queueName, queue] of queue_1.WorkerQueueRegistry.registry) {
                try {
                    const [active, waiting, delayed] = await Promise.all([queue.getActive(), queue.getWaiting(), queue.getDelayed()]);
                    for (const job of active) {
                        live.push({
                            id: `${queueName}:${job.id}`,
                            queue: queueName,
                            queueId: job.id,
                            name: job.name,
                            data: job.data,
                            status: 'active',
                            attempt: job.attemptsMade,
                            createdAt: job.timestamp,
                            shouldExecuteAt: job.timestamp + (job.opts.delay ?? 0),
                            executedAt: job.processedOn ?? null
                        });
                    }
                    for (const job of waiting) {
                        live.push({
                            id: `${queueName}:${job.id}`,
                            queue: queueName,
                            queueId: job.id,
                            name: job.name,
                            data: job.data,
                            status: 'waiting',
                            attempt: job.attemptsMade,
                            createdAt: job.timestamp,
                            shouldExecuteAt: job.timestamp + (job.opts.delay ?? 0),
                            executedAt: null
                        });
                    }
                    for (const job of delayed) {
                        live.push({
                            id: `${queueName}:${job.id}`,
                            queue: queueName,
                            queueId: job.id,
                            name: job.name,
                            data: job.data,
                            status: 'delayed',
                            attempt: job.attemptsMade,
                            createdAt: job.timestamp,
                            shouldExecuteAt: job.timestamp + (job.opts.delay ?? 0),
                            executedAt: null
                        });
                    }
                }
                catch {
                    // queue unavailable — skip
                }
            }
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            let history = [];
            try {
                const dbProvider = this.app.get(state_1.DBProvider);
                const jobs = await dbProvider.db.query(entity_1.JobEntity).orderBy('completedAt', 'desc').limit(200).find();
                history = jobs.map(__assignType(j => ({
                    id: j.id,
                    queue: j.queue,
                    queueId: j.queueId,
                    name: j.name,
                    data: j.data,
                    status: j.status,
                    result: j.result,
                    attempt: j.attempt,
                    traceId: j.traceId,
                    createdAt: j.createdAt.getTime(),
                    shouldExecuteAt: j.shouldExecuteAt.getTime(),
                    executedAt: j.executedAt.getTime(),
                    completedAt: j.completedAt.getTime()
                }), ['j', '', 'P"2!"/"']));
            }
            catch {
                // _jobs table may not exist if recorder hasn't run
            }
            return { jsonData: JSON.stringify({ live, history }) };
        });
        // Database entities
        s.registerMessageHandler('uGetDatabaseEntities', async () => {
            try {
                const dbProvider = this.app.get(state_1.DBProvider);
                const db = dbProvider.db;
                const allEntities = db.entityRegistry.all();
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const dialect = (0, dialect_1.getDialect)(db.adapter);
                const entities = allEntities
                    .map(__assignType(entity => {
                    const reflection = type_1.ReflectionClass.from(entity);
                    const tableName = reflection.getCollectionName() || reflection.name || '';
                    const columns = reflection.getProperties().map(__assignType(p => p.name, ['p', '', 'P"2!"/"']));
                    const quotedTable = (0, dialect_1.quoteId)(dialect, tableName);
                    return { name: reflection.name ?? '', table: tableName, columns, quotedTable };
                }, ['entity', '', 'P"2!"/"']))
                    .sort(__assignType((a, b) => a.name.localeCompare(b.name), ['a', 'b', '', 'P"2!"2""/#']));
                return { entities };
            }
            catch {
                return { entities: [] };
            }
        });
        // Database query
        s.registerMessageHandler('uDatabaseQuery', __assignType(async (_stream, data) => {
            const sql = data.sql.trim();
            if (!sql) {
                return { columns: [], rows: [], rowCount: 0, error: 'Empty query' };
            }
            try {
                const dbProvider = this.app.get(state_1.DBProvider);
                const isSelect = /^\s*(select|show|describe|explain|with)\b/i.test(sql);
                if (isSelect) {
                    const rawRows = await dbProvider.db.rawQuery(sql);
                    const columns = rawRows[0] ? Object.keys(rawRows[0]) : [];
                    const rows = rawRows.map(__assignType(row => ({
                        values: columns.map(__assignType(col => (row[col] == null ? '' : String(row[col])), ['col', '', 'P"2!"/"']))
                    }), ['row', '', 'P"2!"/"']));
                    return { columns, rows, rowCount: rawRows.length };
                }
                else {
                    const result = await dbProvider.db.rawExecute(sql);
                    return { columns: [], rows: [], rowCount: 0, affectedRows: result.affectedRows };
                }
                // oxlint-disable-next-line typescript/no-explicit-any
            }
            catch (err) {
                return { columns: [], rows: [], rowCount: 0, error: err.message || String(err) };
            }
        }, ['_stream', 'data', '', 'P"2!"2""/#']));
        // Health checks
        s.registerMessageHandler('uGetHealthChecks', async () => {
            try {
                const hcSvc = this.app.get(healthcheck_service_1.HealthcheckService);
                const results = await hcSvc.checkIndividual();
                return { jsonData: JSON.stringify(results) };
            }
            catch {
                return { jsonData: JSON.stringify([]) };
            }
        });
        // Database Log
        s.registerMessageHandler('uGetDatabaseQueries', async () => {
            return { jsonData: JSON.stringify(this.store.dbQueries.toArray().reverse()) };
        });
        s.registerMessageHandler('uClearDatabaseQueries', async () => {
            this.store.clearDatabaseQueries();
            this.broadcast('db:cleared', {});
            return {};
        });
        // Clear
        s.registerMessageHandler('uClearRequests', async () => {
            this.store.clearHttpEntries();
            this.broadcast('http:cleared', {});
            return {};
        });
        s.registerMessageHandler('uClearSrpcMessages', async () => {
            this.store.clearSrpcMessages();
            this.broadcast('srpc:cleared', {});
            return {};
        });
        // Mutexes
        s.registerMessageHandler('uGetMutexes', async () => {
            return {
                jsonData: JSON.stringify({
                    active: Array.from(this.store.activeMutexes.values()),
                    history: this.store.mutexEntries.toArray().reverse()
                })
            };
        });
    }
    ensureReplContext() {
        if (!this.replInitialized) {
            (0, repl_context_1.buildReplContext)();
            this.replInitialized = true;
        }
    }
    async handleReplEval(code) {
        this.ensureReplContext();
        // Capture console output without mutating the global console object.
        // We wrap the eval in a Function that shadows `console` with a local
        // capturing instance, avoiding race conditions with concurrent requests.
        const logs = [];
        const capture = __assignType((...args) => logs.push(args.map(__assignType(a => (typeof a === 'string' ? a : (0, util_1.inspect)(a, { depth: 4, colors: false })), ['a', '', 'P"2!"/"'])).join(' ')), ['args', '', 'P#@2!"/"']);
        const localConsole = { log: capture, warn: capture, error: capture, info: capture, debug: capture };
        try {
            // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
            const fn = new Function('console', `return eval(${JSON.stringify(code)})`);
            let result = fn(localConsole);
            if (result && typeof result === 'object' && typeof result.then === 'function') {
                result = await result;
            }
            const resultStr = result === undefined ? '' : (0, util_1.inspect)(result, { depth: 4, colors: false });
            const output = [...logs, resultStr].filter(Boolean).join('\n');
            return { output };
        }
        catch (err) {
            const output = logs.length > 0 ? logs.join('\n') : '';
            const errorStr = err instanceof Error ? (err.stack ?? err.message) : String(err);
            return { output, error: errorStr };
        }
    }
    handleReplComplete(code, cursorPos) {
        this.ensureReplContext();
        const textBeforeCursor = code.slice(0, cursorPos);
        // Match expression like: identifier.chain.of.props.partialNa
        // or just: partialNa
        const exprMatch = textBeforeCursor.match(/((?:\$\$?|[a-zA-Z_]\w*)(?:\.[a-zA-Z_]\w*)*(?:\.\w*)?)$/);
        if (!exprMatch) {
            return { items: [], replaceStart: cursorPos, replaceEnd: cursorPos };
        }
        const fullExpr = exprMatch[1];
        const parts = fullExpr.split('.');
        const prefix = parts.pop() ?? '';
        const replaceStart = cursorPos - prefix.length;
        let target;
        if (parts.length === 0) {
            // Completing a top-level name — offer globals
            const items = [];
            const globals = [
                '$',
                '$$',
                'console',
                'process',
                'require',
                'global',
                'setTimeout',
                'setInterval',
                'Promise',
                'JSON',
                'Math',
                'Date',
                'Array',
                'Object',
                'String',
                'Number',
                'Boolean',
                'Map',
                'Set',
                'RegExp',
                'Error',
                'Buffer',
                'parseInt',
                'parseFloat',
                'undefined',
                'null',
                'true',
                'false',
                'async',
                'await'
            ];
            for (const name of globals) {
                if (name.startsWith(prefix)) {
                    items.push({ label: name, kind: 'global' });
                }
            }
            return { items: items.slice(0, 50), replaceStart, replaceEnd: cursorPos };
        }
        // Resolve the object chain before the final dot
        try {
            // eslint-disable-next-line no-eval
            target = eval(parts.join('.'));
        }
        catch {
            return { items: [], replaceStart, replaceEnd: cursorPos };
        }
        if (target == null) {
            return { items: [], replaceStart, replaceEnd: cursorPos };
        }
        const items = collectProperties(target, prefix);
        return { items: items.slice(0, 50), replaceStart, replaceEnd: cursorPos };
    }
    broadcast(type, data) {
        const jsonData = JSON.stringify(data);
        for (const stream of this.server.streamsById.values()) {
            this.server.invoke(stream, 'dEvent', { type, jsonData }).catch(() => {
                // fire-and-forget: ignore errors from individual clients
            });
        }
    }
    close() {
        this.server.close();
    }
}
exports.DevConsoleSrpcServer = DevConsoleSrpcServer;
DevConsoleSrpcServer.__type = [() => __ΩSrpcMeta, () => __ΩDCClientMsg, () => __ΩDCServerMsg, () => SrpcServer_1.SrpcServer, 'server', 'replInitialized', function () { return false; }, () => __ΩScopedLogger, 'logger', 'constructor', 'registerHandlers', 'ensureReplContext', 'code', 'output', 'error', 'handleReplEval', 'cursorPos', () => __ΩUReplCompleteItem, 'items', 'replaceStart', 'replaceEnd', 'handleReplComplete', 'type', 'data', 'broadcast', 'close', 'DevConsoleSrpcServer', 'Pn!n"n#7$3%;)3&;>\'!!Pn(2)"0*P"0+;P"0,;P&2-P&4.&4/8M`00;P&2-\'21Pn2F43\'44\'45M06;P&27#28"09P"0:5w;'];
function collectProperties(obj, prefix) {
    const seen = (Set.Ω = [['&']], new Set());
    const items = [];
    let current = obj;
    while (current != null) {
        for (const name of Object.getOwnPropertyNames(current)) {
            if (name.startsWith(prefix) && !seen.has(name) && !name.startsWith('__')) {
                seen.add(name);
                let kind = 'property';
                try {
                    const desc = Object.getOwnPropertyDescriptor(current, name);
                    if (desc && typeof desc.value === 'function') {
                        kind = 'method';
                    }
                    else if (desc && (desc.get || desc.set)) {
                        kind = 'accessor';
                    }
                }
                catch {
                    // ignore
                }
                items.push({ label: name, kind });
            }
        }
        current = Object.getPrototypeOf(current);
        // Stop at Object.prototype to avoid noise
        if (current === Object.prototype)
            break;
    }
    // Sort: exact prefix match first, then alphabetical
    items.sort(__assignType((a, b) => a.label.localeCompare(b.label), ['a', 'b', '', 'P"2!"2""/#']));
    return items;
}
collectProperties.__type = ['obj', 'prefix', () => __ΩUReplCompleteItem, 'collectProperties', 'P#2!&2"n#F/$'];
//# sourceMappingURL=devconsole.ws.js.map