import { readdirSync, statSync, readFileSync } from 'fs';
import {
    join, extname, basename
} from 'path';
import {
    lodash, parser, generator, winPath
} from '@fesjs/utils';
import { parse } from '@vue/compiler-sfc';
import { Logger } from '@fesjs/compiler';
import { runtimePath } from '../../../utils/constants';

const logger = new Logger('fes:router');

//   pages
//  ├── index.vue         # 根路由页面 路径 /
//  ├── *.vue             # 模糊匹配 路径 *
//  ├── a.vue             # 路径 /a
//  ├── b
//  │   ├── index.vue     # 路径 /b
//  │   ├── @id.vue       # 动态路由 /b/:id
//  │   └── c.vue         # 路径 /b/c
//  └── layout.vue        # 根路由下所有page共用的外层

const isProcessFile = function (path) {
    const ext = extname(path);
    return statSync(path).isFile() && ['.vue', '.jsx', '.tsx'].includes(ext);
};

const isProcessDirectory = function (path, item) {
    const component = winPath(join(path, item));
    return statSync(component).isDirectory() && !['components'].includes(item);
};

const checkHasLayout = function (path) {
    const dirList = readdirSync(path);
    return dirList.some((item) => {
        if (!isProcessFile(winPath(join(path, item)))) {
            return false;
        }
        const ext = extname(item);
        const fileName = basename(item, ext);
        return fileName === 'layout';
    });
};

