"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLinuxDesktopSessionContexts = getLinuxDesktopSessionContexts;
exports.quoteShellArg = quoteShellArg;
exports.runLinuxDesktopCommand = runLinuxDesktopCommand;
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const DEFAULT_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
const DESKTOP_ENV_KEYS = [
    'DISPLAY',
    'WAYLAND_DISPLAY',
    'DBUS_SESSION_BUS_ADDRESS',
    'XDG_RUNTIME_DIR',
    'XDG_SESSION_TYPE',
    'XAUTHORITY',
    'HOME',
    'USER',
    'LOGNAME',
    'PATH',
];
const RUNUSER_CANDIDATES = [
    '/usr/sbin/runuser',
    '/sbin/runuser',
    '/usr/bin/runuser',
    '/bin/runuser',
];
const SHELL_PATH = fs_1.default.existsSync('/bin/bash') ? '/bin/bash' : '/bin/sh';
const summarizeError = (error) => (String(error?.message || error || 'unknown error').trim().split('\n')[0].slice(0, 240));
const parsePasswdFile = () => {
    try {
        return fs_1.default.readFileSync('/etc/passwd', 'utf8')
            .split(/\r?\n/)
            .map((line) => line.trim())
            .filter(Boolean)
            .map((line) => {
            const parts = line.split(':');
            return {
                name: String(parts[0] || '').trim(),
                uid: Number(parts[2]),
                home: String(parts[5] || '').trim(),
            };
        })
            .filter((entry) => !!entry.name && Number.isFinite(entry.uid));
    }
    catch {
        return [];
    }
};
const lookupUserNameByUid = (uid) => (parsePasswdFile().find((entry) => entry.uid === uid)?.name || '');
const lookupHomeDirectory = (user) => {
    const normalized = String(user || '').trim();
    if (!normalized)
        return '';
    return parsePasswdFile().find((entry) => entry.name === normalized)?.home || '';
};
const parseProcEnvironment = (raw) => {
    const env = {};
    for (const entry of raw.toString('utf8').split('\0')) {
        const index = entry.indexOf('=');
        if (index <= 0)
            continue;
        const key = entry.slice(0, index).trim();
        if (!key)
            continue;
        env[key] = entry.slice(index + 1);
    }
    return env;
};
const readProcUid = (pid) => {
    try {
        const status = fs_1.default.readFileSync(path_1.default.join('/proc', pid, 'status'), 'utf8');
        const match = status.match(/^Uid:\s+(\d+)/m);
        if (match) {
            return Number(match[1]);
        }
    }
    catch { }
    return -1;
};
const buildDesktopEnv = (sourceEnv, userHint = '') => {
    const env = {};
    for (const key of DESKTOP_ENV_KEYS) {
        const value = String(sourceEnv[key] || '').trim();
        if (value)
            env[key] = value;
    }
    const user = String(userHint || env.USER || env.LOGNAME || '').trim();
    if (!env.USER && user)
        env.USER = user;
    if (!env.LOGNAME && user)
        env.LOGNAME = user;
    if (!env.HOME) {
        const home = lookupHomeDirectory(user) || String(sourceEnv.HOME || '').trim() || (user && user === process.env.USER ? os_1.default.homedir() : '');
        if (home)
            env.HOME = home;
    }
    if (!env.PATH) {
        env.PATH = String(sourceEnv.PATH || process.env.PATH || DEFAULT_PATH).trim() || DEFAULT_PATH;
    }
    if (!env.XAUTHORITY && env.DISPLAY && env.HOME) {
        const xauthority = path_1.default.join(env.HOME, '.Xauthority');
        if (fs_1.default.existsSync(xauthority)) {
            env.XAUTHORITY = xauthority;
        }
    }
    if (!env.DBUS_SESSION_BUS_ADDRESS && env.XDG_RUNTIME_DIR) {
        const runtimeBus = path_1.default.join(env.XDG_RUNTIME_DIR, 'bus');
        if (fs_1.default.existsSync(runtimeBus)) {
            env.DBUS_SESSION_BUS_ADDRESS = `unix:path=${runtimeBus}`;
        }
    }
    return env;
};
const hasDesktopSessionHints = (env) => (!!String(env.DISPLAY || '').trim() || !!String(env.WAYLAND_DISPLAY || '').trim());
function getLinuxDesktopSessionContexts() {
    if (process.platform !== 'linux')
        return [];
    const contexts = [];
    const pushContext = (context) => {
        const normalizedEnv = buildDesktopEnv(context.env, context.user);
        if (!hasDesktopSessionHints(normalizedEnv))
            return;
        contexts.push({
            user: String(context.user || normalizedEnv.USER || normalizedEnv.LOGNAME || '').trim(),
            env: normalizedEnv,
            source: String(context.source || 'unknown'),
        });
    };
    const currentEnv = buildDesktopEnv(process.env, String(process.env.SUDO_USER || process.env.USER || process.env.LOGNAME || '').trim());
    if (hasDesktopSessionHints(currentEnv)) {
        pushContext({
            user: String(currentEnv.USER || currentEnv.LOGNAME || '').trim(),
            env: currentEnv,
            source: 'process:current',
        });
    }
    try {
        const procEntries = fs_1.default.readdirSync('/proc', { withFileTypes: true });
        for (const entry of procEntries) {
            if (!entry.isDirectory() || !/^\d+$/.test(entry.name))
                continue;
            try {
                const environ = parseProcEnvironment(fs_1.default.readFileSync(path_1.default.join('/proc', entry.name, 'environ')));
                if (!hasDesktopSessionHints(buildDesktopEnv(environ)))
                    continue;
                const uid = readProcUid(entry.name);
                const user = String(environ.USER || environ.LOGNAME || lookupUserNameByUid(uid) || '').trim();
                const comm = fs_1.default.existsSync(path_1.default.join('/proc', entry.name, 'comm'))
                    ? fs_1.default.readFileSync(path_1.default.join('/proc', entry.name, 'comm'), 'utf8').trim()
                    : '';
                pushContext({
                    user,
                    env: environ,
                    source: `proc:${entry.name}${comm ? `:${comm}` : ''}`,
                });
            }
            catch {
                // Skip inaccessible or short-lived processes.
            }
        }
    }
    catch {
        // Ignore proc enumeration failures and fall back to current process env.
    }
    const seen = new Set();
    const deduped = contexts.filter((context) => {
        const signature = [
            context.user,
            context.env.DISPLAY || '',
            context.env.WAYLAND_DISPLAY || '',
            context.env.XDG_RUNTIME_DIR || '',
            context.env.XAUTHORITY || '',
        ].join('|');
        if (seen.has(signature))
            return false;
        seen.add(signature);
        return true;
    });
    deduped.sort((a, b) => {
        const score = (context) => {
            let value = 0;
            if (context.source === 'process:current')
                value += 20;
            if (context.user && context.user !== 'root')
                value += 10;
            if (context.env.XAUTHORITY)
                value += 4;
            if (context.env.XDG_RUNTIME_DIR)
                value += 3;
            if (context.env.WAYLAND_DISPLAY)
                value += 2;
            if (context.env.DISPLAY)
                value += 1;
            return value;
        };
        return score(b) - score(a);
    });
    return deduped;
}
const execFileText = (command, args, options) => new Promise((resolve, reject) => {
    (0, child_process_1.execFile)(command, args, {
        timeout: options.timeout,
        env: options.env,
        windowsHide: true,
        maxBuffer: 8 * 1024 * 1024,
    }, (error, stdout, stderr) => {
        if (error) {
            reject(new Error(String(stderr || stdout || error.message || 'Command failed').trim() || 'Command failed'));
            return;
        }
        resolve(String(stdout || stderr || '').trim());
    });
});
function quoteShellArg(value) {
    return `'${String(value ?? '').replace(/'/g, `'\"'\"'`)}'`;
}
async function runLinuxDesktopCommand(command, timeoutMs = 15000) {
    if (process.platform !== 'linux') {
        throw new Error(`Linux desktop commands are unsupported on platform '${process.platform}'`);
    }
    const contexts = getLinuxDesktopSessionContexts();
    if (contexts.length === 0) {
        throw new Error('No Linux desktop session detected');
    }
    const currentUid = typeof process.getuid === 'function' ? Number(process.getuid()) : -1;
    const currentUser = currentUid >= 0
        ? lookupUserNameByUid(currentUid) || String(process.env.USER || process.env.LOGNAME || '').trim()
        : String(process.env.USER || process.env.LOGNAME || '').trim();
    const runningAsRoot = currentUid === 0;
    const errors = [];
    for (const context of contexts) {
        const mergedEnv = {
            ...process.env,
            ...context.env,
            PATH: String(context.env.PATH || process.env.PATH || DEFAULT_PATH).trim() || DEFAULT_PATH,
        };
        try {
            if (runningAsRoot && context.user && context.user !== 'root' && context.user !== currentUser) {
                const runuser = RUNUSER_CANDIDATES.find((candidate) => fs_1.default.existsSync(candidate));
                if (runuser) {
                    const envArgs = Object.entries(context.env)
                        .filter(([, value]) => !!String(value || '').trim())
                        .map(([key, value]) => `${key}=${value}`);
                    return await execFileText(runuser, ['-u', context.user, '--', 'env', ...envArgs, SHELL_PATH, '-lc', command], {
                        timeout: timeoutMs,
                        env: { ...process.env, PATH: mergedEnv.PATH },
                    });
                }
            }
            return await execFileText(SHELL_PATH, ['-lc', command], {
                timeout: timeoutMs,
                env: mergedEnv,
            });
        }
        catch (error) {
            errors.push(`${context.source}${context.user ? `/${context.user}` : ''}: ${summarizeError(error)}`);
        }
    }
    throw new Error(`Linux desktop command failed: ${errors.join(' | ').slice(0, 700)}`);
}
//# sourceMappingURL=linux-desktop.js.map