/**
 * Worker utilities — standalone helper functions used by SubAgentRuntime,
 * WorktreeSession, and other phantom modules.
 */
import { execAsync as exec } from '../../utils/exec-async.js';
import * as fs from 'fs';
import * as path from 'path';
// ─── shell helpers ────────────────────────────────────────────────────────────
/** Shell-safe single-quote wrapper. */
export function quote(value) {
    return `'${value.replace(/'/g, `'\\''`)}'`;
}
/**
 * Run a shell command in the given cwd. Returns a CommandRecord on success.
 * Throws (or returns the record with exitCode≠0) when allowFailure is false.
 */
export async function runCommand(cwd, command, allowFailure = false, timeoutMs) {
    try {
        const { stdout, stderr } = await exec(command, {
            cwd,
            maxBuffer: 1024 * 1024 * 20,
            ...(timeoutMs && timeoutMs > 0 ? { timeout: timeoutMs } : {}),
        });
        return { command, cwd, exitCode: 0, stdout, stderr };
    }
    catch (error) {
        const record = {
            command,
            cwd,
            exitCode: typeof error?.code === 'number' ? error.code : 1,
            stdout: String(error?.stdout ?? ''),
            stderr: String(error?.stderr ?? error?.message ?? ''),
        };
        if (!allowFailure)
            throw new Error(record.stderr || record.stdout || `Command failed: ${command}`);
        return record;
    }
}
// ─── file utilities ───────────────────────────────────────────────────────────
/** Converts an absolute path to a path relative to root; returns original if it escapes root. */
export function relativeTo(root, target) {
    const rel = path.relative(root, target);
    return rel.startsWith('..') ? target : rel;
}
/** Sanitise an arbitrary string into a safe filename component (≤60 chars). */
export function sanitizeFileName(value) {
    return value.replace(/[^a-z0-9-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 60) || 'command';
}
/**
 * Walk the repo for source/test files using git ls-files (with a filesystem
 * fallback). Returns FileRef[] with path, sizeBytes, and lastModified.
 */
export async function scanFiles(repoRoot) {
    const candidates = [
        path.join(repoRoot, 'src'),
        path.join(repoRoot, 'test'),
        path.join(repoRoot, 'README.md'),
    ];
    const files = [];
    try {
        const { stdout } = await exec('git ls-files -z', { cwd: repoRoot });
        const tracked = stdout.split('\0').filter(Boolean);
        await Promise.all(tracked.map(async (relativePath) => {
            if (!relativePath.startsWith('src/') && !relativePath.startsWith('test/') && relativePath !== 'README.md')
                return;
            const target = path.join(repoRoot, relativePath);
            try {
                const stat = await fs.promises.stat(target);
                if (stat.isFile() && stat.size <= 200_000) {
                    files.push({ path: target, sizeBytes: stat.size, lastModified: stat.mtimeMs });
                }
            }
            catch {
                // Ignore inaccessible files.
            }
        }));
        if (files.length > 0)
            return files;
    }
    catch {
        // Not a git repo or git not installed, fallback below.
    }
    const visit = async (target) => {
        const stat = await fs.promises.stat(target).catch(() => null);
        if (!stat)
            return;
        if (stat.isFile() && stat.size <= 200_000) {
            files.push({ path: target, sizeBytes: stat.size, lastModified: stat.mtimeMs });
            return;
        }
        let entries;
        try {
            entries = await fs.promises.readdir(target, { withFileTypes: true });
        }
        catch (e) {
            if (e.code === 'EACCES') {
                throw new Error(`EACCES Permission Denied: Nexus Prime scanner requires elevated permissions to access '${target}'. Please run the daemon/installation with 'sudo' or grant Full Disk Access.`);
            }
            return;
        }
        await Promise.all(entries
            .filter(entry => entry.name !== 'node_modules' && entry.name !== 'dist' && !entry.name.startsWith('.git'))
            .map(entry => visit(path.join(target, entry.name))));
    };
    await Promise.all(candidates.map(visit));
    return files;
}
// ─── type guards ──────────────────────────────────────────────────────────────
/** Type guard: true if value is a MemoryBackend (has descriptor property). */
export function isMemoryBackend(value) {
    return !!value && typeof value === 'object' && 'descriptor' in value;
}
// ─── dedup utilities ──────────────────────────────────────────────────────────
export function dedupeStrings(values) {
    return [...new Set(values.filter(Boolean))];
}
export function dedupeSkillArtifacts(values) {
    const seen = new Set();
    return values.filter((value) => {
        const key = value.name.toLowerCase();
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
export function dedupeWorkflowArtifacts(values) {
    const seen = new Set();
    return values.filter((value) => {
        const key = value.name.toLowerCase();
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
export function dedupeHookArtifacts(values) {
    const seen = new Set();
    return values.filter((value) => {
        const key = value.name.toLowerCase();
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
export function dedupeAutomationArtifacts(values) {
    const seen = new Set();
    return values.filter((value) => {
        const key = value.name.toLowerCase();
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
// ─── tool-binding helpers ─────────────────────────────────────────────────────
/**
 * Intersect allowedTools with defaults. Falls back to defaults when
 * allowedTools is empty or produces no valid intersection.
 */
export function scopedTools(allowedTools, defaults) {
    if (!allowedTools || allowedTools.length === 0)
        return defaults;
    const scoped = defaults.filter((tool) => allowedTools.includes(tool));
    return scoped.length > 0 ? scoped : defaults;
}
/** Filter bindings to only those whose type is in allowedTools. */
export function filterBindingsByTools(bindings, allowedTools) {
    const allowed = new Set(allowedTools);
    return bindings.filter((binding) => allowed.has(binding.type));
}
// ─── token / run helpers ──────────────────────────────────────────────────────
/** Rough token count estimator — 1 token ≈ 4 chars of text / JSON. */
export function estimateTokenCount(value) {
    if (typeof value !== 'string') {
        try {
            return Math.ceil(JSON.stringify(value ?? '').length / 4);
        }
        catch {
            return 0;
        }
    }
    return Math.ceil(value.length / 4);
}
/**
 * Extract the 13-digit millisecond timestamp embedded in a runId.
 * Returns 0 if no match is found.
 */
export function extractRunTimestamp(run) {
    const match = run.runId.match(/(\d{13})/);
    return match ? Number(match[1]) : 0;
}
