/**
 * @copy 该文件代码大部分出自 umi，有需要请参考：
 * https://github.com/umijs/umi/blob/master/packages/preset-built-in/src/plugins/commands/buildDevUtils.ts
 */

import { join, resolve } from 'path';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { rimraf, chalk } from '@fesjs/utils';
import zlib from 'zlib';
import getConfig from './webpackConfig';

export async function getBundleAndConfigs({
    api
}) {
    // get config
    const env = api.env === 'production' ? 'production' : 'development';
    const getConfigOpts = await api.applyPlugins({
        type: api.ApplyPluginsType.modify,
        key: 'modifyBundleConfigOpts',
        initialValue: {
            cwd: api.paths.cwd,
            config: api.config,
            env,
            entry: {
                index: join(api.paths.absTmpPath, 'fes.js')
            },
            // @ts-ignore
            async modifyBabelOpts(opts) {
                return api.applyPlugins({
                    type: api.ApplyPluginsType.modify,
                    key: 'modifyBabelOpts',
                    initialValue: opts
                });
            },
            async modifyBabelPresetOpts(opts) {
                return api.applyPlugins({
                    type: api.ApplyPluginsType.modify,
                    key: 'modifyBabelPresetOpts',
                    initialValue: opts
                });
            },
            async chainWebpack(webpackConfig, opts) {
                return api.applyPlugins({
                    type: api.ApplyPluginsType.modify,
                    key: 'chainWebpack',
                    initialValue: webpackConfig,
                    args: {
                        ...opts
                    }
                });
            },
            async headScripts() {
                return api.applyPlugins({
                    key: 'addHTMLHeadScripts',
                    type: api.ApplyPluginsType.add,
                    initialState: []
                });
            },
            publicPath: await api.applyPlugins({
                key: 'modifyPublicPathStr',
                type: api.ApplyPluginsType.modify,
                initialValue: api.config.publicPath || '',
                args: {
                }
            })

        },
        args: {
        }
    });

    const bundleConfig = await api.applyPlugins({
        type: api.ApplyPluginsType.modify,
        key: 'modifyBundleConfig',
        initialValue: await getConfig({ api, ...getConfigOpts }),
        args: {
        }
    });

    return { bundleConfig };
}

export function cleanTmpPathExceptCache({
    absTmpPath
}) {
    if (!existsSync(absTmpPath)) return;
    readdirSync(absTmpPath).forEach((file) => {
        if (file === '.cache') return;
        rimraf.sync(join(absTmpPath, file));
    });
}

// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 1.8 * 1024 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1 * 1024 * 1024;

export function printFileSizes(stats, dir) {
    const ui = require('cliui')({ width: 80 });
    const json = stats.toJson({
        hash: false,
        modules: false,
        chunks: false
    });

    const filesize = (bytes) => {
        bytes = Math.abs(bytes);
        const radix = 1024;
        const unit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        let loop = 0;

        // calculate
        while (bytes >= radix) {
            bytes /= radix;
            ++loop;
        }
        return `${bytes.toFixed(1)} ${unit[loop]}`;
    };

    const assets = json.assets
        ? json.assets
        : json?.children?.reduce((acc, child) => acc.concat(child?.assets), []);

    const seenNames = new Map();
    const isJS = val => /\.js$/.test(val);
    const isCSS = val => /\.css$/.test(val);

    const orderedAssets = assets.map((a) => {
        a.name = a.name.split('?')[0];
        // These sizes are pretty large
        const isMainBundle = a.name.indexOf('fes.') === 0;
        const maxRecommendedSize = isMainBundle
            ? WARN_AFTER_BUNDLE_GZIP_SIZE
            : WARN_AFTER_CHUNK_GZIP_SIZE;
        const isLarge = maxRecommendedSize && a.size > maxRecommendedSize;
        return {
            ...a,
            suggested: isLarge && isJS(a.name)
        };
    })
        .filter((a) => {
            if (seenNames.has(a.name)) {
                return false;
            }
            seenNames.set(a.name, true);
            return isJS(a.name) || isCSS(a.name);
        })
        .sort((a, b) => {
            if (isJS(a.name) && isCSS(b.name)) return -1;
            if (isCSS(a.name) && isJS(b.name)) return 1;
            return b.size - a.size;
        });

    function getGzippedSize(asset) {
        const filepath = resolve(join(dir, asset.name));
        if (existsSync(filepath)) {
            const buffer = readFileSync(filepath);
            return filesize(zlib.gzipSync(buffer).length);
        }
        return filesize(0);
    }

    function makeRow(a, b, c) {
        return ` ${a}\t      ${b}\t ${c}`;
    }

    ui.div(
        `${makeRow(
            chalk.cyan.bold('File'),
            chalk.cyan.bold('Size'),
            chalk.cyan.bold('Gzipped')
        )
        }\n\n${
            // eslint-disable-next-line
            orderedAssets.map(asset => makeRow(/js$/.test(asset.name) ? (asset.suggested ? chalk.yellow(join(dir, asset.name)) : chalk.green(join(dir, asset.name))) : chalk.blue(join(dir, asset.name)),
                filesize(asset.size),
                getGzippedSize(asset)))
                .join('\n')}`
    );


    console.log(
        `${ui.toString()}\n\n  ${chalk.gray(
            'Images and other types of assets omitted.'
        )}\n`
    );

    if (orderedAssets?.some(asset => asset.suggested)) {
    // We'll warn for bundles exceeding them.
    // TODO: use umi docs
        console.log();
        console.log(
            chalk.yellow('The bundle size is significantly larger than recommended.')
        );
        console.log(
            chalk.yellow(
                'Consider reducing it with code splitting'
            )
        );
        console.log(
            chalk.yellow(
                'You can also analyze the project dependencies using ANALYZE=1'
            )
        );
        console.log();
    }
}
