// @cerxes
import {host, Host} from "@cerxes/host";

// Observables
import {Observable} from "zen-observable/lib/Observable";
import {merge, combineLatest, zip} from "zen-observable/lib/extras";

// Rollup and plugins
import * as rollup from 'rollup';
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import json from "rollup-plugin-json";
import postcss from "rollup-plugin-postcss";
import { terser } from 'rollup-plugin-terser';

/** @type {Host} */
const srcDir = host.from("tests");
/** @type {Host} */
const distDir = host.from("public");
const assetsGlob = `**/*.@(${[
    "html",
    "otf",
    'svg',
    'ico',
    'png',
    'jpg',
    'jpeg'
].join('|')})`;
const sourcesGlob = `**/index.@(js|jsx)`;
const args = process.argv.slice(2);

const entryConfig = (entry, output)=>({
    input: srcDir.resolve(entry),
    output: {
        file: distDir.resolve(output),
        format: 'esm',
        sourcemap: true
    },
    plugins: [
        // json
        json(),
        // PostCSS-style
        postcss({
            plugins: [],
            inject: false,
            minimize: !!args.includes('build')
        }),
        // babel
        babel(),
        // node_modules
        resolve({
            extensions: ['.mjs', '.js', '.jsx', '.json'],
        }),
        // CJS-modules
        commonjs({
            namedExports: {
                // If there were any...
            }
        }),
        // minify, but only in production
        args.includes('build') && terser(),
    ]
});


function logBundle(output, duration){
    let relativeOut = distDir.relative(output);
    console.log(relativeOut + " built in " + (duration/1000)+"s");
}

/**
 * Build the tests
 * @returns {Promise<void>}
 */
async function build(){
    // Clean host
    await host.remove(distDir.workingDirectory, {recursive: true});
    console.log("Dist cleaned!");
    
    let assetsJob = srcDir.glob(assetsGlob).then(
        matched=>Promise.all(
            matched.map(asset=>host.copy(srcDir.resolve(asset), distDir.resolve(asset)).then(()=>asset))
        )
    ).then((assets)=>{
        console.log("Assets copied");
        return assets;
    });
    
    let sourceJobs = srcDir.glob(sourcesGlob).then(
        matched=>Promise.all(
            matched.map(async (source)=>{
                let sourceStart = new Date();
                let entry = source;
                let output = distDir.resolve(source,'..','index.js');
                let rollupCfg = entryConfig(entry,output);
                return await rollup.rollup(rollupCfg).then(async (bundle)=>{
                    let written = await bundle.write(rollupCfg.output);
                    logBundle(output, (new Date()).getTime()-sourceStart.getTime());
                    return {bundle, written};
                });
            })
        )
    );
    
    // Wait for both to be done
    let [app, assets] = await Promise.all([sourceJobs, assetsJob]);
}

/**
 * Watch the app (watch asset files and automatically rebuild, this triggers development mode, as it should never be used on the server!)
 */
async function watch(){
    let lastUpdate = new Date();
    // Clean host
    await host.remove(distDir.workingDirectory, { recursive: true });
    console.log("Dist cleaned!");
    
    // Configure a WebPackage (containing build-info)
    
    // Watch sources and map them to an observable
    let sourceJobs = await srcDir.glob(sourcesGlob).then(
        matched=>matched.map((source)=>{
            let entry = source;
            let output = distDir.resolve(source,'..','index.js');
            let rollupCfg = entryConfig(entry,output);
            let sourceWatcher = rollup.watch(rollupCfg);
            return new Observable(observer => {
                sourceWatcher.on('event', event => {
                    if(event.code==='BUNDLE_END'){
                        logBundle(output, event.duration);
                        observer.next(event);
                    }else if(event.code==='FATAL'){
                        observer.complete();
                    }else if(event.code==='ERROR'){
                        console.error(event.error.toString());
                    }
                });
    
                return () =>{
                    // On unsubscription, do what?
                };
            });
        })
    );
    
    let assetsObservable = srcDir.watch().glob(assetsGlob).sync(distDir.workingDirectory).map(event=>{
        console.log("Assets synced!");
        // console.log(event.files.join('\n'));
        return event.files;
    });
    
    combineLatest(...sourceJobs, assetsObservable).subscribe(async (built)=>{
        let newUpdate = new Date();
        console.log(`Updated in ${(newUpdate.getTime()- lastUpdate.getTime())/1000}s`);
        lastUpdate = newUpdate;
    });
}

// Run!
if(args.includes('watch')){
    watch();
}else{
    build();
}

