import less from 'less';
import fs from 'fs-extra';
import pLimit from 'p-limit';
import globImport from 'glob';
import {optimize} from 'svgo';
import {promisify} from 'util';
import minimist from 'minimist';
import CleanCSS from 'clean-css';
import html from 'rollup-plugin-html';
import buble from '@rollup/plugin-buble';
import alias from '@rollup/plugin-alias';
import modify from 'rollup-plugin-modify';
import replace from '@rollup/plugin-replace';
import {basename, dirname, join} from 'path';
import {exec as execImport} from 'child_process';
import {rollup, watch as rollupWatch} from 'rollup';
import {minify as rollupMinify} from 'rollup-plugin-esbuild';

const limit = pLimit(Number(process.env.cpus || 2));

export const exec = promisify(execImport);
export const glob = promisify(globImport);
export const {pathExists, readJson} = fs;

export const banner = `/*! UIkit ${await getVersion()} | https://www.getuikit.com | (c) 2014 - ${new Date().getFullYear()} YOOtheme | MIT License */\n`;
export const validClassName = /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/;

const argv = minimist(process.argv.slice(2));

argv._.forEach(arg => {
    const tokens = arg.split('=');
    argv[tokens[0]] = tokens[1] || true;
});

export const args = argv;

export function read(file) {
    return fs.readFile(file, 'utf8');
}

export async function write(dest, data) {

    const err = await fs.writeFile(dest, data);

    if (err) {
        console.log(err);
        throw err;
    }

    await logFile(dest);

    return dest;

}

export async function logFile(file) {
    const data = await read(file);
    console.log(`${cyan(file)} ${getSize(data)}`);
}

export async function minify(file) {

    const {styles} = await limit(() => new CleanCSS({
        advanced: false,
        keepSpecialComments: 0,
        rebase: false,
        returnPromise: true
    }).minify([file]));

    await write(`${join(dirname(file), basename(file, '.css'))}.min.css`, styles);

    return styles;

}

export function renderLess(data, options) {
    return limit(async () => (await less.render(data, options)).css);
}

export async function compile(file, dest, {external, globals, name, aliases, replaces} = {}) {

    const minify = !args.nominify;
    const debug = args.d || args.debug;
    const watch = args.w || args.watch;

    name = (name || '').replace(/[^\w]/g, '_');

    const inputOptions = {
        external,
        input: file,
        plugins: [
            replace({
                preventAssignment: true,
                values: {
                    VERSION: `'${await getVersion()}'`,
                    ...replaces
                }
            }),
            alias({
                entries: {
                    'uikit-util': './src/js/util/index.js',
                    ...aliases
                }
            }),
            html({
                include: '**/*.svg',
                htmlMinifierOptions: {
                    collapseWhitespace: true
                }
            }),
            buble({namedFunctionExpressions: false}),
            modify({
                find: /(>)\\n\s+|\\n\s+(<)/,
                replace: (m, m1, m2) => `${m1 || ''} ${m2 || ''}`
            })
        ]
    };

    const outputOptions = {
        globals,
        banner,
        format: 'umd',
        amd: {id: `UIkit${name}`.toLowerCase()},
        name: `UIkit${ucfirst(name)}`,
        sourcemap: debug ? 'inline' : false
    };

    const output = [{
        ...outputOptions,
        file: `${dest}.js`
    }];

    if (minify) {
        output.push({
            ...outputOptions,
            file: `${dest}.min.js`,
            plugins: [minify ? rollupMinify() : undefined]
        });
    }

    if (!watch) {
        const bundle = await rollup(inputOptions);

        for (const options of output) {
            await limit(() => bundle.write(options));
            logFile(options.file);
        }

        await bundle.close();
    } else {

        console.log('UIkit is watching the files...');

        const watcher = rollupWatch({
            ...inputOptions,
            output
        });

        watcher.on('event', ({code, result, output, error}) => {
            if (result) {
                result.close();

            }
            if (code === 'BUNDLE_END' && output) {
                output.map(logFile);
            }
            if (error) {
                console.error(error);
            }
        });

        watcher.close();
    }

}

export async function icons(src) {

    const options = {
        plugins: [
            {
                name: 'preset-default',
                params: {
                    overrides: {
                        removeViewBox: false,
                        cleanupNumericValues: {
                            floatPrecision: 3
                        },
                        convertPathData: false,
                        convertShapeToPath: false,
                        mergePaths: false,
                        removeDimensions: false,
                        removeStyleElement: false,
                        removeScriptElement: false,
                        removeUnknownsAndDefaults: false,
                        removeUselessStrokeAndFill: false
                    }
                }
            }
        ]
    };

    const files = await glob(src, {nosort: true});
    const icons = await Promise.all(
        files.map(file =>
            limit(async () =>
                (await optimize(await read(file), options)).data
            )
        )
    );

    return JSON.stringify(files.reduce((result, file, i) => {
        result[basename(file, '.svg')] = icons[i];
        return result;
    }, {}), null, '    ');

}

export async function run(cmd, options) {
    const {stdout, stderr} = await limit(() => exec(cmd, options));

    stdout && console.log(stdout.trim());
    stderr && console.log(stderr.trim());

    return stdout;
}

export function ucfirst(str) {
    return str.length ? str.charAt(0).toUpperCase() + str.slice(1) : '';
}

export async function getVersion() {
    return (await readJson('package.json')).version;
}

export async function replaceInFile(file, fn) {
    await write(file, await fn(await read(file)));
}

function cyan(str) {
    return `\x1b[1m\x1b[36m${str}\x1b[39m\x1b[22m`;
}

function getSize(data) {
    return `${(data.length / 1024).toFixed(2)}kb`;
}
