const semver = require('semver');

let changelogDeps = {};
let loopVersion = '';
let uniqueCommits = [];
let isMajor = false;
let isMinor = false;
const issueLinks = function (item) {
    if (typeof loopVersion !== 'string') {
        return item;
    }
    const repository = semver.gte(loopVersion, '2.4.0') ? 'fomantic/Fomantic-UI' : 'Semantic-Org/Semantic-UI';
    item.subject = item.subject.replace(/#(\d+)([ ,]|$)/, '[`#$1`](https://github.com/' + repository + '/issues/$1) ');

    return item;
};

module.exports = function (Handlebars) {
    Handlebars.registerHelper('commit-collector', (merges, commits, major, minor, options) => {
        const commitsFromMerges = merges.map((merge) => merge.commit);
        const result = [...commits, ...commitsFromMerges];
        isMajor = major;
        isMinor = minor;

        return options.fn(result);
    });

    Handlebars.registerHelper('commit-list-breaking', (context, options) => (isMajor || isMinor ? Handlebars.helpers['commit-list-enhanced'](context, options) : ''));

    Handlebars.registerHelper('commit-list-enhanced', (context, options) => {
        const {
            exclude,
            message,
            subject,
            heading,
        } = options.hash;

        if (!context || context.length === 0 || !heading) {
            return '';
        }

        loopVersion = options.data.root.releases[options.data.index].tag;
        uniqueCommits = [];

        let list = context
            .filter((item) => {
                const commit = item.commit || item;
                if (exclude) {
                    const pattern = new RegExp(exclude, 'm');
                    if (pattern.test(commit.message)) {
                        return false;
                    }
                }
                if (message) {
                    const pattern = new RegExp(message, 'm');

                    return pattern.test(commit.message);
                }
                if (subject) {
                    const pattern = new RegExp(subject);

                    return pattern.test(commit.subject);
                }

                return true;
            })
            .filter((item) => {
                if (uniqueCommits.includes(item.subject)) {
                    return false;
                }
                uniqueCommits.push(item.subject);

                return true;
            })
            .map(issueLinks)
            .map((item) => options.fn(item));

        if (list.length === 0) {
            return '';
        }

        list = list.filter((item) => item.trim() !== '').sort().join('');

        let returnString = '';
        if (heading) {
            returnString = `${heading}\n\n`;
        }
        if (list) {
            returnString += `${list}\n`;
        }

        return returnString;
    });

    Handlebars.registerHelper('noprefix', (text) => {
        if (!(typeof text === 'string')) {
            return '';
        }
        const result = text.replace(/^(\w+(\([^()]+\))?:|\[[\w ]+]) */, '');

        return new Handlebars.SafeString(result);
    });

    Handlebars.registerHelper('contributorlink', (text) => {
        if (!(typeof text === 'string')) {
            return '';
        }
        const result = text.replace(/add ([\dA-Za-z-]+) as a contributor/, '[`$1`](https://github.com/$1)');

        return Handlebars.helpers.noprefix(result);
    });

    Handlebars.registerHelper('commit-list-dependencies', (context, options) => {
        const {
            exclude,
            message,
            subject,
            heading,
        } = options.hash;

        if (!context || context.length === 0 || !heading) {
            return '';
        }

        loopVersion = options.data.root.releases[options.data.index].tag;

        changelogDeps = {};
        const depsRegex = /(?:build\(deps[A-Za-z-]*\):|\[Snyk]) (?:\[?[Ss]ecurity]? )?(?:bump|upgrade) ([\w./@-]+) from (\d+\.\d+\.\d+) to (\d+\.\d+\.\d+)/;
        const detectVersionRange = function (item) {
            const subjectDetails = item.subject.match(depsRegex);
            if (!subjectDetails) {
                return true;
            }

            const depPackage = subjectDetails[1];
            const depVersionFrom = subjectDetails[2];
            const depVersionTo = subjectDetails[3];
            if (!changelogDeps[depPackage]) {
                changelogDeps[depPackage] = {
                    from: '999.999.999',
                    to: '0.0.0',
                };
            }
            if (semver.lt(depVersionFrom, changelogDeps[depPackage].from)) {
                changelogDeps[depPackage].from = depVersionFrom;
            }
            if (semver.gte(depVersionTo, changelogDeps[depPackage].to)) {
                changelogDeps[depPackage].to = depVersionTo;

                return true;
            }

            return false;
        };
        uniqueCommits = [];
        const list = context
            .filter((item) => {
                const commit = item.commit || item;
                if (exclude) {
                    const pattern = new RegExp(exclude, 'm');
                    if (pattern.test(commit.message)) {
                        return false;
                    }
                }
                if (message) {
                    const pattern = new RegExp(message, 'm');

                    return pattern.test(commit.message);
                }
                if (subject) {
                    const pattern = new RegExp(subject);

                    return pattern.test(commit.subject);
                }

                return true;
            })
            .filter((item) => {
                if (uniqueCommits.includes(item.subject)) {
                    return false;
                }
                uniqueCommits.push(item.subject);

                return true;
            })
            .filter(detectVersionRange)
            // second round as the previous list came unordered from git
            .filter(detectVersionRange)
            // adjust from version to create the whole range in one line (linked to the latest commit)
            .map((item) => {
                const subjectDetails = item.subject.match(depsRegex);
                if (!subjectDetails) {
                    return item;
                }
                const depPackage = subjectDetails[1];
                const depVersionFrom = subjectDetails[2];
                item.subject = item.subject.replace(depVersionFrom, changelogDeps[depPackage].from);

                return item;
            })
            .map(issueLinks)
            .map((item) => options.fn(item))
            .sort()
            .join('');
        if (!list) {
            return '';
        }

        if (!heading) {
            return `${list}\n`;
        }

        return `${heading}\n\n${list}\n`;
    });
};
