import fs from "fs-extra";
import path from "path";
import type { Dirent } from "fs-extra";
import { execSync } from "node:child_process";

/**
 * Example usage with node >= v22:
 *    node --experimental-strip-types bin/cleanupNodeModules.ts /path/to/build/folder [--skip-prune-dev-deps]
 * Example usage with tsx:
 *    tsx bin/cleanupNodeModules.ts /path/to/build/folder [--skip-prune-dev-deps]
 */
function main() {

    if (process.argv.length > 4 || process.argv.length < 3) {
        console.error("Usage: cleanupNodeModules.ts [path-to-build-folder] [--skip-prune-dev-deps]");
        process.exit(1);
    }

    const basePath = process.argv[2];
    const pruneDevDeps = process.argv[3] !== "--skip-prune-dev-deps";

    if (!fs.existsSync(basePath)) {
        console.error(`Supplied path '${basePath}' does not exist. Aborting.`);
        process.exit(1);
    }

    console.log(`Starting pruning of node_modules ${!pruneDevDeps ? '(skipping npm pruning)' : ''} in '${basePath}'...`);
    cleanupNodeModules(basePath, pruneDevDeps);
    console.log("Successfully pruned node_modules.");
}

function cleanupNodeModules(basePath: string, pruneDevDeps: boolean = true) {

    // This needs to run for the server and Docker build,
    // but needs to be skipped for electron-forge: its
    // built-in pruning takes care of it already
    if (pruneDevDeps) {
        execSync(`npm ci --omit=dev --prefix ${basePath}`);
    }

    const nodeModulesDirPath = path.join(basePath, "node_modules");
    const nodeModulesContent = fs.readdirSync(nodeModulesDirPath, { recursive: true, withFileTypes: true });
    //const libDir = fs.readdirSync(path.join(basePath, "./libraries"), { recursive: true, withFileTypes: true });

    /**
     * Delete unnecessary folders
     */
    const filterableDirs = new Set([
        "demo",
        "demos",
        "doc",
        "docs",
        "example",
        "examples",
        "test",
        "tests"
    ]);

    nodeModulesContent
        .filter(el => el.isDirectory() && filterableDirs.has(el.name))
        .forEach(dir => removeDirent(dir));

    /**
     * Delete unnecessary files based on file extension
     * TODO filter out useless (README).md files
     */
    const filterableFileExt = new Set([
        "ts",
        "map"
    ]);

    nodeModulesContent
        // TriliumNextTODO: check if we can improve this naive file ext matching, without introducing any additional dependency
        .filter(el => el.isFile() && filterableFileExt.has(el.name.split(".").at(-1) || ""))
        .forEach(dir => removeDirent(dir));


    /**
     * Delete specific unnecessary folders
     * TODO: check if we want removeSync to throw an error, if path does not exist anymore -> currently it will silently fail
     */
    const extraFoldersDelete = new Set([
        path.join(nodeModulesDirPath, ".bin"),
        path.join(nodeModulesDirPath, "@excalidraw", "excalidraw", "dist", "dev"),
        path.join(nodeModulesDirPath, "boxicons", "svg"),
        path.join(nodeModulesDirPath, "boxicons", "node_modules"),
        path.join(nodeModulesDirPath, "boxicons", "src"),
        path.join(nodeModulesDirPath, "boxicons", "iconjar"),
        path.join(nodeModulesDirPath, "@jimp", "plugin-print", "fonts"),
        path.join(nodeModulesDirPath, "jimp", "dist", "browser") // missing "@" in front of jimp is not a typo here
    ]);

    nodeModulesContent
        .filter(el => el.isDirectory() && extraFoldersDelete.has(path.join(el.parentPath, el.name)))
        .forEach(dir => removeDirent(dir))
}


function removeDirent(el: Dirent) {
    const elementToDelete = path.join(el.parentPath, el.name);
    fs.removeSync(elementToDelete);

    if (process.env.VERBOSE) {
        console.log(`Deleted ${elementToDelete}`);
    }

}

main()