#!/usr/bin/env node

const path = require('path');
const program = require('commander');
const fs = require('fs');
const fse = require('fs-extra');
const babel = require('@babel/core');

program
    .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
    .option('--clean', 'clear the lib folder before building')
    .parse(process.argv);

// the various important paths
const paths = {
    main: path.resolve(__dirname, '..'),
    core: path.resolve(__dirname, '..', 'core'),
    vendor: path.resolve(__dirname, '..', 'vendor'),
    libDirBase: path.resolve(__dirname, '..', 'lib'),
};

// util.promisify requires Node.js 8.x, so we have our own
function promisify(original) {
    return function promiseWrap() {
        const args = Array.prototype.slice.call(arguments);
        return new Promise((resolve, reject) => {
            original.apply(this, args.concat((err, value) => {
                if (err) return reject(err);
                resolve(value);
            }));
        });
    };
}

const writeFile = promisify(fs.writeFile);

const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat);

const ensureDir = promisify(fse.ensureDir);

const babelTransformFile = promisify(babel.transformFile);

// walkDir *recursively* walks directories trees,
// calling the callback for all normal files found.
function walkDir(basePath, cb, filter) {
    return readdir(basePath)
        .then((files) => {
            const paths = files.map(filename => path.join(basePath, filename));
            return Promise.all(paths.map(filepath => lstat(filepath)
                .then((stats) => {
                    if (filter !== undefined && !filter(filepath, stats)) return;

                    if (stats.isSymbolicLink()) return;
                    if (stats.isFile()) return cb(filepath);
                    if (stats.isDirectory()) return walkDir(filepath, cb, filter);
                })));
        });
}

function makeLibFiles(sourceMaps) {
    // NB: we need to make a copy of babelOpts, since babel sets some defaults on it
    const babelOpts = () => ({
        plugins: [],
        presets: [
            [ '@babel/preset-env',
              { modules: 'commonjs' } ]
        ],
        ast: false,
        sourceMaps: sourceMaps,
    });

    fse.ensureDirSync(paths.libDirBase);

    const outFiles = [];

    const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve()
        .then(() => {
            const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename));

            if (path.extname(filename) !== '.js') {
                return;  // skip non-javascript files
            }
            return Promise.resolve()
                .then(() => ensureDir(path.dirname(outPath)))
                .then(() => {
                    const opts = babelOpts();
            // Adjust for the fact that we move the core files relative
            // to the vendor directory
                    if (vendorRewrite) {
                        opts.plugins.push(["import-redirect",
                                           {"root": paths.libDirBase,
                                            "redirect": { "vendor/(.+)": "./vendor/$1"}}]);
                    }

                    return babelTransformFile(filename, opts)
                        .then((res) => {
                            console.log(`Writing ${outPath}`);
                            const {map} = res;
                            let {code} = res;
                            if (sourceMaps === true) {
                    // append URL for external source map
                                code += `\n//# sourceMappingURL=${path.basename(outPath)}.map\n`;
                            }
                            outFiles.push(`${outPath}`);
                            return writeFile(outPath, code)
                                .then(() => {
                                    if (sourceMaps === true || sourceMaps === 'both') {
                                        console.log(`  and ${outPath}.map`);
                                        outFiles.push(`${outPath}.map`);
                                        return writeFile(`${outPath}.map`, JSON.stringify(map));
                                    }
                                });
                        });
                });
        });

    Promise.resolve()
        .then(() => {
            const handler = handleDir.bind(null, false, paths.main);
            return walkDir(paths.vendor, handler);
        })
        .then(() => {
            const handler = handleDir.bind(null, true, paths.core);
            return walkDir(paths.core, handler);
        })
        .catch((err) => {
            console.error(`Failure converting modules: ${err}`);
            process.exit(1);
        });
}

let options = program.opts();

if (options.clean) {
    console.log(`Removing ${paths.libDirBase}`);
    fse.removeSync(paths.libDirBase);
}

makeLibFiles(options.withSourceMaps);
