"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("../util");
function default_1(compiler, next) {
    return __awaiter(this, void 0, void 0, function* () {
        yield next();
        compiler.shims.push(util_1.wrap('' +
            "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nlet originalFsMethods = null;\nlet lazyRestoreFs = () => { };\n// optional Win32 file namespace prefix followed by drive letter and colon\nconst windowsFullPathRegex = /^(\\\\{2}\\?\\\\)?([a-zA-Z]):/;\nconst upcaseDriveLetter = (s) => s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`);\nfunction shimFs(binary, fs = require('fs')) {\n    if (originalFsMethods !== null) {\n        return;\n    }\n    originalFsMethods = Object.assign({}, fs);\n    const { blobPath, resources: manifest } = binary, { resourceStart, stat } = binary.layout, directories = {}, notAFile = '!@#$%^&*', isWin = process.platform.startsWith('win'), isString = (x) => typeof x === 'string' || x instanceof String, noop = () => { }, path = require('path'), winPath = isWin ? upcaseDriveLetter : (s) => s, baseDir = winPath(path.dirname(process.execPath));\n    let log = (_) => true;\n    let loggedManifest = false;\n    if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {\n        log = (text) => {\n            setupManifest();\n            if (!loggedManifest) {\n                process.stderr.write('[nexe] - MANIFEST' + JSON.stringify(manifest, null, 4) + '\\n');\n                process.stderr.write('[nexe] - DIRECTORIES' + JSON.stringify(directories, null, 4) + '\\n');\n                loggedManifest = true;\n            }\n            return process.stderr.write('[nexe] - ' + text + '\\n');\n        };\n    }\n    const getKey = function getKey(filepath) {\n        if (Buffer.isBuffer(filepath)) {\n            filepath = filepath.toString();\n        }\n        if (!isString(filepath)) {\n            return notAFile;\n        }\n        let key = path.resolve(baseDir, filepath);\n        return winPath(key);\n    };\n    const statTime = function () {\n        return {\n            dev: 0,\n            ino: 0,\n            nlink: 0,\n            rdev: 0,\n            uid: 123,\n            gid: 500,\n            blksize: 4096,\n            blocks: 0,\n            atime: new Date(stat.atime),\n            atimeMs: stat.atime.getTime(),\n            mtime: new Date(stat.mtime),\n            mtimeMs: stat.mtime.getTime(),\n            ctime: new Date(stat.ctime),\n            ctimMs: stat.ctime.getTime(),\n            birthtime: new Date(stat.birthtime),\n            birthtimeMs: stat.birthtime.getTime(),\n        };\n    };\n    let BigInt;\n    try {\n        BigInt = eval('BigInt');\n    }\n    catch (ignored) { }\n    const createStat = function (extensions, options) {\n        const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions);\n        if (options && options.bigint && BigInt) {\n            for (const k in stat) {\n                if (Object.prototype.hasOwnProperty.call(stat, k) && typeof stat[k] === 'number') {\n                    stat[k] = BigInt(stat[k]);\n                }\n            }\n        }\n        return stat;\n    };\n    const ownStat = function (filepath, options) {\n        setupManifest();\n        const key = getKey(filepath);\n        if (directories[key]) {\n            let mode = binary.layout.stat.mode;\n            mode |= fs.constants.S_IFDIR;\n            mode &= ~fs.constants.S_IFREG;\n            return createStat({ mode, size: 0 }, options);\n        }\n        if (manifest[key]) {\n            return createStat({ size: manifest[key][1] }, options);\n        }\n    };\n    const getStat = function (fn) {\n        return function stat(filepath, options, callback) {\n            let stat;\n            if (typeof options === 'function') {\n                callback = options;\n                stat = ownStat(filepath, null);\n            }\n            else {\n                stat = ownStat(filepath, options);\n            }\n            if (stat) {\n                process.nextTick(() => {\n                    callback(null, stat);\n                });\n            }\n            else {\n                return originalFsMethods[fn].apply(fs, arguments);\n            }\n        };\n    };\n    function makeLong(filepath) {\n        return path._makeLong && path._makeLong(filepath);\n    }\n    function fileOpts(options) {\n        return !options ? {} : isString(options) ? { encoding: options } : options;\n    }\n    let setupManifest = () => {\n        Object.keys(manifest).forEach((filepath) => {\n            const entry = manifest[filepath];\n            const absolutePath = getKey(filepath);\n            const longPath = makeLong(absolutePath);\n            const normalizedPath = winPath(path.normalize(filepath));\n            if (!manifest[absolutePath]) {\n                manifest[absolutePath] = entry;\n            }\n            if (longPath && !manifest[longPath]) {\n                manifest[longPath] = entry;\n            }\n            if (!manifest[normalizedPath]) {\n                manifest[normalizedPath] = manifest[filepath];\n            }\n            let currentDir = path.dirname(absolutePath);\n            let prevDir = absolutePath;\n            while (currentDir !== prevDir) {\n                directories[currentDir] = directories[currentDir] || {};\n                directories[currentDir][path.basename(prevDir)] = true;\n                const longDir = makeLong(currentDir);\n                if (longDir && !directories[longDir]) {\n                    directories[longDir] = directories[currentDir];\n                }\n                prevDir = currentDir;\n                currentDir = path.dirname(currentDir);\n            }\n        });\n        manifest[notAFile] = false;\n        directories[notAFile] = false;\n        setupManifest = noop;\n    };\n    //naive patches intended to work for most use cases\n    const nfs = {\n        existsSync: function existsSync(filepath) {\n            setupManifest();\n            const key = getKey(filepath);\n            if (manifest[key] || directories[key]) {\n                return true;\n            }\n            return originalFsMethods.existsSync.apply(fs, arguments);\n        },\n        realpath: function realpath(filepath, options, cb) {\n            setupManifest();\n            const key = getKey(filepath);\n            if (isString(filepath) && (manifest[filepath] || manifest[key])) {\n                return process.nextTick(() => cb(null, filepath));\n            }\n            return originalFsMethods.realpath.call(fs, filepath, options, cb);\n        },\n        realpathSync: function realpathSync(filepath, options) {\n            setupManifest();\n            const key = getKey(filepath);\n            if (manifest[key]) {\n                return filepath;\n            }\n            return originalFsMethods.realpathSync.call(fs, filepath, options);\n        },\n        readdir: function readdir(filepath, options, callback) {\n            setupManifest();\n            const dir = directories[getKey(filepath)];\n            if (dir) {\n                if ('function' === typeof options) {\n                    callback = options;\n                    options = { encoding: 'utf8' };\n                }\n                process.nextTick(() => callback(null, Object.keys(dir)));\n            }\n            else {\n                return originalFsMethods.readdir.apply(fs, arguments);\n            }\n        },\n        readdirSync: function readdirSync(filepath, options) {\n            setupManifest();\n            const dir = directories[getKey(filepath)];\n            if (dir) {\n                return Object.keys(dir);\n            }\n            return originalFsMethods.readdirSync.apply(fs, arguments);\n        },\n        readFile: function readFile(filepath, options, callback) {\n            setupManifest();\n            const entry = manifest[getKey(filepath)];\n            if (!entry) {\n                return originalFsMethods.readFile.apply(fs, arguments);\n            }\n            const [offset, length] = entry;\n            const resourceOffset = resourceStart + offset;\n            const encoding = fileOpts(options).encoding;\n            callback = typeof options === 'function' ? options : callback;\n            originalFsMethods.open(blobPath, 'r', function (err, fd) {\n                if (err)\n                    return callback(err, null);\n                originalFsMethods.read(fd, Buffer.alloc(length), 0, length, resourceOffset, function (error, bytesRead, result) {\n                    if (error) {\n                        return originalFsMethods.close(fd, function () {\n                            callback(error, null);\n                        });\n                    }\n                    originalFsMethods.close(fd, function (err) {\n                        if (err) {\n                            return callback(err, result);\n                        }\n                        callback(err, encoding ? result.toString(encoding) : result);\n                    });\n                });\n            });\n        },\n        createReadStream: function createReadStream(filepath, options) {\n            setupManifest();\n            const entry = manifest[getKey(filepath)];\n            if (!entry) {\n                return originalFsMethods.createReadStream.apply(fs, arguments);\n            }\n            const [offset, length] = entry;\n            const resourceOffset = resourceStart + offset;\n            const opts = fileOpts(options);\n            return originalFsMethods.createReadStream(blobPath, Object.assign({}, opts, {\n                start: resourceOffset,\n                end: resourceOffset + length - 1,\n            }));\n        },\n        readFileSync: function readFileSync(filepath, options) {\n            setupManifest();\n            const entry = manifest[getKey(filepath)];\n            if (!entry) {\n                return originalFsMethods.readFileSync.apply(fs, arguments);\n            }\n            const [offset, length] = entry;\n            const resourceOffset = resourceStart + offset;\n            const encoding = fileOpts(options).encoding;\n            const fd = originalFsMethods.openSync(process.execPath, 'r');\n            const result = Buffer.alloc(length);\n            originalFsMethods.readSync(fd, result, 0, length, resourceOffset);\n            originalFsMethods.closeSync(fd);\n            return encoding ? result.toString(encoding) : result;\n        },\n        statSync: function statSync(filepath, options) {\n            const stat = ownStat(filepath, options);\n            if (stat) {\n                return stat;\n            }\n            return originalFsMethods.statSync.apply(fs, arguments);\n        },\n        stat: getStat('stat'),\n        lstat: getStat('lstat'),\n        lstatSync: function statSync(filepath, options) {\n            const stat = ownStat(filepath, options);\n            if (stat) {\n                return stat;\n            }\n            return originalFsMethods.lstatSync.apply(fs, arguments);\n        },\n    };\n    if (typeof fs.exists === 'function') {\n        nfs.exists = function (filepath, cb) {\n            cb = cb || noop;\n            const exists = nfs.existsSync(filepath);\n            process.nextTick(() => cb(exists));\n        };\n    }\n    const patches = process.nexe.patches || {};\n    delete process.nexe;\n    patches.internalModuleReadFile = function (original, ...args) {\n        setupManifest();\n        const filepath = getKey(args[0]);\n        if (manifest[filepath]) {\n            log('read     (hit)              ' + filepath);\n            return nfs.readFileSync(filepath, 'utf-8');\n        }\n        log('read          (miss)       ' + filepath);\n        return original.call(this, ...args);\n    };\n    let returningArray;\n    patches.internalModuleReadJSON = function (original, ...args) {\n        if (returningArray == null)\n            returningArray = Array.isArray(original.call(this, ''));\n        const res = patches.internalModuleReadFile.call(this, original, ...args);\n        return returningArray && !Array.isArray(res)\n            ? [res, /\"(main|name|type|exports|imports)\"/.test(res)]\n            : res;\n    };\n    patches.internalModuleStat = function (original, ...args) {\n        setupManifest();\n        const filepath = getKey(args[0]);\n        if (manifest[filepath]) {\n            log('stat     (hit)              ' + filepath + '   ' + 0);\n            return 0;\n        }\n        if (directories[filepath]) {\n            log('stat dir (hit)              ' + filepath + '   ' + 1);\n            return 1;\n        }\n        const res = original.call(this, ...args);\n        if (res === 0) {\n            log('stat          (miss)        ' + filepath + '   ' + res);\n        }\n        else if (res === 1) {\n            log('stat dir      (miss)        ' + filepath + '   ' + res);\n        }\n        else {\n            log('stat                 (fail) ' + filepath + '   ' + res);\n        }\n        return res;\n    };\n    if (typeof fs.exists === 'function') {\n        nfs.exists = function (filepath, cb) {\n            cb = cb || noop;\n            const exists = nfs.existsSync(filepath);\n            if (!exists) {\n                return originalFsMethods.exists(filepath, cb);\n            }\n            process.nextTick(() => cb(exists));\n        };\n    }\n    if (typeof fs.copyFile === 'function') {\n        nfs.copyFile = function (filepath, dest, flags, callback) {\n            setupManifest();\n            const entry = manifest[getKey(filepath)];\n            if (!entry) {\n                return originalFsMethods.copyFile.apply(fs, arguments);\n            }\n            if (typeof flags === 'function') {\n                callback = flags;\n                flags = 0;\n            }\n            nfs.readFile(filepath, (err, buffer) => {\n                if (err) {\n                    return callback(err);\n                }\n                originalFsMethods.writeFile(dest, buffer, (err) => {\n                    if (err) {\n                        return callback(err);\n                    }\n                    callback(null);\n                });\n            });\n        };\n        nfs.copyFileSync = function (filepath, dest) {\n            setupManifest();\n            const entry = manifest[getKey(filepath)];\n            if (!entry) {\n                return originalFsMethods.copyFileSync.apply(fs, arguments);\n            }\n            return originalFsMethods.writeFileSync(dest, nfs.readFileSync(filepath));\n        };\n    }\n    Object.assign(fs, nfs);\n    lazyRestoreFs = () => {\n        Object.keys(nfs).forEach((key) => {\n            fs[key] = originalFsMethods[key];\n        });\n        lazyRestoreFs = () => { };\n    };\n    return true;\n}\nexports.shimFs = shimFs;\nfunction restoreFs() {\n    lazyRestoreFs();\n}\nexports.restoreFs = restoreFs;\n" +
            '\nshimFs(process.__nexe)' +
            `\n${compiler.options.fs ? '' : 'restoreFs()'}`
        //TODO support only restoring specific methods
        ));
        compiler.shims.push(util_1.wrap(`
    if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
      const cluster = require('cluster')
      cluster._setupWorker()
      delete process.env.NODE_UNIQUE_ID
    }
  `));
        compiler.shims.push(util_1.wrap(`
      if (!process.send) {
        const path = require('path')
        const entry = path.resolve(path.dirname(process.execPath),${JSON.stringify(compiler.entrypoint)})
        process.argv.splice(1,0, entry)
      }
    `));
    });
}
exports.default = default_1;
