import {
    dirname, join, basename, relative, extname
} from 'path';
import {
    compatESModuleRequire,
    resolve,
    winPath,
    pkgUp,
    lodash
} from '@fesjs/utils';

import { PluginType } from '../enums';

const RE = {
    [PluginType.plugin]: /^(@fesjs\/|@webank\/fes-|fes-)plugin-/,
    [PluginType.preset]: /^(@fesjs\/|@webank\/fes-|fes-)preset-/
};

export function isPluginOrPreset(type, name) {
    const hasScope = name.charAt(0) === '@';
    const re = RE[type];
    if (hasScope) {
        return re.test(name.split('/')[1]) || re.test(name);
    }
    return re.test(name);
}

export function getPluginsOrPresets(type, opts) {
    const upperCaseType = type.toUpperCase();
    return [
        // dependencies
        // opts
        ...((opts[type === PluginType.preset ? 'presets' : 'plugins']) || []),
        // env
        ...(process.env[`FES_${upperCaseType}S`] || '').split(',').filter(Boolean),
        ...Object.keys(opts.pkg.devDependencies || {})
            .concat(Object.keys(opts.pkg.dependencies || {}))
            .filter(isPluginOrPreset.bind(null, type)),
        // user config
        ...((opts[
            type === PluginType.preset ? 'userConfigPresets' : 'userConfigPlugins'
        ]) || [])
    ].map(path => resolve.sync(path, {
        basedir: opts.cwd,
        extensions: ['.js', '.ts']
    }));
}

// e.g.
// initial-state -> initialState
// webpack.css-loader -> webpack.cssLoader
function nameToKey(name) {
    return name
        .split('.')
        .map(part => lodash.camelCase(part))
        .join('.');
}

function pkgNameToKey(pkgName, type) {
    if (pkgName.charAt(0) === '@' && !pkgName.startsWith('@fesjs/')) {
        pkgName = pkgName.split('/')[1];
    }
    return nameToKey(pkgName.replace(RE[type], ''));
}

export function pathToObj({ path, type, cwd }) {
    let pkg = null;
    let isPkgPlugin = false;
    const pkgJSONPath = pkgUp.sync({ cwd: path });
    if (pkgJSONPath) {
        // eslint-disable-next-line
        pkg = require(pkgJSONPath);
        isPkgPlugin = winPath(join(dirname(pkgJSONPath), pkg.main || 'index.js'))
            === winPath(path);
    }

    let id;
    if (isPkgPlugin) {
        id = pkg.name;
    } else if (winPath(path).startsWith(winPath(cwd))) {
        id = `./${winPath(relative(cwd, path))}`;
    } else if (pkgJSONPath) {
        id = winPath(join(pkg.name, relative(dirname(pkgJSONPath), path)));
    } else {
        id = winPath(path);
    }
    id = id.replace('@fesjs/preset-built-in/lib/plugins', '@@');
    id = id.replace(/\.js$/, '');

    const key = isPkgPlugin
        ? pkgNameToKey(pkg.name, type)
        : nameToKey(basename(path, extname(path)));

    return {
        id,
        key,
        path: winPath(path),
        apply() {
            // use function to delay require
            try {
                // eslint-disable-next-line
                const ret = require(path);
                // use the default member for es modules
                return compatESModuleRequire(ret);
            } catch (e) {
                throw new Error(`Register ${path} failed, since ${e.message}`);
            }
        },
        defaultConfig: null
    };
}

export function resolvePresets(opts) {
    const type = PluginType.preset;
    const presets = [...getPluginsOrPresets(type, opts)];
    return presets.map(path => pathToObj({
        type,
        path,
        cwd: opts.cwd
    }));
}

export function resolvePlugins(opts) {
    const type = PluginType.plugin;
    const plugins = getPluginsOrPresets(type, opts);
    return plugins.map(path => pathToObj({
        path,
        type,
        cwd: opts.cwd
    }));
}

export function isValidPlugin(plugin) {
    return plugin.id && plugin.key && plugin.apply;
}
