#!/usr/bin/env node
import { start } from 'node:repl';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { format as _format, inspect as _inspect, parseArgs } from 'node:util';
// let's try if the following message on old node causes test failure on test262.fyi
// "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time"
// import packageJson from '../../package.json' with { type: 'json' };
import { createRequire } from 'node:module';
import { createConsole } from "../inspector/utils.mjs";
import { loadImportedModuleSync } from "./module.mjs";
import { setSurroundingAgent, FEATURES, inspect, Value, Completion, AbruptCompletion, evalQ, Agent, ManagedRealm, Throw, skipDebugger, createTest262Intrinsics, surroundingAgent, ThrowCompletion, ValueOfNormalCompletion, ScriptEvaluation, } from '#self';
const packageJson = createRequire(import.meta.url)('../../package.json');
const help = `
engine262 v${packageJson.version}

Usage:

    engine262 [options]
    engine262 [options] [input file]
    engine262 [input file]

Options:

    -h, --help      Show help (this screen)
    -m, --module    Evaluate contents of input-file as a module.
    -e, --eval      Evaluate the given string.
    --features=...  A comma separated list of features.
    --features=all  Enable all features.
    --list-features List available features.
    --no-test262    Do not expose $ and $262 for test262.
    --no-inspector  Do not attach an inspector.
    --no-preview    Do not enable preview in the inspector.
`;
const argv = parseArgs({
    args: process.argv.slice(2),
    allowPositionals: true,
    allowNegative: true,
    strict: true,
    options: {
        'help': { type: 'boolean', short: 'h' },
        'eval': { type: 'string', short: 'e' },
        'module': { type: 'boolean', short: 'm' },
        'features': { type: 'string' },
        'list-features': { type: 'boolean' },
        'inspector': { type: 'boolean' },
        'test262': { type: 'boolean', default: true },
        // hidden options
        'preview-debug': { type: 'boolean' },
    },
});
if (argv.values.help) {
    process.stdout.write(help);
    process.exit(0);
}
else if (argv.values['list-features']) {
    let nameLength = 0;
    let flagLength = 0;
    FEATURES.forEach((f) => {
        if (f.name.length > nameLength) {
            nameLength = f.name.length;
        }
        if (f.flag.length > flagLength) {
            flagLength = f.flag.length;
        }
    });
    const log = (f, n, u) => {
        process.stdout.write(`${f.padEnd(flagLength, ' ')} ${n.padEnd(nameLength, ' ')} ${u}\n`);
    };
    log('flag', 'name', 'url');
    log('----', '----', '---');
    FEATURES.forEach((f) => {
        log(f.flag, f.name, f.url);
    });
    process.exit(0);
}
let features;
if (argv.values.features === 'all') {
    features = FEATURES.map((f) => f.flag);
}
else if (argv.values.features) {
    features = argv.values.features.split(',');
}
else {
    features = [];
}
const agent = new Agent({
    features,
    supportedImportAttributes: ['type'],
    loadImportedModule: loadImportedModuleSync,
});
setSurroundingAgent(agent);
const realm = new ManagedRealm({ resolverCache: new Map() });
// Define console.log
{
    const format = (function* format(args) {
        const str = [];
        for (const arg of args) {
            // TODO: inspect should return a PlainEvaluator so debugger can hook in.
            str.push(inspect(arg));
        }
        return str.join(' ');
    });
    createConsole(realm, {
        *log(args) {
            process.stdout.write(`${yield* format(args)}\n`);
        },
        *error(args) {
            process.stderr.write(`${yield* format(args)}\n`);
        },
        *debug(args) {
            process.stderr.write(`${yield* format(args)}\n`);
        },
    });
}
if (argv.values.test262) {
    createTest262Intrinsics(realm, argv.values.test262);
}
let inspector;
if (argv.values.inspector !== false) {
    let has_ws = false;
    try {
        await import('ws');
        has_ws = true;
    }
    catch {
        if (argv.values.inspector === true) {
            process.stderr.write('--inspector requires the "ws" package to be installed.\n');
            process.exit(1);
        }
    }
    if (has_ws) {
        const { NodeWebsocketInspector } = await import("./inspector.mjs");
        inspector = await NodeWebsocketInspector.new();
        inspector.attachAgent(surroundingAgent, [realm]);
        inspector.preference.previewDebug = argv.values['preview-debug'] || false;
    }
}
function oneShotEval(source, filename) {
    realm.scope(() => {
        const completion = evalQ((Q) => {
            if (argv.values.module || filename.endsWith('.mjs')) {
                const module = Q(realm.createSourceTextModule(filename, source));
                realm.HostDefined.resolverCache?.set(filename, module);
                const load = Q(module.LoadRequestedModules());
                if (load.PromiseState === 'rejected') {
                    Q(Throw(load.PromiseResult, 'Raw', load.PromiseResult));
                }
                else if (load.PromiseState === 'pending') {
                    throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
                }
                Q(module.Link());
                const evaluate = Q(skipDebugger(module.Evaluate()));
                if (evaluate.PromiseState === 'rejected') {
                    Q(Throw(evaluate.PromiseResult, 'Raw', evaluate.PromiseResult));
                }
            }
            else {
                Q(realm.evaluateScript(source, { specifier: filename }));
            }
        });
        if (completion instanceof AbruptCompletion) {
            const inspected = inspect(completion);
            process.stderr.write(`${inspected}\n`);
            process.exit(1);
        }
    });
    inspector?.stop();
}
if (argv.positionals[0]) {
    const source = readFileSync(argv.positionals[0], 'utf8');
    oneShotEval(source, resolve(argv.positionals[0]));
}
else if (!process.stdin.isTTY) {
    process.stdin.setEncoding('utf8');
    let source = '';
    process.stdin.on('data', (data) => {
        source += data;
    });
    process.stdin.once('end', () => {
        oneShotEval(source, process.cwd());
    });
}
else if (argv.values.eval) {
    oneShotEval(argv.values.eval, process.cwd());
}
else {
    process.stdout.write(`${packageJson.name} v${String(packageJson.version).replace('0.0.1-', '')}
Type ".help" for more information. Please report bugs to ${packageJson.bugs.url}
`);
    const server = start({
        prompt: '> ',
        eval: (cmd, _context, _filename, callback) => {
            try {
                const script = realm.compileScript(cmd, {});
                if (script instanceof ThrowCompletion) {
                    callback(null, script);
                    return;
                }
                let c;
                surroundingAgent.evaluate(ScriptEvaluation(ValueOfNormalCompletion(script)), (completion) => {
                    c = completion;
                    callback(null, completion);
                });
                if (!c) {
                    surroundingAgent.resumeEvaluate();
                }
            }
            catch (e) {
                callback(e, null);
            }
        },
        preview: false,
        writer: (o) => realm.scope(() => {
            if (o instanceof Value || o instanceof Completion) {
                return inspect(o);
            }
            return _inspect(o);
        }),
    });
    server.on('exit', () => inspector?.stop());
}
//# sourceMappingURL=bin.mjs.map