"use strict";
const fs = require("fs");
const path = require("path");
const util_1 = require("util");
async function copy(sourcePath, targetPath, options) {
    const copyFile = util_1.promisify(fs.copyFile);
    const lstat = util_1.promisify(fs.lstat);
    const mkdir = util_1.promisify(fs.mkdir);
    const readdir = util_1.promisify(fs.readdir);
    const readFile = util_1.promisify(fs.readFile);
    const readlink = util_1.promisify(fs.readlink);
    const symlink = util_1.promisify(fs.symlink);
    const unlink = util_1.promisify(fs.unlink);
    const utimes = util_1.promisify(fs.utimes);
    const writeFile = util_1.promisify(fs.writeFile);
    const defaultOptions = {
        overwrite: true,
        errorOnExist: false,
        dereference: false,
        preserveTimestamps: false,
        dryRun: false,
    };
    const derivedOptions = Object.assign(defaultOptions, options);
    const { overwrite, errorOnExist, dereference, preserveTimestamps, dryRun, rename, filter, transform, afterEach } = derivedOptions;
    const flag = overwrite ? 'w' : 'wx';
    const [directories, files, symlinks, size] = await cpPath(sourcePath, targetPath, [0, 0, 0, 0]);
    return {
        directories,
        files,
        symlinks,
        size,
    };
    async function cpPath(src, dst, subTotals) {
        const source = { path: src, stats: await lstat(src) };
        const target = { path: dst, stats: await lstat(dst).catch(ENOENT) };
        if (rename) {
            target.path = await Promise.resolve(rename(source, target, derivedOptions)) || target.path;
            target.stats = await lstat(target.path).catch(ENOENT) || target.stats;
        }
        if (!filter || await Promise.resolve(filter(source, target, derivedOptions))) {
            if (errorOnExist && target.stats && !overwrite) {
                throw Error(`target already exists: ${target}`);
            }
            if (source.stats.isFile() || (dereference && source.stats.isSymbolicLink())) {
                const fileSize = await cpFile(source, target);
                subTotals[1] += 1;
                subTotals[3] += fileSize;
            }
            else if (source.stats.isDirectory()) {
                subTotals[0] += 1;
                await cpDir(source, target, subTotals);
            }
            else if (source.stats.isSymbolicLink()) {
                const symlinkSize = await cpSymlink(source, target);
                subTotals[2] += 1;
                subTotals[3] += symlinkSize;
            }
            if (!dryRun) {
                if (preserveTimestamps && (!target.stats || overwrite)) {
                    if (!source.stats.isSymbolicLink()) {
                        await utimes(target.path, source.stats.atime, source.stats.mtime);
                    }
                }
                if (afterEach) {
                    target.stats = target.stats || await lstat(target.path).catch(ENOENT);
                }
            }
            if (afterEach) {
                await Promise.resolve(afterEach(source, target, derivedOptions));
            }
        }
        return subTotals;
    }
    async function cpDir(source, target, subTotals) {
        if (!dryRun && !target.stats) {
            await mkdir(target.path, { recursive: true, mode: source.stats.mode });
        }
        await Promise.all((await readdir(source.path)).map(async (child) => cpPath(path.join(source.path, child), path.join(target.path, child), subTotals)));
    }
    async function cpFile(source, target) {
        if (transform) {
            const data = await Promise.resolve(transform(await readFile(source.path), source, target, derivedOptions));
            if (!dryRun && (!target.stats || overwrite)) {
                await writeFile(target.path, data, { mode: source.stats.mode, flag });
            }
            return data.length;
        }
        else if (!target.stats || overwrite) {
            if (!dryRun) {
                await copyFile(source.path, target.path);
            }
            return source.stats.size;
        }
        else {
            return 0;
        }
    }
    async function cpSymlink(source, target) {
        if (!target.stats || overwrite) {
            const link = await readlink(source.path);
            if (!dryRun) {
                if (target.stats) {
                    await unlink(target.path);
                }
                await symlink(link, target.path);
            }
            return source.stats.size;
        }
        else {
            return 0;
        }
    }
    async function ENOENT(err) {
        if (err.code === 'ENOENT') {
            return undefined;
        }
        else {
            throw err;
        }
    }
}
module.exports = copy;
//# sourceMappingURL=index.js.map