// Node imports
import path from 'path';
import process from "process";
import { exec } from "child_process";

// Rollup plugin imports
import less from 'rollup-plugin-less';
import sourcemaps from 'rollup-plugin-sourcemaps';
import stringLoader from "rollup-plugin-string";
import jsonPlugin from "rollup-plugin-json";

// Build/dev imports
import {Buildup, assetPlugin, babelPlugin, copyPlugin, handlebarsPlugin, minifyPlugin, commonJSPlugin} from "@cerxes/buildup";

// Util imports
import readArgs from "./util/read-args";

// Control
async function build(options) {
    // Build
    let error = false, result;
    let watch = options.get("watch","w");
    let development = options.get("dev", "d");
    let server = options.get("server", "s");
    let minify = options.get("minify", "m");
    if(minify===undefined){minify = !development;}

    try {
        let settings = {
            target: "web",
            dev: development,
            minify: minify,
            es6: false //development
        };
        let builder = new Buildup(({meta: settings})=>({
            outDir: "dist/" + settings.target,
            inDir: "src",
            clean: true,
            alias: [
                ["common", "src/common"]
            ],
            sourcemap: settings.dev?"inline":true, // 'inline' tends to work more reliably in chrome than just true (which is default and should be preferred)
            plugins: [
                // Import less as strings
                less({// TODO fork/clone the less plugin, it's npm package is annoyingly transpiled (requiring us to have a babel-runtime) and it has other minor issues...
                    output: (args)=>args// This is how we avoid the .css file from being made, and it feels hacky
                }),
                // Resolve maps from node_modules
                sourcemaps(),
                // Resolve non-es6 node_module libraries
                commonJSPlugin({
                    include: /node_modules\/.*$/,
                    exclude: /.*[\/\.]es6?[\/\.].*/
                }),
            ],

            steps: (config)=>[
                [
                    { // Site Build (www, targeting browsers)
                        inDir: "src/www",
                        outDir: config.outDir + (settings.outDir? "/"+settings.outDir : "")+"/www",

                        plugins: [
                            ...config.plugins,

                            // Add ability to pass non-js files through the build-system
                            assetPlugin({
                                exclude: [/\.svg$/]
                            }),
                            // Load gcodes and svgs  as strings
                            stringLoader({
                                include: ['**/*.gcode', '**/*.svg']
                            }),
                            // Transform source files with babel (uses src/.babelrc for configuration)
                            babelPlugin({
                                "target":"source"// transform the source inputs (could leave this undefined, its the default)
                            }),
                            // Transform output files with babel for browser compatibility
                            settings.es6? null : babelPlugin({
                                "target": "bundle", // only transform the output bundle, not the source inputs
                                "presets": [
                                    [
                                        "@babel/preset-env",
                                        {
                                            "targets": {
                                                "browsers": [
                                                    "last 6 Chrome versions",
                                                    "last 1 Edge versions",
                                                    "last 2 Firefox versions",
                                                ]
                                            },
                                            "modules": false // Leave module processing to the custom transform-es-module plugin
                                        }
                                    ]
                                ],
                                "plugins": [
                                    "@babel/plugin-syntax-dynamic-import",
                                    "@cerxes/buildup/dist/babel-transform-es-modules"
                                ],
                            }),
                            // Compress output
                            settings.minify? minifyPlugin() : null,
                        ],
                        steps: (wwwConfig)=>[
                            // Serial compilation steps:
                            //   - Compile vendor bundle first (needed/used in the next chunks)
                            [
                                {
                                    // array in, single out
                                    in: [
                                        "@cerxes/cxs",
                                    ],
                                    out: "lib/vendor.js",
                                    bundle:"flat",
                                },
                            ],

                            //   - Copy polyfills
                            Buildup.scanDir(
                                "node_modules/@webcomponents/webcomponentsjs/bundles/",
                                /(webcomponents-([\-a-zA-Z]+)\.js)$/
                            ).then(wcPolyfills=>wcPolyfills.map(polyfill=>({
                                meta: polyfill,
                                in: polyfill.files,
                                out: "lib/polyfills/[1]",
                                plugins: [
                                    copyPlugin()
                                ]
                            }))),

                            //   - Compile pages
                            Buildup.scanDir(
                                wwwConfig.resolveIn("pages"), // Scan directory src/pages for files
                                /(.*)\.page\.js$/ // (...) RegEx capture-group feeds into chunk-meta step allowing us to use [1]...[n] in strings below
                            ).then(pages=>pages.map(page=>({
                                meta: page,
                                in: page.files,
                                out: "[1].page.js",
                                steps: (pageChunk)=>[
                                    {
                                        in: {
                                            alias: "[1].polyfill-loader.virtual.js", // Passing in an alias within the src/ map will allow babel to find the appropriate .babelrc to transpile it
                                            content: `import {loadPolyfills} from "pages/polyfill-loader"; loadPolyfills();`
                                        },
                                        out: "[1].loader.virtual.js",
                                        emit: false,
                                        steps: (polyloaderChunk)=>({
                                            in: {
                                                alias: "[1].loader.virtual.js", // Passing in an alias within the src/ map will allow babel to find the appropriate .babelrc to transpile it
                                                content: `import {loadPage} from "pages/page-loader"; import(\`${"./" + path.basename(pageChunk.outPath)}\`).then(module=>loadPage(module));` // Loader-code
                                            },
                                            out: "[1].loader.virtual.js",
                                            emit: false,
                                            steps: (loaderChunk)=>[
                                                {
                                                    // Feed the loader as input to html-generator
                                                    in: "page.hbs",
                                                    plugins: [handlebarsPlugin({
                                                        context: {
                                                            polyfill: polyloaderChunk.compiled.code,
                                                            pageCode: loaderChunk.compiled.code,
                                                            es6: settings.es6
                                                        }
                                                    })],
                                                    out: "[1].html"
                                                }
                                            ]
                                        })
                                    }
                                ]
                            }))),

                            //  - Copy assets
                            Buildup.scanDir(
                                wwwConfig.resolveIn(""), // Scan directory src/pages for files
                                /(.*)\.(eot|svg|ttf|woff|png|jpg)$/ // (...) RegEx capture-group feeds into chunk-meta step allowing us to use [1]...[n] in strings below
                            ).then(assets=>assets.map(asset=>({
                                meta: asset,
                                in: asset.files[0],
                                out: "[1].[2]",
                                asset: true
                            })))
                        ]
                    }
                ],
                [
                    { // Server Build (targetting node-environment)
                        inDir: "src/server",
                        outDir: config.outDir + (settings.outDir? "/"+settings.outDir : ""),

                        plugins: [
                            jsonPlugin({
                                // for tree-shaking, properties will be declared as
                                // variables, using either `var` or `const`
                                preferConst: true, // Default: false
                            }),
                            ...config.plugins,
                            // Transform source files with babel (uses src/.babelrc for configuration) for nodejs compatibility
                            babelPlugin({
                                "target":"source"// transform the source inputs (could leave this undefined, its the default)
                            }),
                            // Transform output files with babel for nodejs compatibility
                            babelPlugin({
                                "target": "bundle", // only transform the output bundle, not the source inputs
                                "presets": [
                                    ["@babel/preset-env", {
                                        "targets": {
                                            "node": "current"
                                        },
                                    }]
                                ],
                            }),
                            // Compress output
                            settings.minify? minifyPlugin() : null,
                        ],

                        nodeResolve: {
                            browser: false,
                            external: ({importee, importer, resolved, pkg})=> pkg.builtin||(pkg&&pkg.name==='mongodb')// Flag builtins as external
                        },

                        steps: (serverConfig)=>[
                            [
                                {
                                    in: "main.js",
                                    out: "main.js",
                                    bundle:"flat",
                                },
                            ],
                        ]
                    }
                ],
            ]
        }));
        result = await builder.run(settings, {
                watch: watch
            }
        );
    }catch(err){
        error = true;
        console.error(err);
    }

    // Start server?
    if(result&&server) {
        console.log("Starting server...");
        let started = exec("node \"./main.js\"",{
            cwd: './dist/web'
        });

        started?.stdout.on('data', (data)=>{
            console.log(data.toString());
        });

        started?.stderr.on('data', (data)=>{
            console.error(data.toString());
        });
    }

    if(watch) {
        // If in watch/dev-server mode, enter this crappy infinite loop (TODO?)
        let done = false;
        let interval = 500;
        while (!done) {
            let msg = await new Promise((resolve) => setTimeout(() => resolve(error ? "KILL" : "TICK"), interval));
            if (msg === "KILL") {
                done = true;
            }
        }
    }
    return error;
}

// Start
try {
    let args = process.argv.slice(2);
    let options = readArgs(args);
    build(options).catch(ex=>{
        console.error(ex);
    });
}catch(ex){
    console.error(ex);
}
