"use strict";
/* eslint-disable no-await-in-loop, @typescript-eslint/no-non-null-asserted-optional-chain, no-promise-executor-return */
Object.defineProperty(exports, "__esModule", { value: true });
const console_1 = require("./console");
const fs = require("fs");
// eslint-disable-next-line
const fetch = require("node-fetch");
// eslint-disable-next-line
const fm = require("front-matter");
// This function checks if a url is valid.
const isUrl = async (url, errors, counter) => {
    const regexUrl = /(https?:\/\/[\w./-]+)/gm;
    counter.links.total++;
    if (!regexUrl.test(url)) {
        counter.links.error++;
        errors.push({
            exercise: undefined,
            msg: `The repository value of the configuration file is not a link: ${url}`,
        });
        return false;
    }
    const res = await fetch(url, { method: "HEAD" });
    if (!res.ok) {
        counter.links.error++;
        errors.push({
            exercise: undefined,
            msg: `The link of the repository is broken: ${url}`,
        });
    }
    return true;
};
const checkForEmptySpaces = (str) => {
    const isEmpty = true;
    for (const letter of str) {
        if (letter !== " ") {
            return false;
        }
    }
    return isEmpty;
};
const checkSlug = (slug) => {
    // Validate that the length of the slug is less than 50 characters
    // The slug must start with a letter
    if (slug.length > 50) {
        return false;
    }
    if (!/^[A-Za-z]/.test(slug)) {
        return false;
    }
    if (!/[A-Za-z]$/.test(slug)) {
        return false;
    }
    return true;
};
const checkLearnpackClean = (configObj, errors) => {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j;
    if ((((_a = configObj.config) === null || _a === void 0 ? void 0 : _a.outputPath) &&
        fs.existsSync((_b = configObj.config) === null || _b === void 0 ? void 0 : _b.outputPath)) ||
        fs.existsSync(`${(_c = configObj.config) === null || _c === void 0 ? void 0 : _c.dirPath}/_app`) ||
        fs.existsSync(`${(_d = configObj.config) === null || _d === void 0 ? void 0 : _d.dirPath}/reports`) ||
        fs.existsSync(`${(_e = configObj.config) === null || _e === void 0 ? void 0 : _e.dirPath}/resets`) ||
        fs.existsSync(`${(_f = configObj.config) === null || _f === void 0 ? void 0 : _f.dirPath}/app.tar.gz`) ||
        fs.existsSync(`${(_g = configObj.config) === null || _g === void 0 ? void 0 : _g.dirPath}/config.json`) ||
        fs.existsSync(`${(_h = configObj.config) === null || _h === void 0 ? void 0 : _h.dirPath}/vscode_queue.json`) ||
        fs.existsSync(`${(_j = configObj.config) === null || _j === void 0 ? void 0 : _j.dirPath}/telemetry.json`)) {
        errors.push({
            exercise: undefined,
            msg: "You have to run learnpack clean command",
        });
    }
};
const findInFile = (types, content) => {
    const regex = {
        relativeImages: /!\[.*]\s*\((((\.\/)?(\.{2}\/){1,5})(.*\/)*(.[^\s/]*\.[A-Za-z]{2,4})\S*)\)/gm,
        externalImages: /!\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
        markdownLinks: /(\s)+\[.*]\((https?:\/(\/[^)/]+)+\/?)\)/gm,
        url: /(https?:\/\/[\w./-]+)/gm,
        uploadcare: /https:\/\/ucarecdn.com\/(?:.*\/)*([\w./-]+)/gm,
    };
    const validTypes = Object.keys(regex);
    if (!Array.isArray(types))
        types = [types];
    const findings = {};
    for (const type of types) {
        if (!validTypes.includes(type))
            throw new Error("Invalid type: " + type);
        else
            findings[type] = {};
    }
    for (const type of types) {
        let m;
        while ((m = regex[type].exec(content)) !== null) {
            // This is necessary to avoid infinite loops with zero-width matches
            if (m.index === regex.lastIndex) {
                regex.lastIndex++;
            }
            // The result can be accessed through the `m`-variable.
            // m.forEach((match, groupIndex) => values.push(match));
            findings[type][m[0]] = {
                content: m[0],
                absUrl: m[1],
                mdUrl: m[2],
                relUrl: m[6],
            };
        }
    }
    return findings;
};
const checkLinkWithRetry = async (url, retries = 3, delay = 1000) => {
    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            let res = await fetch(url, { method: "HEAD" });
            if (res.status === 429) {
                await new Promise(resolve => setTimeout(resolve, delay));
                delay *= 2; // Exponential backoff
                continue;
            }
            if (res.status === 403) {
                return { isValid: false, status: 403 };
            }
            if (!res.ok) {
                res = await fetch(url, { method: "GET" });
                if (!res.ok) {
                    return { isValid: false, status: res.status };
                }
            }
            return { isValid: true };
        }
        catch (error) {
            console.debug(`Error checking link ${url}:`, error);
            return { isValid: false, status: 429 };
        }
    }
    return { isValid: false, status: 429 };
};
const checkUrl = async (config, filePath, fileName, exercise, errors, warnings, counter) => {
    if (!fs.existsSync(filePath))
        return false;
    const content = fs.readFileSync(filePath).toString();
    const isEmpty = checkForEmptySpaces(content);
    if (isEmpty || !content)
        errors.push({
            exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
            msg: `This file (${fileName}) doesn't have any content inside.`,
        });
    const frontmatter = fm(content);
    for (const attribute in frontmatter.attributes) {
        if (Object.prototype.hasOwnProperty.call(frontmatter.attributes, attribute) &&
            (attribute === "intro" || attribute === "tutorial")) {
            counter && counter.links.total++;
            const url = frontmatter.attributes[attribute];
            const { isValid, status } = await checkLinkWithRetry(url);
            if (!isValid) {
                if (status === 429 || status === 403) {
                    warnings.push({
                        exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                        msg: `Warning: This link might be temporarily inaccessible (${status}): ${url}`,
                    });
                }
                else {
                    counter && counter.links.error++;
                    errors.push({
                        exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                        msg: `This link is broken: ${url}`,
                    });
                }
            }
        }
    }
    // Check URLs in README files
    const findings = findInFile(["relativeImages", "externalImages", "markdownLinks"], content);
    for (const finding in findings) {
        if (Object.prototype.hasOwnProperty.call(findings, finding)) {
            const obj = findings[finding];
            if (finding === "externalImages" && Object.keys(obj).length > 0) {
                for (const img in obj) {
                    if (Object.prototype.hasOwnProperty.call(obj, img)) {
                        counter && counter.images.total++;
                        const url = obj[img].absUrl;
                        const { isValid, status } = await checkLinkWithRetry(url);
                        if (!isValid) {
                            if (status === 429 || status === 403) {
                                warnings.push({
                                    exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                                    msg: `Warning: This image link might be temporarily inaccessible (${status}): ${url}`,
                                });
                            }
                            else {
                                counter && counter.images.error++;
                                errors.push({
                                    exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                                    msg: `This image link is broken: ${url}`,
                                });
                            }
                        }
                    }
                }
            }
            else if (finding === "markdownLinks" && Object.keys(obj).length > 0) {
                for (const link in obj) {
                    if (Object.prototype.hasOwnProperty.call(obj, link)) {
                        counter && counter.links.total++;
                        const url = obj[link].mdUrl;
                        if (!url.includes("twitter")) {
                            const { isValid, status } = await checkLinkWithRetry(url);
                            if (!isValid) {
                                if (status === 429 || status === 403) {
                                    warnings.push({
                                        exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                                        msg: `Warning: This markdown link might be temporarily inaccessible (${status}): ${url}`,
                                    });
                                }
                                else {
                                    counter && counter.links.error++;
                                    errors.push({
                                        exercise: exercise === null || exercise === void 0 ? void 0 : exercise.title,
                                        msg: `This markdown link is broken: ${url}`,
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return true;
};
// This function writes a file in the given path.
const writeFile = (filePath, content) => {
    try {
        fs.writeFileSync(filePath, content);
    }
    catch (error) {
        if (error)
            console_1.default.error(`We weren't able to write the file in this path "${filePath}".`, error);
    }
};
// This function checks if there are errors, and show them in the console at the end.
const showErrors = (errors, counter) => {
    return new Promise((resolve, reject) => {
        if (errors) {
            if (errors.length > 0) {
                console_1.default.log("Checking for errors...");
                for (const [i, error] of errors.entries())
                    console_1.default.error(`${i + 1}) ${error.msg} ${error.exercise ? `(Exercise: ${error.exercise})` : ""}`);
                if (counter) {
                    console_1.default.error(` We found ${errors.length} error${errors.length > 1 ? "s" : ""} among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`);
                }
                else {
                    console_1.default.error(` We found ${errors.length} error${errors.length > 1 ? "s" : ""} related with the project integrity.`);
                }
                process.exit(1);
            }
            else {
                if (counter) {
                    console_1.default.success(`We didn't find any errors in this repository among ${counter.images.total} images, ${counter.links.total} link, ${counter.readmeFiles} README files and ${counter.exercises} exercises.`);
                }
                else {
                    console_1.default.success(`We didn't find any errors in this repository.`);
                }
                process.exit(0);
            }
        }
        else {
            reject("Failed");
        }
    });
};
// This function checks if there are warnings, and show them in the console at the end.
const showWarnings = (warnings) => {
    return new Promise((resolve, reject) => {
        if (warnings) {
            if (warnings.length > 0) {
                console_1.default.log("Checking for warnings...");
                for (const [i, warning] of warnings.entries())
                    console_1.default.warning(`${i + 1}) ${warning.msg} ${warning.exercise ? `File: ${warning.exercise}` : ""}`);
            }
            resolve("SUCCESS");
        }
        else {
            reject("Failed");
        }
    });
};
exports.default = {
    isUrl,
    checkForEmptySpaces,
    checkLearnpackClean,
    findInFile,
    checkUrl,
    writeFile,
    showErrors,
    showWarnings,
    checkSlug,
};
