"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = require("crypto");
const fs_1 = require("fs");
const path_1 = require("path");
const tempy_1 = require("tempy");
const typescript_1 = require("typescript");
const diagnosticToString_1 = require("./diagnosticToString");
// tslint:disable-next-line:no-var-requires
const sourceMapSupport = require('source-map-support');
const DEFAULT_COMPILER_OPTIONS = {
    sourceMap: true,
    inlineSourceMap: false,
    inlineSources: true,
    declaration: false,
    noEmit: false,
    outDir: '$$ts-transpilation$$',
};
function transpileNode(cwd, compilerOptions) {
    compilerOptions = Object.assign({}, compilerOptions, DEFAULT_COMPILER_OPTIONS);
    const cacheDirectory = tempy_1.directory();
    const originalJsHandler = require.extensions['.js'];
    const memoryCache = {
        contents: Object.create(null),
        outputs: Object.create(null),
    };
    const extensions = ['.ts', '.tsx'];
    const compile = (code, fileName) => {
        const result = typescript_1.transpileModule(code, {
            fileName,
            compilerOptions,
            reportDiagnostics: true,
        });
        const diagnosticList = result.diagnostics;
        if (diagnosticList && diagnosticList.length) {
            throw new Error(diagnosticToString_1.diagnosticsToString(diagnosticList, cwd));
        }
        return [result.outputText, result.sourceMapText];
    };
    sourceMapSupport.install({
        environment: 'node',
        retrieveFile(path) {
            return memoryCache.outputs[path];
        },
    });
    const register = {
        cwd,
        cacheDirectory,
        extensions,
        compile: readThrough(cacheDirectory, memoryCache, compile, path_1.extname),
    };
    extensions.forEach(extension => {
        registerExtension(extension, register, originalJsHandler);
    });
}
exports.transpileNode = transpileNode;
function readThrough(cachedir, memoryCache, compile, getExtension) {
    // Make sure the cache directory exists before continuing.
    return (code, fileName) => {
        const cachePath = path_1.join(cachedir, getCacheName(code, fileName));
        const extension = getExtension(fileName);
        const outputPath = `${cachePath}${extension}`;
        try {
            const contents = fs_1.readFileSync(outputPath, 'utf8');
            if (isValidCacheContent(contents)) {
                memoryCache.outputs[fileName] = contents;
                return contents;
            }
        }
        catch (err) {
            /* Ignore. */
        }
        const [value, sourceMap] = compile(code, fileName);
        const output = updateOutput(value, fileName, sourceMap, getExtension);
        memoryCache.outputs[fileName] = output;
        fs_1.writeFileSync(outputPath, output);
        return output;
    };
}
function updateOutput(outputText, fileName, sourceMap, getExtension) {
    const base64Map = Buffer.from(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64');
    const sourceMapContent = `data:application/json;charset=utf-8;base64,${base64Map}`;
    const sourceMapLength = `${path_1.basename(fileName)}.map`.length + (getExtension(fileName).length - path_1.extname(fileName).length);
    return outputText.slice(0, -sourceMapLength) + sourceMapContent;
}
function updateSourceMap(sourceMapText, fileName) {
    const sourceMap = JSON.parse(sourceMapText);
    sourceMap.file = fileName;
    sourceMap.sources = [fileName];
    delete sourceMap.sourceRoot;
    return JSON.stringify(sourceMap);
}
function isValidCacheContent(contents) {
    return /(?:9|0=|Q==)$/.test(contents.slice(-3));
}
function getCacheName(sourceCode, fileName) {
    return crypto_1.createHash('sha256')
        .update(path_1.extname(fileName), 'utf8')
        .update('\x00', 'utf8')
        .update(sourceCode, 'utf8')
        .digest('hex');
}
function registerExtension(ext, register, originalHandler) {
    const old = require.extensions[ext] || originalHandler;
    require.extensions[ext] = (m, filename) => {
        const _compile = m._compile;
        m._compile = function (code, fileName) {
            return _compile.call(this, register.compile(code, fileName), fileName);
        };
        return old(m, filename);
    };
}
//# sourceMappingURL=transpileNode.js.map