import { spawn } from 'spawn-but-with-promises';
import delay from 'delay';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const TIMEOUT = 600000;
const EXTRASPACE = new RegExp('\\s+', 'g');
const CHECKUPDATESCACHE = path.join(os.homedir(), '.cache', 'artix-checkupdates');
const fsp = fs.promises;
const NAMECOMPLIANCE = [
    (p) => p.replace(/([a-zA-Z0-9]+)\+([a-zA-Z]+)/g, '$1-$2'),
    (p) => p.replace(/\+/g, "plus"),
    (p) => p.replace(/[^a-zA-Z0-9_\-\.]/g, "-"),
    (p) => p.replace(/[_\-]{2,}/g, "-")
];
export class Checkupdates {
    _timeout;
    constructor(options = {}) {
        this._timeout = options.timeout || TIMEOUT;
    }
    async cleanUpLockfiles() {
        try {
            await fsp.rm(CHECKUPDATESCACHE, { recursive: true, force: true });
        }
        catch (ex) {
            console.error('Failed to remove the artix-checkupdates cache directory:', ex);
        }
    }
    applyCompliance(str) {
        return NAMECOMPLIANCE.reduce((s, fn) => fn(s), str);
    }
    parseCheckUpdatesOutput(output, applyCompliance = false) {
        const packages = [];
        const lines = output.split('\n');
        lines.forEach(l => {
            // "package" is "reserved"
            const reservethis = l.trim().replace(EXTRASPACE, ' ');
            if (reservethis.length > 0 && reservethis.indexOf('Package basename') < 0) {
                const cols = reservethis.split(' ');
                const basename = cols[0] || '';
                packages.push({
                    basename: applyCompliance ? this.applyCompliance(basename) : basename,
                    artixRepo: cols[1],
                    artixVersion: cols[2] || '',
                    archRepo: cols[3],
                    archVersion: cols[4],
                    packager: cols[5] || ''
                });
            }
        });
        return packages;
    }
    async checkupdates(flag, applyCompliance = false) {
        const process = spawn('artix-checkupdates', [flag]);
        const to = setTimeout(async () => {
            process.kill() && await this.cleanUpLockfiles();
            throw new Error('Timed out');
        }, this._timeout);
        let outputstr = '';
        let errorOutput = '';
        process.stdout.on('data', data => {
            outputstr += data.toString();
        });
        process.stderr.on('data', err => {
            const errstr = err.toString();
            errorOutput += `${errstr}, `;
            console.error(errstr);
        });
        const code = await process;
        clearTimeout(to);
        if (code !== 0 || errorOutput.length !== 0) {
            errorOutput.includes('unable to lock database') && this.cleanUpLockfiles();
            throw new Error((code && `exited with ${code}`) || errorOutput);
        }
        else {
            return this.parseCheckUpdatesOutput(outputstr, applyCompliance);
        }
    }
    async retryCheckupdates(flag, applyCompliance = false, retries = 1, pause = 2300) {
        let tries = 0;
        while (retries > tries++) {
            try {
                return this.checkupdates(flag, applyCompliance);
            }
            catch {
                await delay(pause);
            }
        }
        return this.checkupdates(flag, applyCompliance);
    }
    fetchUpgradable(applyCompliance = false, retries = 0) {
        return this.retryCheckupdates('-u', applyCompliance, retries);
    }
    fetchMovable(applyCompliance = false, retries = 0) {
        return this.retryCheckupdates('-m', applyCompliance, retries);
    }
    fetchLooseMovable(applyCompliance = false, retries = 0) {
        return this.retryCheckupdates('-ml', applyCompliance, retries);
    }
}
export default Checkupdates;
