import { Command, flags } from '@heroku-cli/command';
import { color, hux } from '@heroku/heroku-cli-util';
import { ux } from '@oclif/core/ux';
import tsheredoc from 'tsheredoc';
import { ago } from '../../lib/time.js';
import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
const heredoc = tsheredoc.default;
export default class Index extends Command {
    static description = 'list dynos for an app';
    static examples = [heredoc `
    ${color.command('heroku ps')}
    === run: one-off dyno
    run.1: up for 5m: bash
    === web: bundle exec thin start -p $PORT
    web.1: created for 30s
  `, heredoc `
    ${color.command('heroku ps run')} # specifying types
    === run: one-off dyno
    run.1: up for 5m: bash
  `];
    static flags = {
        app: flags.app({ required: true }),
        extended: flags.boolean({ char: 'x', hidden: true }),
        json: flags.boolean({ description: 'display as json' }),
        'no-wrap': flags.noWrap(),
        remote: flags.remote(),
    };
    static strict = false;
    static topic = 'ps';
    static usage = 'ps [TYPE [TYPE ...]]';
    async run() {
        const { flags, ...restParse } = await this.parse(Index);
        const { app, extended, json } = flags;
        const types = restParse.argv;
        const suffix = extended ? '?extended=true' : '';
        const promises = {
            accountInfo: this.heroku.request('/account', {
                headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
            }),
            appInfo: this.heroku.request(`/apps/${app}`, {
                headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
            }),
            dynos: this.heroku.request(`/apps/${app}/dynos${suffix}`, {
                headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
            }),
        };
        const [{ body: dynos }, { body: appInfo }, { body: accountInfo }] = await Promise.all([promises.dynos, promises.appInfo, promises.accountInfo]);
        const shielded = appInfo.space && appInfo.space.shield;
        if (shielded) {
            dynos.forEach(d => {
                d.size = d.size.replace('Private-', 'Shield-');
            });
        }
        let selectedDynos = dynos;
        if (types.length > 0) {
            selectedDynos = selectedDynos.filter(dyno => types.find((t) => dyno.type === t));
            types.forEach(t => {
                if (!selectedDynos.some(d => d.type === t)) {
                    throw new Error(`No ${color.info(t)} dynos on ${color.app(app)}`);
                }
            });
        }
        selectedDynos = selectedDynos.sort(byProcessName);
        if (json)
            hux.styledJSON(selectedDynos);
        else if (extended)
            printExtended(selectedDynos, flags['no-wrap']);
        else {
            await printAccountQuota(this.heroku, appInfo, accountInfo);
            if (selectedDynos.length === 0)
                ux.stdout(`No dynos on ${color.app(app)}`);
            else
                printDynos(selectedDynos);
        }
    }
}
function byProcessName(a, b) {
    if (a.name > b.name) {
        return 1;
    }
    if (b.name > a.name) {
        return -1;
    }
    return 0;
}
function byProcessNumber(a, b) {
    return getProcessNumber(a.name) - getProcessNumber(b.name);
}
function byProcessTypeAndNumber(a, b) {
    if (a.type > b.type) {
        return 1;
    }
    if (b.type > a.type) {
        return -1;
    }
    return getProcessNumber(a.name) - getProcessNumber(b.name);
}
function decorateCommandDyno(dyno) {
    const since = ago(new Date(dyno.updated_at));
    const state = dyno.state === 'up' ? color.success(dyno.state) : color.warning(dyno.state);
    return `${dyno.name}: ${state} ${color.gray(since)}`;
}
function decorateOneOffDyno(dyno) {
    const since = ago(new Date(dyno.updated_at));
    // eslint-disable-next-line unicorn/explicit-length-check
    const size = dyno.size || '1X';
    const state = dyno.state === 'up' ? color.success(dyno.state) : color.warning(dyno.state);
    return `${dyno.name} (${color.info(size)}): ${state} ${color.gray(since)}: ${dyno.command}`;
}
function getProcessNumber(s) {
    const [processType, dynoNumber] = (s.match(/^([^.]+)\.(.*)$/) || []).slice(1, 3);
    if (!processType || !dynoNumber?.match(/^\d+$/))
        return 0;
    return Number.parseInt(dynoNumber, 10);
}
async function printAccountQuota(heroku, app, account) {
    if (app.process_tier !== 'eco') {
        return;
    }
    if (app.owner.id !== account.id) {
        return;
    }
    const { body: quota } = await heroku.request(`/accounts/${account.id}/actions/get-quota`, { headers: { Accept: 'application/vnd.heroku+json; version=3.account-quotas' } }).catch(() => ({ body: null }));
    if (!quota || (quota.id && quota.id === 'not_found')) {
        return;
    }
    const remaining = (quota.account_quota === 0) ? 0 : quota.account_quota - quota.quota_used;
    const percentage = (quota.account_quota === 0) ? 0 : Math.floor(remaining / quota.account_quota * 100);
    const remainingMinutes = remaining / 60;
    const hours = Math.floor(remainingMinutes / 60);
    const minutes = Math.floor(remainingMinutes % 60);
    const appQuota = quota.apps.find(appQuota => appQuota.app_uuid === app.id);
    const appQuotaUsed = appQuota ? appQuota.quota_used / 60 : 0;
    const appPercentage = appQuota ? Math.floor(appQuota.quota_used * 100 / quota.account_quota) : 0;
    const appHours = Math.floor(appQuotaUsed / 60);
    const appMinutes = Math.floor(appQuotaUsed % 60);
    ux.stdout(`Eco dyno hours quota remaining this month: ${hours}h ${minutes}m (${percentage}%)`);
    ux.stdout(`Eco dyno usage for this app: ${appHours}h ${appMinutes}m (${appPercentage}%)`);
    ux.stdout('For more information on Eco dyno hours, see:');
    ux.stdout(color.info('https://devcenter.heroku.com/articles/eco-dyno-hours'));
    ux.stdout();
}
function printDynos(dynos) {
    const oneOffs = dynos.filter(d => d.type === 'run').sort(byProcessNumber);
    const commands = dynos.filter(d => d.type !== 'run')
        .map(d => d.command)
        .filter(uniqueValues);
    // Print one-off dynos
    if (oneOffs.length > 0) {
        hux.styledHeader(`${color.label('run')}: one-off processes (${oneOffs.length})`);
        oneOffs.forEach(dyno => ux.stdout(decorateOneOffDyno(dyno)));
        ux.stdout();
    }
    // Print dynos grouped by command
    commands.forEach(command => {
        const commandDynos = dynos.filter(d => d.command === command).sort(byProcessNumber);
        const { size = '1X', type } = commandDynos[0];
        hux.styledHeader(`${color.label(type)} (${color.info(size)}): ${command} (${commandDynos.length})`);
        for (const dyno of commandDynos)
            ux.stdout(decorateCommandDyno(dyno));
        ux.stdout();
    });
}
function printExtended(dynos, noWrap = false) {
    const sortedDynos = dynos.sort(byProcessTypeAndNumber);
    /* eslint-disable perfectionist/sort-objects */
    hux.table(sortedDynos, {
        ID: { get: (dyno) => dyno.id },
        Process: { get: (dyno) => dyno.name },
        State: { get: (dyno) => `${dyno.state} ${ago(new Date(dyno.updated_at))}` },
        Region: { get: (dyno) => dyno.extended?.region ?? '' },
        'Execution Plane': { get: (dyno) => dyno.extended?.execution_plane ?? '' },
        Fleet: { get: (dyno) => dyno.extended?.fleet ?? '' },
        Instance: { get: (dyno) => dyno.extended?.instance ?? '' },
        IP: { get: (dyno) => dyno.extended?.ip ?? '' },
        Port: { get: (dyno) => dyno.extended?.port?.toString() ?? '' },
        AZ: { get: (dyno) => dyno.extended?.az ?? '' },
        Release: { get: (dyno) => dyno.release.version },
        Command: { get: (dyno) => truncate(dyno.command) },
        Route: { get: (dyno) => dyno.extended?.route ?? '' },
        Size: { get: (dyno) => dyno.size },
    }, huxTableNoWrapOptions(noWrap));
    /* eslint-enable perfectionist/sort-objects */
}
function truncate(s) {
    return s.length > 35 ? `${s.slice(0, 34)}…` : s;
}
function uniqueValues(value, index, self) {
    return self.indexOf(value) === index;
}
