#!/usr/bin/env node

const { ESLint } = require('eslint');
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

function createConfig(config) {
    const options = {
        cache: true,
        cacheLocation: `.eslintcache-${config}`,
    };

    if (config === 'legacy')
        options.overrideConfigFile='lint/eslintrc-legacy.yml';

    return new ESLint(options);
}

function git(...args) {
    const git = spawn('git', args, { stdio: ['ignore', null, 'ignore'] });
    git.stdout.setEncoding('utf8');

    return new Promise(resolve => {
        let out = '';
        git.stdout.on('data', chunk => out += chunk);
        git.stdout.on('end', () => resolve(out.trim()));
    });
}

function createCommon(report1, report2, ignoreColumn=false) {
    return report1.map(result => {
        const { filePath, messages } = result;
        const match =
            report2.find(r => r.filePath === filePath) || { messages: [] };

        const filteredMessages = messages.filter(
            msg => match.messages.some(
                m => m.line === msg.line && (ignoreColumn || m.column === msg.column)));

        const [errorCount, warningCount] = filteredMessages.reduce(
            ([e, w], msg) => {
                return [
                    e + Number(msg.severity === 2),
                    w + Number(msg.severity === 1)];
            }, [0, 0]);

        return {
            filePath,
            messages: filteredMessages,
            errorCount,
            warningCount,
        };
    });
}

async function getMergeRequestChanges(remote, branch) {
    await git('fetch', remote, branch);
    const branchPoint = await git('merge-base', 'HEAD', 'FETCH_HEAD');
    const diff = await git('diff', '-U0', `${branchPoint}...HEAD`);

    const report = [];
    let messages = null;
    for (const line of diff.split('\n')) {
        if (line.startsWith('+++ b/')) {
            const filePath = path.resolve(line.substring(6));
            messages = filePath.endsWith('.js') ? [] : null;
            if (messages)
                report.push({ filePath, messages });
        } else if (messages && line.startsWith('@@ ')) {
            [, , changes] = line.split(' ');
            [start, count] = `${changes},1`.split(',').map(i => parseInt(i));
            for (let i = start; i < start + count; i++)
                messages.push({ line: i });
        }
    }

    return report;
}

function getOption(...names) {
    const optIndex =
        process.argv.findIndex(arg => names.includes(arg)) + 1;

    if (optIndex === 0)
        return undefined;

    return process.argv[optIndex];
}

(async function main() {
    const outputOption = getOption('--output-file', '-o');
    const outputPath = outputOption ? path.resolve(outputOption) : null;

    const sourceDir = path.dirname(process.argv[1]);
    process.chdir(path.resolve(sourceDir, '..'));

    const remote = getOption('--remote') || 'origin';
    const branch = getOption('--branch', '-b');

    const sources = ['js', 'subprojects/extensions-app/js'];
    const regular = createConfig('regular');

    const ops = [];
    ops.push(regular.lintFiles(sources));
    if (branch)
        ops.push(getMergeRequestChanges(remote, branch));
    else
        ops.push(createConfig('legacy').lintFiles(sources));

    const results = await Promise.all(ops);
    const commonResults = createCommon(...results, branch !== undefined);

    const formatter = await regular.loadFormatter(getOption('--format', '-f'));
    const resultText = formatter.format(commonResults);

    if (outputPath) {
        fs.mkdirSync(path.dirname(outputPath), { recursive: true });
        fs.writeFileSync(outputPath, resultText);
    } else {
        console.log(resultText);
    }

    process.exitCode = commonResults.some(r => r.errorCount > 0) ? 1 : 0;
})().catch((error) => {
    process.exitCode = 1;
    console.error(error);
});