// "error-stack-parser": "2.0.1",
// "stacktrace-gps": "3.0.2"
// additional sources https://github.com/stacktracejs/stacktrace.js/blob/master/stacktrace.js#L102-L116

import fs from 'fs';
import path from 'path';
import { SourceMapConsumer } from 'source-map';
import StackTraceGPS from 'stacktrace-gps';
import ErrorStackParser from 'error-stack-parser';
import mapValues from 'lodash/mapValues';
import update from 'lodash/update';

const blackList = ['/webpack-runtime.js'];

function formatFilename(name) {
    return path.basename(name);
}

function normalizeAssetNames(assets) {
    return Object.values(assets)
        .map(i => i.js)
        .filter(Boolean)
        .filter(name => !blackList.includes(name))
        .map(formatFilename);
}

function _getSources(filenames, publicPath) {
    return filenames.reduce(function collectSources(acc, filename) {
        const resolvedPath = path.join(publicPath, filename);
        const sourceFile = fs.readFileSync(resolvedPath, 'utf-8');
        acc[filename] = sourceFile;
        return acc;
    }, {});
}

function getFileSources(filenames, publicPath) {
    return _getSources(filenames, publicPath);
}

function getSourceMapSources(filenames, publicPath) {
    const sourceMapCache = _getSources(filenames, publicPath);
    return mapValues(sourceMapCache, file => new SourceMapConsumer(file));
}

export function createSourceMapsCache(assets, publicPath) {
    const filenames = normalizeAssetNames(assets);
    const sourceCache = getFileSources(filenames, publicPath);
    const filenamesMap = filenames.map(name => `${name}.map`);
    const sourceMapConsumerCache = getSourceMapSources(filenamesMap, publicPath);

    const gps = new StackTraceGPS({
        offline: true,
        sourceCache,
        sourceMapConsumerCache
    });

    return {
        formatError(errorObject) {
            return Promise.resolve().then(function formatErrorStack() {
                const parsedErrorStack = ErrorStackParser.parse(errorObject)
                    .filter(o => o.fileName)
                    .map(obj => update(obj, 'fileName', formatFilename))
                    .map(s => gps.pinpoint(s));

                return Promise.all(parsedErrorStack).then(function formatError(stack) {
                    return {
                        name: errorObject.name,
                        message: errorObject.message,
                        stack: stack.join('\n')
                    };
                });
            });
        }
    };
}