const fs = require('fs');
const path = require('path');
const glob = require('glob');
const JSONC = require('jsonc-parser');
const assert = require('assert');

/**
 * Builds a faked resource path for production source maps in webpack.
 *
 * @param {string} packageName The name of the package where webpack is running.
 */
function monorepoResourceNameMapper(packageName) {
    /**@type {(info: {resourcePath: string}) => string} */
    const mapper = (info) => {
        const parts = [];

        // Walk backwards looking for the monorepo
        for (const part of info.resourcePath.split('/').reverse()) {
            if (part === '..' || part === 'packages') {
                break;
            }

            if (part === '.') {
                parts.push(packageName);
                break;
            }

            parts.push(part);
        }

        return parts.reverse().join('/');
    };
    return mapper;
}

/**
 * Returns the list of node_modules folders for the entire monorepo.
 *
 * @param {string} workspaceRoot
 */
function managedPaths(workspaceRoot) {
    // this function is never called, but we want to know if that ever changes. see comment in cacheConfig
    throw new Error('uh oh, this was added back upstream so it needs to be rewritten for npm workspaces');
    // eslint-disable-next-line no-unreachable
    const contents = fs.readFileSync(path.join(workspaceRoot, 'lerna.json'), 'utf-8');
    /** @type {{ packages: string[] }} */
    const data = JSON.parse(contents);
    const patterns = data.packages;

    const paths = [path.resolve(workspaceRoot, 'node_modules')];
    paths.push(
        ...patterns
            .flatMap((p) => glob.sync(p, { cwd: workspaceRoot }))
            .map((p) => path.resolve(workspaceRoot, p, 'node_modules'))
    );

    return paths;
}

/**
 * Builds a webpack caching config, given the calling module's __dirname and __filename.
 * @param {string} dirname __dirname
 * @param {string} filename __filename
 * @param {string | undefined} name name of the webpack instance, if using multiple configs
 */
function cacheConfig(dirname, filename, name = undefined) {
    // Temporarily disabled: caching breaks when switching branches,
    // after typescript compilation errors, and so on.
    if (true) {
        return undefined;
    }
    return {
        type: /** @type {'filesystem'} */ ('filesystem'),
        cacheDirectory: path.resolve(dirname, '.webpack_cache'),
        buildDependencies: {
            config: [filename],
        },
        managedPaths: managedPaths(path.resolve(dirname, '..', '..')),
        name,
    };
}

/**
 * Builds a webpack resolve alias configuration from a tsconfig.json file.
 * This is an alternative to using tsconfig-paths-webpack-plugin, which
 * has a number of unfixed bugs.
 *
 * https://github.com/dividab/tsconfig-paths/issues/143
 * https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/59
 * https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/60
 * @param {string} tsconfigPath Path to tsconfig
 */
function tsconfigResolveAliases(tsconfigPath) {
    tsconfigPath = path.resolve(tsconfigPath);
    const tsconfigDir = path.dirname(tsconfigPath);

    const tsconfig = JSONC.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
    const baseUrl = tsconfig.baseUrl;
    assert(typeof baseUrl === 'string' || baseUrl === undefined);
    const baseDir = baseUrl ? path.resolve(tsconfigDir, baseUrl) : tsconfigDir;

    const paths = tsconfig['compilerOptions']['paths'];
    assert(typeof paths === 'object');

    const endWildcard = /\/\*$/;

    return Object.fromEntries(
        Object.entries(paths).map(([from, toArr]) => {
            assert(typeof from === 'string', typeof from);
            assert(Array.isArray(toArr) && toArr.length === 1);
            let to = toArr[0];
            assert(typeof to === 'string');
            assert(endWildcard.test(from));
            assert(endWildcard.test(to));

            from = from.replace(endWildcard, '');
            to = to.replace(endWildcard, '');

            return [from, path.resolve(baseDir, to)];
        })
    );
}

module.exports = {
    monorepoResourceNameMapper,
    cacheConfig,
    tsconfigResolveAliases,
};
