const chalk = require("chalk");
const process = require("process");
const { pathToFileURL } = require("url");
const { testDefinitions } = require("@deno/shim-deno/test-internals");
const filePaths = [
    "config/index.test.js",
    "emver-lite/test.js",
    "test.js",
    "utils/propertiesMatcher.test.js",
];
async function main() {
    const testContext = {
        process,
        chalk,
    };
    for (const [i, filePath] of filePaths.entries()) {
        if (i > 0) {
            console.log("");
        }
        const scriptPath = "./script/" + filePath;
        console.log("Running tests in " + chalk.underline(scriptPath) + "...\n");
        process.chdir(__dirname + "/script");
        const scriptTestContext = {
            origin: pathToFileURL(filePath).toString(),
            ...testContext,
        };
        try {
            require(scriptPath);
        }
        catch (err) {
            console.error(err);
            process.exit(1);
        }
        await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), scriptTestContext);
        const esmPath = "./esm/" + filePath;
        console.log("\nRunning tests in " + chalk.underline(esmPath) + "...\n");
        process.chdir(__dirname + "/esm");
        const esmTestContext = {
            origin: pathToFileURL(filePath).toString(),
            ...testContext,
        };
        await import(esmPath);
        await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), esmTestContext);
    }
}
async function runTestDefinitions(testDefinitions, options) {
    const testFailures = [];
    for (const definition of testDefinitions) {
        options.process.stdout.write("test " + definition.name + " ...");
        if (definition.ignore) {
            options.process.stdout.write(` ${options.chalk.gray("ignored")}\n`);
            continue;
        }
        const context = getTestContext(definition, undefined);
        let pass = false;
        try {
            await definition.fn(context);
            if (context.hasFailingChild) {
                testFailures.push({
                    name: definition.name,
                    err: new Error("Had failing test step.")
                });
            }
            else {
                pass = true;
            }
        }
        catch (err) {
            testFailures.push({
                name: definition.name,
                err
            });
        }
        const testStepOutput = context.getOutput();
        if (testStepOutput.length > 0) {
            options.process.stdout.write(testStepOutput);
        }
        else {
            options.process.stdout.write(" ");
        }
        options.process.stdout.write(getStatusText(pass ? "ok" : "fail"));
        options.process.stdout.write("\n");
    }
    if (testFailures.length > 0) {
        options.process.stdout.write("\nFAILURES");
        for (const failure of testFailures) {
            options.process.stdout.write("\n\n");
            options.process.stdout.write(failure.name + "\n");
            options.process.stdout.write(indentText((failure.err?.stack ?? failure.err).toString(), 1));
        }
        options.process.exit(1);
    }
    function getTestContext(definition, parent) {
        return {
            name: definition.name,
            parent,
            origin: options.origin,
            /** @type {any} */ err: undefined,
            status: "ok",
            children: [],
            get hasFailingChild() {
                return this.children.some((c) => c.status === "fail" || c.status === "pending");
            },
            getOutput() {
                let output = "";
                if (this.parent) {
                    output += "test " + this.name + " ...";
                }
                if (this.children.length > 0) {
                    output += "\n" + this.children.map((c) => indentText(c.getOutput(), 1)).join("\n") + "\n";
                }
                else if (!this.err) {
                    output += " ";
                }
                if (this.parent && this.err) {
                    output += "\n";
                }
                if (this.err) {
                    output += indentText((this.err.stack ?? this.err).toString(), 1);
                    if (this.parent) {
                        output += "\n";
                    }
                }
                if (this.parent) {
                    output += getStatusText(this.status);
                }
                return output;
            },
            async step(nameOrTestDefinition, fn) {
                const definition = getDefinition();
                const context = getTestContext(definition, this);
                context.status = "pending";
                this.children.push(context);
                if (definition.ignore) {
                    context.status = "ignored";
                    return false;
                }
                try {
                    await definition.fn(context);
                    context.status = "ok";
                    if (context.hasFailingChild) {
                        context.status = "fail";
                        return false;
                    }
                    return true;
                }
                catch (err) {
                    context.status = "fail";
                    context.err = err;
                    return false;
                }
                /** @returns {TestDefinition} */ function getDefinition() {
                    if (typeof nameOrTestDefinition === "string") {
                        if (!(fn instanceof Function)) {
                            throw new TypeError("Expected function for second argument.");
                        }
                        return {
                            name: nameOrTestDefinition,
                            fn
                        };
                    }
                    else if (typeof nameOrTestDefinition === "object") {
                        return nameOrTestDefinition;
                    }
                    else {
                        throw new TypeError("Expected a test definition or name and function.");
                    }
                }
            }
        };
    }
    function getStatusText(status) {
        switch (status) {
            case "ok":
                return options.chalk.green(status);
            case "fail":
            case "pending":
                return options.chalk.red(status);
            case "ignored":
                return options.chalk.gray(status);
            default:
                {
                    const _assertNever = status;
                    return status;
                }
        }
    }
    function indentText(text, indentLevel) {
        if (text === undefined) {
            text = "[undefined]";
        }
        else if (text === null) {
            text = "[null]";
        }
        else {
            text = text.toString();
        }
        return text.split(/\r?\n/).map((line) => "  ".repeat(indentLevel) + line).join("\n");
    }
}
main();
