import repl from 'node:repl';
import fs from 'node:fs';
import path from 'node:path';
import { glob } from 'glob';
import { set } from 'moderndash';
const REPL_PROP = '$$repl$$';
export function serviceRepl(app, codepath, onExit) {
    class FakeReq {
        path;
        locals = { app };
        headers = {};
        query = new URLSearchParams();
        body = {};
        constructor(path) {
            this.path = path;
            this.locals.app = app;
        }
    }
    const rl = repl.start({
        prompt: '> ',
    });
    Object.assign(rl.context, app.locals, {
        app,
        req: new FakeReq('/'),
        dump(o) {
            console.log(JSON.stringify(o, null, '\t'));
        },
        // Use iTerm2's escape code to copy to clipboard
        pbcopy(str) {
            const encoded = Buffer.from(str.toString(), 'utf8').toString('base64');
            process.stdout.write(`\x1b]52;c;${encoded}\x07`);
        },
    });
    rl.setupHistory(path.resolve('.node_repl_history'), (err) => {
        if (err) {
            // eslint-disable-next-line no-console
            console.error('History setup failed', err);
        }
    });
    app.locals.service.attachRepl?.(app, rl);
    loadReplFunctions(app, codepath, rl).catch((error) => {
        // eslint-disable-next-line no-console
        console.error('Failed to load REPL functions', error);
    });
    rl.on('exit', onExit);
}
async function loadReplFunctions(app, codepath, rl) {
    if (!codepath) {
        return;
    }
    const files = glob.sync(path.join(codepath, '**/*.{js,ts}'));
    for (const file of files) {
        try {
            // Read the file content as text
            const fileContent = fs.readFileSync(file, 'utf-8');
            // Check if repl$ is present, in a very rudimentary way (note built JS has close paren not open)
            if (/repl\$[()]/.test(fileContent)) {
                const module = await import(path.resolve(file));
                // Look for functions with the REPL_PROP marker
                for (const exported of Object.values(module)) {
                    if (!exported) {
                        continue;
                    }
                    if (typeof exported === 'function') {
                        const replName = exported[REPL_PROP];
                        if (replName) {
                            set(rl.context, replName, exported.bind(null, app));
                        }
                    }
                }
            }
        }
        catch (err) {
            // eslint-disable-next-line no-console
            console.error(`Failed to load REPL functions from ${file}:`, err);
        }
    }
}
/**
 * This decorator-like function can be applied to functions and the service will load and expose
 * the function when the repl is engaged.
 *
 * async function myFunction(app: MyService['App'], arg1: string, arg2: number) {
 * }
 * repl$(myFunction);
 *
 * or
 *
 * repl(myFunction, 'some.func.name');
 */
export function repl$(fn, name) {
    const functionName = name || fn.name;
    if (!functionName) {
        throw new Error('Function must have a name or a name must be provided.');
    }
    Object.defineProperty(fn, REPL_PROP, {
        enumerable: false,
        value: functionName,
    });
}
//# sourceMappingURL=repl.js.map