"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.needsRebuilding = exports.getModulePackageJSON = exports.getLastModifiedFile = exports.getLastCommitTime = exports.getLastModifiedSourceTime = exports.getLastBuildTime = exports.getLINCDDependencies = exports.getPackageJSON = exports.isImportWithMissingExtension = exports.isValidLINCDImport = exports.isImportOutsideOfPackage = exports.isInvalidLINCDImport = exports.getFileImports = void 0;
exports.execp = execp;
exports.execPromise = execPromise;
exports.generateScopedNameProduction = generateScopedNameProduction;
exports.generateScopedName = generateScopedName;
exports.log = log;
exports.debug = debug;
exports.debugInfo = debugInfo;
exports.warn = warn;
exports.flatten = flatten;
exports.getLinkedTailwindColors = getLinkedTailwindColors;
exports.getFiles = getFiles;
exports.stripAnsi = stripAnsi;
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const module_1 = require("module");
const path = __importStar(require("path"));
const typescript_1 = __importDefault(require("typescript"));
const crypto = __importStar(require("crypto"));
const find_nearest_package_json_1 = require("find-nearest-package-json");
const glob = __importStar(require("glob"));
var gruntConfig;
// Credit: https://gist.github.com/tinovyatkin/727ddbf7e7e10831a1eca9e4ff2fc32e
const tsHost = typescript_1.default.createCompilerHost({
    allowJs: true,
    noEmit: true,
    isolatedModules: true,
    resolveJsonModule: false,
    moduleResolution: typescript_1.default.ModuleResolutionKind.Classic, // we don't want node_modules
    incremental: true,
    noLib: true,
    noResolve: true,
}, true);
var getFileImports = async function (filePath) {
    try {
        const importing = [];
        const delintNode = (node) => {
            if (typescript_1.default.isImportDeclaration(node)) {
                const moduleName = node.moduleSpecifier.getText().replace(/['"]/g, '');
                if (!moduleName.startsWith('node:') &&
                    !module_1.builtinModules.includes(moduleName))
                    importing.push(moduleName);
            }
            else
                typescript_1.default.forEachChild(node, delintNode);
        };
        const sourceFile = tsHost.getSourceFile(filePath, typescript_1.default.ScriptTarget.Latest, (msg) => {
            throw new Error(`Failed to parse ${filePath}: ${msg}`);
        });
        //check if its a directory, then we wanr that we can't parse it
        let stat = fs.lstatSync(filePath);
        if (stat.isDirectory()) {
            return importing;
        }
        if (!sourceFile) {
            console.warn(`Failed to find file ${filePath}`);
            return importing;
        }
        delintNode(sourceFile);
        return importing;
    }
    catch (err) {
        console.warn(`Error parsing file ${filePath}: ${err}`);
        return [];
    }
};
exports.getFileImports = getFileImports;
/**
 *
 * @param importPath The import path to check
 * @param curFileDepth How many folders deep the current file is (0 = src, 1 = src/foo, etc.)
 * @returns
 */
var isInvalidLINCDImport = function (importPath, curFileDepth) {
    return (importPath.includes('lincd') &&
        (importPath.includes('/src/') || importPath.includes('/lib/')));
};
exports.isInvalidLINCDImport = isInvalidLINCDImport;
var isImportOutsideOfPackage = function (importPath, curFileDepth) {
    if (importPath.includes('..')) {
        //the number of '..' in the import path should be less than or equal to the current file depth
        //if its bigger then the import is outside of the package
        return importPath.split('..').length - 1 > curFileDepth;
    }
    return false;
};
exports.isImportOutsideOfPackage = isImportOutsideOfPackage;
/**
 *
 * @param importPath The import path to check
 * @param curFileDepth How many folders deep the current file is (0 = src, 1 = src/foo, etc.)
 * @returns
 */
var isValidLINCDImport = function (importPath, curFileDepth) {
    const validLincdPath = importPath.includes('lincd') && !importPath.includes('/src/');
    let validRelativePath = false;
    if (importPath.includes('..')) {
        // '../bad/path' from 'src/file.ts' should be invalid:
        //    ^ should get split into ['', '/bad/path'], and the file depth is 0
        //    meaning that it'll be invalid.
        // And this should be true for all relative imports containing '..'
        validRelativePath = importPath.split('..').length - 1 <= curFileDepth;
    }
    return validLincdPath || validRelativePath;
};
exports.isValidLINCDImport = isValidLINCDImport;
var isImportWithMissingExtension = function (importPath) {
    //if  a relative import then it needs an extension
    if (importPath.startsWith('../') || importPath.startsWith('./')) {
        //check if the last part of the import after the last slash has a file extension
        if (importPath.split('/').pop().split('.').length < 2) {
            //if it doesn't have an extension, then it's missing, so return true
            return true;
        }
    }
    return false;
};
exports.isImportWithMissingExtension = isImportWithMissingExtension;
var getPackageJSON = function (root = process.cwd(), error = true) {
    // console.log('Getting package.json from ' + chalk.cyan(root));
    //log stack trace
    let packagePath = path.join(root, 'package.json');
    if (fs.existsSync(packagePath)) {
        return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
    }
    else if (root === process.cwd()) {
        if (error) {
            console.warn('Could not find package.json. Make sure you run this command from the root of a lincd module or a lincd yarn workspace');
            process.exit();
        }
    }
};
exports.getPackageJSON = getPackageJSON;
/**
 * Scans package.json for dependencies that are LINCD packages
 * Also looks into dependencies of dependencies
 * If no packageJson is given, it will attempt to obtain it from the current working directory
 * Returns an array of lincd packages, with each entry containing an array with the package name and the local path to the package
 * @param packageJson
 */
var getLINCDDependencies = function (packageJson, checkedPackages = new Set()) {
    if (!packageJson) {
        packageJson = (0, exports.getPackageJSON)();
    }
    let dependencies = {
        ...packageJson.dependencies,
        ...packageJson.devDependencies,
    };
    let lincdPackagePaths = [];
    let firstTime = checkedPackages.size === 0;
    for (var dependency of Object.keys(dependencies)) {
        try {
            if (!checkedPackages.has(dependency)) {
                let [modulePackageJson, modulePath] = (0, exports.getModulePackageJSON)(dependency);
                checkedPackages.add(dependency);
                if (modulePackageJson === null || modulePackageJson === void 0 ? void 0 : modulePackageJson.lincd) {
                    lincdPackagePaths.push([
                        modulePackageJson.name,
                        modulePath,
                        [
                            ...Object.keys({
                                ...modulePackageJson.dependencies,
                                ...modulePackageJson.devDependencies,
                            }),
                        ],
                    ]);
                    //also check if this package has any dependencies that are lincd packages
                    lincdPackagePaths = lincdPackagePaths.concat((0, exports.getLINCDDependencies)(modulePackageJson, checkedPackages));
                }
                if (!modulePackageJson) {
                    //this seems to only happen with yarn workspaces for some grunt related dependencies of lincd-cli
                    // console.log(`could not find package.json of ${dependency}`);
                }
            }
        }
        catch (err) {
            console.log(`could not check if ${dependency} is a lincd package: ${err}`);
        }
    }
    if (firstTime) {
        // let dependencyMap:Map<string,Set<string>> = new Map();
        let lincdPackageNames = new Set(lincdPackagePaths.map(([packageName, modulePath, pkgDependencies]) => packageName));
        //remove lincd-cli from the list of lincd packages
        lincdPackageNames.delete('lincd-cli');
        lincdPackagePaths.forEach(([packageName, modulePath, pkgDependencies], key) => {
            let lincdDependencies = pkgDependencies.filter((dependency) => lincdPackageNames.has(dependency));
            if (packageName === 'lincd-cli') {
                //remove lincd-modules from the dependencies of lincd-cli (it's not a hard dependency, and it messes things up)
                lincdDependencies.splice(lincdDependencies.indexOf('lincd-modules'), 1);
            }
            // dependencyMap.set(packageName, new Set(lincdDependencies));
            //update dependencies to be the actual lincd package objects
            lincdPackagePaths[key][2] = lincdDependencies;
        });
        // //add the nested dependencies for each lincd package
        // for (let [packageName,pkgDependencies] of dependencyMap) {
        //   pkgDependencies.forEach((dependency) => {
        //     if (dependencyMap.has(dependency)) {
        //       dependencyMap.get(dependency).forEach((nestedDependency) => {
        //         pkgDependencies.add(nestedDependency);
        //       });
        //     }
        //   });
        // }
        //
        // dependencyMap.forEach((dependencies,packageName) => {
        //   //check for circular dependencies
        //   if([...dependencies].some(dependency => {
        //     return dependencyMap.get(dependency).has(packageName);
        //   }))
        //   {
        //     console.warn(`Circular dependency detected between ${packageName} and ${dependency}`);
        //   }
        //
        // });
        // a simple sort with dependencyMap doesn't seem to work,so we start with LINCD (least dependencies) and from there add packages that have all their dependencies already added
        let sortedPackagePaths = [];
        let addedPackages = new Set();
        let lincdItself = lincdPackagePaths.find(([packageName]) => {
            return packageName === 'lincd';
        });
        if (lincdItself) {
            sortedPackagePaths.push(lincdItself);
            addedPackages = new Set(['lincd']);
        }
        while (addedPackages.size !== lincdPackagePaths.length) {
            let startSize = addedPackages.size;
            lincdPackagePaths.forEach(([packageName, modulePath, pkgDependencies]) => {
                if (!addedPackages.has(packageName) &&
                    pkgDependencies.every((dependency) => addedPackages.has(dependency))) {
                    sortedPackagePaths.push([packageName, modulePath, pkgDependencies]);
                    addedPackages.add(packageName);
                }
            });
            if (startSize === addedPackages.size) {
                console.warn('Could not sort lincd packages, circular dependencies?');
                break;
            }
        }
        //sort the lincd packages by least dependent first
        // lincdPackagePaths = lincdPackagePaths.sort(([packageNameA],[packageNameB]) => {
        //   //if package A depends on package B, then package B should come first
        //   if (dependencyMap.get(packageNameA).has(packageNameB)) {
        //     console.log(packageNameA+' depends on '+packageNameB+ ' (below)')
        //       return 1;
        //   }
        //   console.log(packageNameA+' above '+packageNameB)
        //   return -1;
        // });
        return sortedPackagePaths;
    }
    return lincdPackagePaths;
};
exports.getLINCDDependencies = getLINCDDependencies;
const getLastBuildTime = (packagePath) => {
    return (0, exports.getLastModifiedFile)(packagePath + '/@(builds|lib|dist)/**/*.js');
};
exports.getLastBuildTime = getLastBuildTime;
const getLastModifiedSourceTime = (packagePath) => {
    return (0, exports.getLastModifiedFile)(packagePath + '/@(src|data|css|modules)/**/*', {
        ignore: [
            packagePath + '/**/*.css.json',
            packagePath + '/**/*.d.ts',
            packagePath + '/**/node_modules/**/*',
            packagePath + '/**/lib/**/*',
            packagePath + '/**/dist/**/*',
        ],
    });
};
exports.getLastModifiedSourceTime = getLastModifiedSourceTime;
const getLastCommitTime = (packagePath) => {
    // console.log(`git log -1 --format=%ci -- ${packagePath}`);
    // process.exit();
    return execPromise(`cd ${packagePath} && git log -1 --format="%h %ci" -- .`)
        .then(async (result) => {
        let commitId = result.substring(0, result.indexOf(' '));
        let date = result.substring(commitId.length + 1);
        let lastCommitDate = new Date(date);
        let changes = await execPromise(`cd ${packagePath} && git show --stat --oneline ${commitId} -- .`);
        // log(packagePath, result, lastCommitDate);
        // log(changes);
        return { date: lastCommitDate, changes, commitId };
    })
        .catch(({ error, stdout, stderr }) => {
        debugInfo(chalk_1.default.red('Git error: ') + error.message.toString());
        return null;
    });
};
exports.getLastCommitTime = getLastCommitTime;
const getLastModifiedFile = (filePath, config = {}) => {
    var files = glob.sync(filePath, config);
    // console.log(files.join(" - "));
    var lastModifiedName;
    var lastModified;
    var lastModifiedTime = 0;
    files.forEach((fileName) => {
        if (fs.lstatSync(fileName).isDirectory()) {
            // console.log("skipping directory "+fileName);
            return;
        }
        let mtime = fs.statSync(path.join(fileName)).mtime;
        let modifiedTime = mtime.getTime();
        if (modifiedTime > lastModifiedTime) {
            // console.log(fileName,mtime);
            lastModifiedName = fileName;
            lastModified = mtime;
            lastModifiedTime = modifiedTime;
        }
    });
    return { lastModified, lastModifiedName, lastModifiedTime };
};
exports.getLastModifiedFile = getLastModifiedFile;
//from https://github.com/haalcala/node-packagejson/blob/master/index.js
var getModulePackageJSON = function (module_name, work_dir) {
    if (!work_dir) {
        work_dir = process.cwd();
    }
    else {
        work_dir = path.resolve(work_dir);
    }
    var package_json;
    if (fs.existsSync(path.resolve(work_dir, './node_modules'))) {
        var module_dir = path.resolve(work_dir, './node_modules/' + module_name);
        if (fs.existsSync(module_dir) &&
            fs.existsSync(module_dir + '/package.json')) {
            package_json = JSON.parse(fs.readFileSync(module_dir + '/package.json', 'utf-8'));
        }
    }
    if (!package_json && work_dir != '/') {
        return (0, exports.getModulePackageJSON)(module_name, path.resolve(work_dir, '..'));
    }
    return [package_json, module_dir];
};
exports.getModulePackageJSON = getModulePackageJSON;
function execp(cmd, log = false, allowError = false, options = {}) {
    // opts || (opts = {});
    if (log)
        console.log(chalk_1.default.cyan(cmd));
    return new Promise((resolve, reject) => {
        var child = (0, child_process_1.exec)(cmd, options);
        child.stdout.pipe(process.stdout);
        child.stderr.pipe(process.stderr);
        // process.stdin.pipe(child.stdin);
        child.on('close', function (code) {
            if (code === 0) {
                resolve(null);
            }
            else {
                reject();
            }
            // console.log('killing child');
            // child.kill('SIGHUP');
            // resolve(code);
        });
        // child.on('data', function (result) {
        // 	// if (log)
        // 	// {
        // 	// 	console.log(result);
        // 	// }
        // 	resolve(result);
        // 	console.log('resolve data');
        //
        // });
        child.on('error', function (err) {
            if (!allowError) {
                // console.log('reject err');
                reject(err);
                return;
            }
            else if (log) {
                console.warn(err);
            }
            // console.log('resolve err');
            resolve(null);
        });
        child.on('exit', function (code, signal) {
            if (code !== 0) {
                reject('Child process exited with error code ' + code);
                return;
            }
            // console.log('resolve exit');
            resolve(null);
        });
    });
}
function execPromise(command, log = false, allowError = false, options, pipeOutput = false) {
    return new Promise(function (resolve, reject) {
        if (log)
            console.log(chalk_1.default.cyan(command));
        let child = (0, child_process_1.exec)(command, options, (error, stdout, stderr) => {
            if (error) {
                if (!allowError) {
                    reject({ error, stdout, stderr });
                    return;
                }
                else if (log) {
                    console.warn(error);
                }
            }
            //TODO: getting a typescript error for 'trim()', this worked before, is it still used? do we log anywhere?
            let result = stdout['trim']();
            if (log) {
                // console.log(chalk"RESOLVING "+command);
                console.log(result);
                // console.log('ERRORS:'+(result.indexOf('Aborted due to warnings') !== -1));
                // console.log('stderr:'+stderr);
            }
            resolve(result);
        });
        if (pipeOutput) {
            child.stdout.pipe(process.stdout);
            child.stderr.pipe(process.stderr);
        }
    });
}
function generateScopedNameProduction(cssClassName, filepath, css) {
    //for app development we can use short unique hashes
    //but for webpack bundles of lincd modules, we need to ensure unique class names across bundles of many packages
    //generate a short unique hash based on cssClassName and filepath
    let hash = crypto
        .createHash('md5')
        .update(cssClassName + filepath)
        .digest('hex')
        .substring(0, 6);
    return hash;
}
function generateScopedName(cssClassName, filepath, css) {
    // return cssClassName;
    var filename = path
        .basename(filepath)
        .replace(/\.module\.css$/, '')
        .replace(/\.css$/, '');
    let resolved = path.resolve(filepath).replace(/[\w\-_\/]+\/file\:/, '');
    let nearestPackageJson = (0, find_nearest_package_json_1.findNearestPackageJsonSync)(resolved);
    let packageName = nearestPackageJson
        ? nearestPackageJson.data.name
        : 'unknown';
    return (packageName.replace(/[^a-zA-Z0-9_]+/g, '_') +
        '_' +
        filename +
        '_' +
        cssClassName);
}
const needsRebuilding = async function (pkg, useGitForLastModified, log = false) {
    let lastModifiedSourceDate;
    let lastModifiedSourceName;
    if (useGitForLastModified) {
        const { changes, commitId, date } = await (0, exports.getLastCommitTime)(pkg.path);
        lastModifiedSourceDate = date;
        lastModifiedSourceName = commitId;
    }
    else {
        const { lastModified, lastModifiedName, lastModifiedTime } = (0, exports.getLastModifiedSourceTime)(pkg.path);
        lastModifiedSourceName = lastModifiedName;
        lastModifiedSourceDate = lastModified;
    }
    let lastModifiedBundle = (0, exports.getLastBuildTime)(pkg.path);
    let result = lastModifiedSourceDate &&
        lastModifiedSourceDate.getTime() > lastModifiedBundle.lastModifiedTime;
    if (log) {
        console.log(chalk_1.default.cyan('Last modified source: ' +
            lastModifiedSourceName +
            ' on ' +
            lastModifiedSourceDate.toString()));
        console.log(chalk_1.default.cyan('Last build: ' +
            (lastModifiedBundle &&
                typeof lastModifiedBundle.lastModified !== 'undefined'
                ? lastModifiedBundle.lastModified.toString()
                : 'never')));
    }
    return result;
};
exports.needsRebuilding = needsRebuilding;
function log(...messages) {
    messages.forEach((message) => {
        console.log(chalk_1.default.cyan(message));
    });
}
function debug(config, ...messages) {
    if (config.debug) {
        log(...messages);
    }
}
function debugInfo(...messages) {
    // messages.forEach((message) => {
    //   console.log(chalk.cyan('Info: ') + message);
    // });
    //@TODO: let packages also use linked.config.json? instead of gruntfile...
    // that way we can read "analyse" here and see if we need to log debug info
    // if(!gruntConfig)
    // {
    //   gruntConfig = getGruntConfig();
    //   console.log(gruntConfig);
    //   process.exit();
    // }
    if (gruntConfig && gruntConfig.analyse === true) {
        messages.forEach((message) => {
            console.log(chalk_1.default.cyan('Info: ') + message);
        });
    }
}
function warn(...messages) {
    messages.forEach((message) => {
        console.log(chalk_1.default.red(message));
    });
}
function flatten(arr) {
    return arr.reduce(function (a, b) {
        return b ? a.concat(b) : a;
    }, []);
}
function getLinkedTailwindColors() {
    return {
        'primary-color': 'var(--primary-color)',
        'font-color': 'var(--font-color)',
    };
}
/**
 * Recursively get all files in a directory
 * https://stackoverflow.com/a/45130990/831465
 * @param dir The directory to get files from
 * @returns A promise that resolves to an array of file paths
 */
async function getFiles(dir, filenameFilter) {
    const entries = await fs.promises.readdir(dir, { withFileTypes: true });
    let files = await Promise.all(entries.map((entry) => {
        const res = path.resolve(dir, entry.name);
        return entry.isDirectory() ? getFiles(res, filenameFilter) : res;
    }));
    //flatten the array of arrays into a single array
    let flatFiles = flatten(files);
    if (filenameFilter) {
        //filter the files to only include those that match the filenameFilter
        flatFiles = flatFiles.filter((file) => file.includes(filenameFilter));
    }
    return flatFiles;
}
/**
 * Strip ANSI escape codes from a string
 * @param str The string to strip ANSI codes from
 * @returns The string with ANSI codes removed
 */
function stripAnsi(str) {
    return str.replace(/\x1b\[[0-9;]*m/g, '');
}
//# sourceMappingURL=utils.js.map