"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const cp = require("child_process");
const path_1 = require("path");
const sys_1 = require("./sys");
const compiler_1 = require("./compiler");
const linker_1 = require("./linker");
const tsconfig_json = require("./tsconfig.ext.json");
const chalk_1 = require("chalk");
const { cyan, magenta, red } = chalk_1.default;
const eslint_1 = require("eslint");
require("@typescript-eslint/eslint-plugin");
require("@typescript-eslint/parser");
const readline = require("readline");
function print(str) {
    console.log(str);
}
function npm_install(base_dir) {
    const npm = process.platform != "win32" ? "npm" : "npm.cmd";
    const { status } = cp.spawnSync(npm, ["install"], { stdio: "inherit", cwd: base_dir });
    if (status != null && status != 0) {
        print(`${cyan("npm install")} failed with exit code ${red(`${status}`)}.`);
        process.exit(status);
    }
}
function is_up_to_date(base_dir, file, metadata) {
    const contents = sys_1.read(path_1.join(base_dir, file));
    if (contents == null)
        return false;
    const old_hash = metadata.signatures[file];
    if (old_hash == null)
        return false;
    const new_hash = sys_1.hash(contents);
    return old_hash == new_hash;
}
function needs_install(base_dir, metadata) {
    if (!sys_1.directory_exists(path_1.join(base_dir, "node_modules")))
        return `New development environment.`;
    else if (!is_up_to_date(base_dir, "package.json", metadata))
        return `package.json has changed.`;
    else if (!is_up_to_date(base_dir, "package-lock.json", metadata))
        return `package-lock.json has changed.`;
    else
        return null;
}
function lint(config_file, paths) {
    const engine = new eslint_1.CLIEngine({
        configFile: config_file,
        extensions: [".ts", ".js"],
    });
    const report = engine.executeOnFiles(paths);
    eslint_1.CLIEngine.outputFixes(report);
    const ok = report.errorCount == 0;
    if (!ok) {
        const formatter = engine.getFormatter();
        const output = formatter(report.results);
        for (const line of output.trim().split("\n"))
            print(line);
    }
    return ok;
}
function init(base_dir, _bokehjs_dir, base_setup) {
    return __awaiter(this, void 0, void 0, function* () {
        print(`Working directory: ${cyan(base_dir)}`);
        const setup = {
            interactive: !!base_setup.interactive,
            bokehjs_version: base_setup.bokehjs_version != null ? base_setup.bokehjs_version : base_setup.bokeh_version.split("-")[0],
            bokeh_version: base_setup.bokeh_version,
        };
        const paths = {
            bokeh_ext: path_1.join(base_dir, "bokeh.ext.json"),
            package: path_1.join(base_dir, "package.json"),
            package_lock: path_1.join(base_dir, "package-lock.json"),
            tsconfig: path_1.join(base_dir, "tsconfig.json"),
            index: path_1.join(base_dir, "index.ts"),
        };
        const is_extension = sys_1.file_exists(paths.bokeh_ext);
        if (is_extension) {
            print("Already a bokeh extension. Quitting.");
            return false;
        }
        function write_json(path, json) {
            sys_1.write(path, JSON.stringify(json, undefined, 2));
            print(`Wrote ${cyan(path)}`);
        }
        const bokeh_ext_json = {};
        write_json(paths.bokeh_ext, bokeh_ext_json);
        const package_json = {
            name: path_1.basename(base_dir),
            version: "0.0.1",
            description: "",
            license: "BSD-3-Clause",
            keywords: [],
            repository: {},
            dependencies: {
                bokehjs: `^${setup.bokehjs_version}`,
            },
            devDependencies: {},
        };
        if (setup.interactive) {
            const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
            function ask(question, default_value) {
                return __awaiter(this, void 0, void 0, function* () {
                    return new Promise((resolve, _reject) => {
                        rl.question(`${question} `, (answer) => {
                            resolve(answer.length != 0 ? answer : default_value);
                        });
                    });
                });
            }
            function ask_yn(question) {
                return __awaiter(this, void 0, void 0, function* () {
                    const ret = yield ask(`${question} [y/n]`, "y");
                    switch (ret) {
                        case "y":
                            return true;
                        case "n":
                            return false;
                        default: {
                            print(`${red("Invalid input")}. Assuming no.`);
                            return false;
                        }
                    }
                });
            }
            if (yield ask_yn(`Create ${cyan("package.json")}? This will allow you to specify external dependencies.`)) {
                const { name } = package_json;
                package_json.name = yield ask(`  What's the extension's name? [${name}]`, name);
                const { version } = package_json;
                package_json.version = yield ask(`  What's the extension's version? [${version}]`, version);
                const { description } = package_json;
                package_json.description = yield ask(`  What's the extension's description? [${description}]`, description);
                write_json(paths.package, package_json);
            }
            if (yield ask_yn(`Create ${cyan("tsconfig.json")}? This will allow for customized configuration and improved IDE experience.`)) {
                write_json(paths.tsconfig, tsconfig_json);
            }
            rl.close();
        }
        else {
            write_json(paths.package, package_json);
            write_json(paths.tsconfig, tsconfig_json);
        }
        sys_1.write(paths.index, "");
        print(`Created empty ${cyan("index.ts")}. This is the entry point of your extension.`);
        const rel = path_1.relative(process.cwd(), base_dir);
        print(`You can build your extension with ${magenta(`bokeh build ${rel}`)}`);
        print("All done.");
        return true;
    });
}
exports.init = init;
function build(base_dir, bokehjs_dir, base_setup) {
    return __awaiter(this, void 0, void 0, function* () {
        print(`Working directory: ${cyan(base_dir)}`);
        const setup = {
            rebuild: !!base_setup.rebuild,
            bokeh_version: base_setup.bokeh_version,
        };
        const bokeh_ext_json_path = path_1.join(base_dir, "bokeh.ext.json");
        const is_extension = sys_1.file_exists(bokeh_ext_json_path);
        if (!is_extension) {
            print("Not a bokeh extension. Quitting.");
            return false;
        }
        const metadata_path = path_1.join(base_dir, ".bokeh");
        const metadata = (() => {
            let obj = sys_1.read_json(metadata_path);
            if (obj == null)
                obj = {};
            if (obj.signatures == null)
                obj.signatures = {};
            return obj;
        })();
        if (metadata.bokeh_version != setup.bokeh_version) {
            print("Using different version of bokeh, rebuilding from scratch.");
            setup.rebuild = true;
        }
        const package_json_path = path_1.join(base_dir, "package.json");
        const package_lock_json_path = path_1.join(base_dir, "package-lock.json");
        const is_package = sys_1.file_exists(package_json_path);
        if (!is_package) {
            print(`${cyan(package_json_path)} doesn't exist. Not a npm package.`);
        }
        else {
            if (setup.rebuild) {
                print(`Running ${cyan("npm install")}.`);
                npm_install(base_dir);
            }
            else {
                const result = needs_install(base_dir, metadata);
                if (result != null) {
                    print(`${result} Running ${cyan("npm install")}.`);
                    npm_install(base_dir);
                }
            }
        }
        const tsconfig_path = path_1.join(base_dir, "tsconfig.json");
        const tsconfig = (() => {
            const preconfigure = {
                baseUrl: base_dir,
                paths: {
                    "@bokehjs/*": [
                        path_1.join(bokehjs_dir, "js/lib/*"),
                        path_1.join(bokehjs_dir, "js/types/*"),
                    ],
                },
            };
            if (sys_1.file_exists(tsconfig_path)) {
                print(`Using ${cyan(tsconfig_path)}`);
                return compiler_1.read_tsconfig(tsconfig_path, is_package ? undefined : preconfigure);
            }
            else
                return compiler_1.parse_tsconfig(tsconfig_json, base_dir, preconfigure);
        })();
        if (compiler_1.is_failed(tsconfig)) {
            print(compiler_1.report_diagnostics(tsconfig.diagnostics).text);
            return false;
        }
        const { files, options } = tsconfig;
        const transformers = compiler_1.default_transformers(options);
        const host = compiler_1.compiler_host(new Map(), options, bokehjs_dir);
        print(`Compiling TypeScript (${magenta(files.length + " files")})`);
        const tsoutput = compiler_1.compile_files(files, options, transformers, host);
        if (compiler_1.is_failed(tsoutput)) {
            print(compiler_1.report_diagnostics(tsoutput.diagnostics).text);
            if (options.noEmitOnError)
                return false;
        }
        const lint_config = path_1.join(base_dir, "eslint.json");
        if (sys_1.file_exists(lint_config)) {
            print(`Linting sources`);
            lint(lint_config, files);
        }
        const dist_dir = path_1.join(base_dir, "dist");
        const lib_dir = options.outDir || dist_dir;
        const artifact = path_1.basename(base_dir);
        const bases = [lib_dir];
        if (is_package)
            bases.push(path_1.join(base_dir, "node_modules"));
        const linker = new linker_1.Linker({
            entries: [path_1.join(lib_dir, "index.js")],
            bases,
            cache: path_1.join(dist_dir, `${artifact}.json`),
            excluded: (dep) => dep.startsWith("@bokehjs/"),
            plugin: true,
        });
        print("Linking modules");
        if (!setup.rebuild)
            linker.load_cache();
        const bundles = linker.link();
        linker.store_cache();
        const outputs = [path_1.join(dist_dir, `${artifact}.js`)];
        const min_js = (js) => sys_1.rename(js, { ext: '.min.js' });
        function bundle(minified, outputs) {
            bundles
                .map((bundle) => bundle.assemble(minified))
                .map((artifact, i) => artifact.write(outputs[i]));
        }
        bundle(false, outputs);
        bundle(true, outputs.map(min_js));
        sys_1.write(metadata_path, JSON.stringify({
            bokeh_version: setup.bokeh_version,
            signatures: {
                "package.json": sys_1.hash_file(package_json_path),
                "package-lock.json": sys_1.hash_file(package_lock_json_path),
                "tsconfig.json": sys_1.hash_file(tsconfig_path),
            },
        }));
        print(`Output written to ${cyan(dist_dir)}`);
        print("All done.");
        return !compiler_1.is_failed(tsoutput);
    });
}
exports.build = build;
//# sourceMappingURL=build.js.map