const fs = require('fs');
const path = require('path');
const webpack = require('webpack');

const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const VirtualModulePlugin = require('virtual-module-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const vueLoaderConfig = require('./vue-loader.conf');
const { _root, _src, _static, _config } = require('./getBlConfig.js');
const Ajv = require('ajv');
const jsonConditionalCompilation = require('./jsonConditionalCompilation');
const DARKMODEPAGECOLOR = ['navigationBarBackgroundColor', 'navigationBarTextStyle', 'backgroundColor', 'backgroundColorTop', 'backgroundColorBottom'];
const TABBARCOLOR = ['color', 'selectedColor', 'backgroundColor', 'borderStyle', 'list'];
const TABBARLISTICON = ['iconPath', 'selectedIconPath'];
const {getLoaderAbsolutePath} = require('./utils');
const MyPlugin = require('./plugin/sapp-component-loader');

module.exports = (ROOT, mode = 'development') => {
    const SRC = path.join(ROOT, _src);
    const DIST = path.join(ROOT, _config, 'dist');
    const STATIC = path.join(ROOT, _static);

    const packageInfo = require(path.join(process.cwd(), 'package.json'));

    const assetsPath = function(_path) {
        const assetsSubDirectory = 'static';

        return path.posix.join(assetsSubDirectory, _path);
    };

        // check app.json
    const ajv = new Ajv();
    const commonProps = {
        hasNavigationBar: {
            type: 'boolean'
        },
        enableScrollEvent: {
            type: 'boolean'
        },
        navigationBarTitleText: {
            type: 'string'
        },
        navigationBarBackgroundColor: {
            type: 'string'
        },
        navigationBarTextStyle: {
            type: 'string'
        },
        backgroundColor: {
            type: 'string'
        },
        enablePullDownRefresh: {
            type: 'boolean'
        },
        onReachBottomDistance: {
            type: 'number'
        },
        disableScroll: {
            type: 'boolean'
        }
    };
    const schema = {
        required: ['pages'],
        properties: {
            pages: {
                type: 'array',
                items: {
                    type: 'object',
                    required: ['path'],
                    properties: commonProps
                }
            },
            window: {
                type: 'object',
                properties: commonProps
            },
            tabBar: {
                type: 'object',
                properties: {
                    color: {
                        type: 'string'
                    },
                    selectedColor: {
                        type: 'string'
                    },
                    backgroundColor: {
                        type: 'string'
                    },
                    borderStyle: {
                        type: 'string'
                    },
                    position: {
                        type: 'string'
                    },
                    list: {
                        type: 'array',
                        items: {
                            type: 'object',
                            required: ['pagePath', 'text'],
                            properties: {
                                pagePath: {
                                    type: 'string'
                                },
                                text: {
                                    type: 'string'
                                },
                                iconPath: {
                                    type: 'string'
                                },
                                selectedIconPath: {
                                    type: 'string'
                                }
                            }
                        }
                    }
                }
            },
            networkTimeout: {
                type: 'object',
                properties: {
                    request: {
                        type: 'number'
                    },
                    downloadFile: {
                        type: 'number'
                    }
                }
            },
            debug: {
                type: 'boolean'
            },
            navigateToMiniProgramAppIdList: {
                type: 'array',
                items: {
                    type: 'string'
                }
            },
            version: {
                type: 'string'
            },
            setRem: {
                type: ['boolean', 'number']
            }
        }
    };

        // check app.json
    let appJson = fs.readFileSync(path.join(SRC, 'app.json'), 'utf8');
    appJson = jsonConditionalCompilation({
        content: appJson,
        platform: 'bl'
    });
    appJson = JSON.parse(appJson);

    // 校验类型和必填
    const valid = ajv.validate(schema, appJson);
    if (!valid) {
        const error = ajv.errors[0];

        if (error.params.missingProperty) {
            throw new Error(`[app.json 错误] 缺失必填字段 ${error.dataPath ? error.dataPath.slice(1) + '.' : ''}${error.params.missingProperty}，详情请参考http://miniapp.bilibili.com/small-app-doc/framework/global-config/#appjson-%E9%85%8D%E7%BD%AE%E9%A1%B9%E5%88%97%E8%A1%A8`);
        } else if (error.params.type) {
            throw new Error(`[app.json 错误] ${error.dataPath.slice(1)} 字段值不合法，期望类型 ${error.params.type}，获取类型 ${typeof eval(`appJson${error.dataPath}`)}`);
        }
    }

    const appJsonPages = appJson.pages;
    const appJsonWindow = appJson.window || {};
    const nbtStyle = appJsonWindow.navigationBarTextStyle;
    const darkmode = appJson.darkmode;
    const darkConfig = appJson.darkConfig || {};
    const darkPages = darkConfig.pages || [];

    // 校验 navigationBarTextStyle 只能为 white / black
    if (nbtStyle && nbtStyle !== 'white' && nbtStyle !== 'black') {
        throw new Error(`[app.json 错误] window 中的 navigationBarTextStyle 字段值不合法，合法取值为"white/black"，详情请参考http://miniapp.bilibili.com/small-app-doc/framework/global-config/#window`);
    }
    appJsonPages.some(page => {
        if (page.navigationBarTextStyle && page.navigationBarTextStyle !== 'white' && page.navigationBarTextStyle !== 'black') {
            throw new Error(`[app.json 错误] ${page.path} 中的 navigationBarTextStyle 字段值不合法，合法取值为"white/black"，详情请参考http://miniapp.bilibili.com/small-app-doc/framework/global-config/#window`);
        }
    });

    const tabBarLink = 'http://miniapp.bilibili.com/small-app-doc/framework/global-config/#tabbar';

    // 校验 tabBar
    if (appJson.tabBar) {
        if (appJson.tabBar.position && appJson.tabBar.position !== 'bottom' && appJson.tabBar.position != 'top') {
            throw new Error(`[app.json 错误] tabBar的 position 字段值不合法，合法取值为"bottom/top"，详情请参考${tabBarLink}`);
        }

        const tabBarList = appJson.tabBar.list;

        if (tabBarList.length < 2 || tabBarList.length > 5) {
            throw new Error(`[app.json 错误] tabbar 数量应为2～5个，详情请参考${tabBarLink}`);
        }
        tabBarList.forEach(tab => {
            if (!appJsonPages.some(page => page.path === tab.pagePath)) {
                throw new Error(`[app.json 错误] tabbar 页面 ${tab.pagePath} 未在 pages 中注册，详情请参考${tabBarLink}`);
            }
        });
    }

    // darkmode 为true的时候 darkConfig format
    // darkmode 的 pages 根据ligth pages 的path 按照light pages 顺序对齐
    if(darkmode) {
        const _darkPages = Array(appJsonPages.length).fill({});
        appJsonPages.forEach((page, index) => {
            const _page = darkPages.filter(fv => fv.path == page.path) && darkPages.filter(fv => fv.path == page.path)[0];
            if(_page) {
                Object.keys(_page).forEach((key) => {
                    if(DARKMODEPAGECOLOR.indexOf(key) == -1) {
                        delete _page[key];
                    }
                });
                _page.path = page.path;
                _darkPages[index] = _page;
            } else {
                _darkPages[index] = {path: page.path};
            }
        });
        // tabBar darkmode模式的list要和light模式下list长度对齐
        const lightTabBar = appJson.tabBar || {};
        let darkTabBar = darkConfig.tabBar || {};
        const lightTabBarList = lightTabBar.list || [];
        let darkTabBarList = darkTabBar.list || [];

        const _darkTabList = [];
        lightTabBarList.forEach((page, index) => {
            let _page = darkTabBarList.filter(fv => fv.pagePath == page.pagePath) && darkTabBarList.filter(fv => fv.pagePath == page.pagePath)[0];
            if(_page) {
                Object.keys(_page).forEach((item) => {
                    if(TABBARLISTICON.indexOf(item) == -1) {
                        delete _page[item];
                    }
                });
                _page = Object.assign({}, {pagePath: page.pagePath, iconPath: page.iconPath, selectedIconPath: page.selectedIconPath}, _page);
                _page.pagePath = page.pagePath;
                _darkTabList[index] = _page;
            } else {
                console.warn('darkmode的tabbar list应该和light 的 tabbar list 保持一致');
                _darkTabList[index] = {pagePath: page.pagePath, iconPath: page.iconPath, selectedIconPath: page.selectedIconPath};
            }
        });
        darkTabBar.list = _darkTabList;

        let _window = darkConfig.window || {};
        Object.keys(_window).forEach((item) => {
            if(DARKMODEPAGECOLOR.indexOf(item) === -1) {
                delete _window[item];
            }
        });
        Object.keys(darkTabBar).forEach((item) => {
            if(TABBARCOLOR.indexOf(item) === -1) {
                delete darkTabBar[item];
            }
        });
        appJson.darkConfig = Object.assign({}, {
            window: {},
            tabBar: {}
        }, {pages: _darkPages, window: _window || {}, tabBar: darkTabBar});
    }

    // write app.json
    if (packageInfo) {
        appJson.version = packageInfo.version;
    }
    if (!fs.existsSync(DIST)) {
        fs.mkdirSync(DIST);
    }
    fs.writeFileSync(path.join(DIST, 'app.json'), JSON.stringify(appJson, null, '    '), 'utf8');

    // entries
    const appConfig = appJson;
    let entries = {};
    appConfig.pages.forEach((conf) => {
        const name = /\/pages\/(.*)/.exec(conf.path)[1];
        entries[name] = `.${_root}${_src}${conf.path}/index.js`; // ./src...
    });

    // js entries
    let entryCode = require('./page-entry');
    let virtualEntries = [];
    Object.keys(entries).forEach(name => {
        let path = entries[name];
        virtualEntries.push({
            moduleName: path,
            contents: entryCode
        });
    });

    let blConfig = { isUni: true };

    if (fs.existsSync(path.join(ROOT, 'bl.config.js'))) {
        blConfig = require(path.join(ROOT, 'bl.config.js'));
        if (blConfig.isUni === undefined) {
            blConfig.isUni = true;
        }
    }

    const configs = {
        node: {
            fs: 'empty'
        },
        mode: mode,
        entry: entries,
        output: {
            filename: 'pages/[name]/render.js',
            chunkFilename: '[name].render.js',
            path: DIST,
            publicPath: './'
        },
        module: {
            rules: [
                {
                    test: /node_modules\/three/,
                    use: [
                        {
                            loader: path.resolve(
                                __dirname,
                                "./loaders/threejs-loader"
                            ),
                        },
                    ],
                },
                {
                    test: /\.vue$/,
                    use: [

                        { loader: path.resolve(__dirname, './loaders/uni-vue-post-loader')},

                        { loader: path.resolve(__dirname, './loaders/bl-sapp-render-post-loader')},
                        {
                            loader: 'vue-loader',
                            options: vueLoaderConfig
                        },
                        { loader: path.resolve(__dirname, './loaders/sapp-component-loader/tag')}
                        // { loader: 'vue-blrender-loader' },
                        // {
                        //     loader: 'uni-vue-loader',
                        //     options: {
                        //         platform: 'bl',
                        //         isUni: blConfig.isUni
                        //     }
                        // }
                    ]
                },
                {
                    test: /\.pug$/,
                    oneOf: [
                        // 这条规则应用到 Vue 组件内的 `<template lang="pug">`
                        {
                            resourceQuery: /^\?vue/,
                            use: ['pug-plain-loader']
                        },
                        // 这条规则应用到 JavaScript 内的 pug 导入
                        {
                            use: ['raw-loader', 'pug-plain-loader']
                        }
                    ]
                },
                {
                    test: /\.json$/,
                    use: [
                        {
                            loader: 'uni-json-loader',
                            options: {
                                platform: 'bl'
                            }
                        }
                    ],
                },
                {
                    test: /\.js$/,
                    use: [
                        {
                            loader: 'babel-loader'
                        },
                        {
                            loader: 'uni-js-loader',
                            options: {
                                platform: 'bl'
                            }
                        }
                    ],
                    include: [
                        SRC,
                        path.join(
                            ROOT,
                            'node_modules',
                            'bili-small-app',
                            'runtime'
                        ),
                        path.join(
                            ROOT,
                            'node_modules',
                            'bili-small-app',
                            'components'
                        ),
                    ]
                },
                {
                    test: /\.ts$/,
                    use: [{
                        loader: 'babel-loader'
                    },
                    {
                        loader: 'uni-js-loader',
                        options: {
                            platform: 'bl'
                        }
                    }, {
                        loader: 'ts-loader',
                        options: {
                            transpileOnly: true,
                            appendTsSuffixTo: [
                                '\\.vue$'
                            ]
                        }
                    }]
                },
                {
                    test: /\.pug$/,
                    oneOf: [
                        // 这条规则应用到 Vue 组件内的 `<template lang="pug">`
                        {
                            resourceQuery: /^\?vue/,
                            use: [
                                {
                                    loader: path.resolve(__dirname, './loaders/uni-vue-post-loader')
                                },

                                'pug-plain-loader'
                            ]
                        },
                        // 这条规则应用到 JavaScript 内的 pug 导入
                        {
                            use: ['raw-loader', 'pug-plain-loader']
                        }
                    ]
                },
                {
                    test: /\.json$/,
                    use: [
                        {
                            loader: 'uni-json-loader',
                            options: {
                                platform: 'bl'
                            }
                        }
                    ],
                },
                {
                    test: /\.css$/,
                    use: [
                        'vue-style-loader',
                        { loader: path.resolve(__dirname, './loaders/sapp-component-loader')},
                        getLoaderAbsolutePath('css-loader'),
                        'postcss-loader'
                    ]
                },
                {
                    test: /\.less$/,
                    use: [
                        // { loader: path.resolve(__dirname, './loaders/bl-sapp-render-post-loader')},
                        'vue-style-loader',
                        { loader: path.resolve(__dirname, './loaders/sapp-component-loader')},
                        getLoaderAbsolutePath('css-loader'),
                        'postcss-loader',
                        'less-loader'
                    ]
                },
                {
                    test: /\.styl$/,
                    use: ['vue-style-loader', getLoaderAbsolutePath('css-loader'), 'postcss-loader', 'stylus-loader']
                },
                {
                    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                    loader: 'url-loader',
                    options: {
                        limit: 1,
                        name: assetsPath('img/[name].[hash:7].[ext]')
                    }
                },
                {
                    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: assetsPath('fonts/[name].[hash:7].[ext]')
                    }
                },
                {
                    test: /small-app\/runtime\/bl/,
                    use: [
                        { loader: 'babel-loader' },
                        {
                            loader: 'string-replace-loader',
                            options: {
                                search: '__BLJS__',
                                replace: 'var bl = {};',
                                flags: 'g'
                            }
                        }
                    ]
                }
            ]
        },
        resolve: {
            extensions: ['.js', '.vue', '.json'],
            alias: {
                '@': ROOT,
                '%': SRC
            },
            mainFields: ['main']
        },
        plugins: [
            new VueLoaderPlugin(),
            new CleanWebpackPlugin(DIST, {
                exclude: ['app.json'],
                root: ROOT,
                verbose: false
            }),
            new CopyWebpackPlugin([
                { from: path.join(STATIC, '**/*'), to: path.join(DIST) }
            ]),
            new webpack.DefinePlugin({
                'APPID': '"' + JSON.parse(fs.readFileSync(path.join(ROOT, _config, '.sapprc'), 'utf8')).appId + '"'
            }),
            ...virtualEntries.map(options => new VirtualModulePlugin(options)),
            new MyPlugin({ options: true })
        ],
        externals: {
            vue: 'Vue'
        }
    };

    if (mode === 'production') {
        configs.optimization = {
            // 生产模式下，也给模块有意义的名称代替id，因为webpack源码修改过，require里的moduleId都是路径，不是id(数字)
            namedModules: true,
            // 生产模式下，也给chunk有意义的名称代替id
            namedChunks: true,
        };
    }
    if (process.env.BL_NOMIN) {
        configs.optimization = {
            minimize: false
        };
    }
    return configs;
};