const getRouteName = function (parentRoutePath, fileName) {
    const routeName = winPath(join(parentRoutePath, fileName));
    return routeName
        .slice(1)
        .replace(/\//g, '_')
        .replace(/@/g, '_')
        .replace(/\*/g, 'FUZZYMATCH');
};

const getRoutePath = function (parentRoutePath, fileName, isFile = true) {
    // /index.vue -> /
    if (isFile && fileName === 'index') {
        fileName = '';
    }
    // /@id.vue -> /:id
    if (fileName.startsWith('@')) {
        fileName = fileName.replace(/@/, ':');
    }
    // /*.vue -> :pathMatch(.*)
    if (fileName.includes('*')) {
        fileName = fileName.replace('*', ':pathMatch(.*)');
    }
    return winPath(join(parentRoutePath, fileName));
};

function getRouteMeta(content) {
    try {
        const ast = parser.parse(content, {
            sourceType: 'module',
            plugins: ['jsx', 'typescript']
        });
        const defineRouteExpression = ast.program.body.filter(expression => expression.type === 'ExpressionStatement' && expression.expression.type === 'CallExpression' && expression.expression.callee.name === 'defineRouteMeta')[0];
        if (defineRouteExpression) {
            const argument = generator(defineRouteExpression.expression.arguments[0]);
            return JSON.parse(argument.code.replace(/'/g, '"').replace(/(\S+):/g, (global, m1) => `"${m1}":`));
        }
        return null;
    } catch (error) {
        return null;
    }
}

let cacheGenRoutes = {};

// TODO 约定 layout 目录作为布局文件夹，
const genRoutes = function (parentRoutes, path, parentRoutePath) {
    const dirList = readdirSync(path);
    const hasLayout = checkHasLayout(path);
    const layoutRoute = {
        children: []
    };
    if (hasLayout) {
        layoutRoute.path = parentRoutePath;
        parentRoutes.push(layoutRoute);
    }
    dirList.forEach((item) => {
        // 文件或者目录的绝对路径
        const filePath = winPath(join(path, item));
        if (isProcessFile(filePath)) {
            const ext = extname(item);
            const fileName = basename(item, ext);
            // 路由的path
            const routePath = getRoutePath(parentRoutePath, fileName);
            if (cacheGenRoutes[routePath]) {
                logger.warn(`[WARNING]: The file path: ${routePath}(.jsx/.tsx/.vue) conflict in router，will only use ${routePath}.tsx or ${routePath}.jsx，please remove one of.`);
                return;
            }
            cacheGenRoutes[routePath] = true;

            // 路由名称
            const routeName = getRouteName(parentRoutePath, fileName);
            const componentPath = winPath(filePath);

            const content = readFileSync(filePath, 'utf-8');
            let routeMeta = {};
            if (ext === '.vue') {
                const { descriptor } = parse(content);
                const routeMetaBlock = descriptor.customBlocks.find(
                    b => b.type === 'config'
                );
                routeMeta = routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {};
                if (descriptor.script) {
                    routeMeta = getRouteMeta(descriptor.script.content) || routeMeta;
                }
                // 优先使用 descriptor.script， 兼容 script 和 script setup 同时存在的情况
                if (descriptor.scriptSetup && lodash.isEmpty(routeMeta)) {
                    routeMeta = getRouteMeta(descriptor.scriptSetup.content) || routeMeta;
                }
            }
            if (ext === '.jsx' || ext === '.tsx') {
                routeMeta = getRouteMeta(content) || {};
            }

            const routeConfig = {
                path: routePath,
                component: componentPath,
                name: routeMeta.name || routeName,
                meta: routeMeta
            };
            if (hasLayout) {
                if (fileName === 'layout') {
                    layoutRoute.component = componentPath;
                } else {
                    layoutRoute.children.push(routeConfig);
                }
            } else {
                parentRoutes.push(routeConfig);
            }
        }
    });

    dirList.forEach((item) => {
        if (isProcessDirectory(path, item)) {
            // 文件或者目录的绝对路径
            const nextPath = winPath(join(path, item));
            const nextParentRouteUrl = getRoutePath(parentRoutePath, item, false);
            if (hasLayout) {
                genRoutes(layoutRoute.children, nextPath, nextParentRouteUrl);
            } else {
                genRoutes(parentRoutes, nextPath, nextParentRouteUrl);
            }
        }
    });
};

/**
 * 智能路由
 * 1、路由的路径是多个“/”组成的字符串，使用“/”分割后得到不同的子项
 * 2、计算子项个数，用个数乘以4，计入得分
 * 3、判断子项是否是静态的，即不包含“：”、“*”等特殊字符串，若是计入3分。
 * 4、判断子项是否是动态的，即包含“：”特殊字符，若是计入2分。
 * 5、判断子项是否是模糊匹配，即包含“*”特殊字符，若是扣除1分。
 * 6、判断子项是否是根端，即只是“/”，若是计入1分。

 * @param {*} routes
 */
const rank = function (routes) {
    routes.forEach((item) => {
        const path = item.path;
        let arr = path.split('/');
        if (arr[0] === '') {
            arr = arr.slice(1);
        }
        let count = 0;
        arr.forEach((sonPath) => {
            count += 4;
            if (sonPath.indexOf(':') !== -1 && sonPath.indexOf(':pathMatch(.*)') === -1) {
                count += 2;
            } else if (sonPath.indexOf(':pathMatch(.*)') !== -1) {
                count -= 1;
            } else if (sonPath === '') {
                count += 1;
            } else {
                count += 3;
            }
        });
        item.count = count;
        if (item.children && item.children.length) {
            rank(item.children);
        }
    });
    routes.sort((a, b) => b.count - a.count);
};

const getRoutes = function ({ config, absPagesPath }) {
    // 用户配置了routes则使用用户配置的
    const configRoutes = config.router.routes;
    if (configRoutes && configRoutes.length > 0) return configRoutes;

    const routes = [];
    cacheGenRoutes = {};
    genRoutes(routes, absPagesPath, '/');
    rank(routes);
    return routes;
};

const getRoutesJSON = function ({ routes, config }) {
    // 因为要往 routes 里加无用的信息，所以必须 deep clone 一下，避免污染
    const clonedRoutes = lodash.cloneDeep(routes);

    function isFunctionComponent(component) {
        // eslint-disable-next-line
        return (
            /^\((.+)?\)(\s+)?=>/.test(component)
            || /^function([^(]+)?\(([^)]+)?\)([^{]+)?{/.test(component)
        );
    }

    function replacer(key, value) {
        switch (key) {
            case 'component':
                if (isFunctionComponent(value)) return value;
                if (config.dynamicImport) {
                    // TODO 针对目录进行 chunk 划分，import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
                    return `() => import('${value}')`;
                }
                return `require('${value}').default`;
            default:
                return value;
        }
    }

    return JSON.stringify(clonedRoutes, replacer, 2)
        .replace(
            /"component": ("(.+?)")/g,
            (global, m1, m2) => `"component": ${m2.replace(/\^/g, '"')}`
        )
        .replace(/\\r\\n/g, '\r\n')
        .replace(/\\n/g, '\r\n');
};

export default function (api) {
    api.describe({
        key: 'router',
        config: {
            schema(joi) {
                return joi
                    .object({
                        routes: joi.array(),
                        mode: joi.string()
                    });
            },
            default: {
                mode: 'hash'
            }
        }
    });

    api.registerMethod({
        name: 'getRoutes',
        async fn() {
            return api.applyPlugins({
                key: 'modifyRoutes',
                type: api.ApplyPluginsType.modify,
                initialValue: getRoutes({
                    config: api.config,
                    absPagesPath: api.paths.absPagesPath
                })
            });
        }
    });

    api.registerMethod({
        name: 'getRoutesJSON',
        async fn() {
            const routes = await api.getRoutes();
            return getRoutesJSON({ routes, config: api.config });
        }
    });

    const {
        utils: { Mustache }
    } = api;

    const namespace = 'core/routes';

    const absCoreFilePath = join(namespace, 'routes.js');

    const absRuntimeFilePath = join(namespace, 'runtime.js');

    const historyType = {
        history: 'createWebHistory',
        hash: 'createWebHashHistory',
        memory: 'createMemoryHistory'
    };

    api.onGenerateFiles(async () => {
        const routesTpl = readFileSync(join(__dirname, 'template/routes.tpl'), 'utf-8');
        const routes = await api.getRoutesJSON();

        api.writeTmpFile({
            path: absCoreFilePath,
            content: Mustache.render(routesTpl, {
                runtimePath,
                routes,
                config: api.config,
                routerBase: api.config.base,
                CREATE_HISTORY: historyType[api.config.router.mode] || 'createWebHashHistory'
            })
        });

        api.writeTmpFile({
            path: absRuntimeFilePath,
            content: readFileSync(join(__dirname, 'template/runtime.tpl'), 'utf-8')
        });
    });

    api.addCoreExports(() => [
        {
            specifiers: ['getRoutes', 'getRouter', 'getHistory', 'destroyRouter', 'defineRouteMeta'],
            source: absCoreFilePath
        }
    ]);

    api.addRuntimePlugin(() => `@@/${absRuntimeFilePath}`);
}
