/**
 * Phase 5b — Project-root sweep + KG eager-build (olam-setup-epic Phase D).
 *
 * Walks `--projects <path>` (default ~/Projects) to max-depth 3.
 * Finds repos with .adb.yaml OR .olam.yaml at repo root.
 * Calls `olam repos add` per repo (--from-sweep flag bypasses interactive confirm).
 * Optionally runs KG eager-build on first 3 repos by mtime, wall-time budget 90s.
 * Appends trust-audit-log entry `setup-project-sweep-completed` on success.
 *
 * Dependency injection: real @olam/core functions are lazily imported so this
 * module resolves in vitest without a pre-built @olam/core dist. Callers
 * inject stubs via WalkFn / EagerBuildFn / AddRepoFn parameters for unit tests.
 */
import { homedir } from 'node:os';
import * as path from 'node:path';
import * as fs from 'node:fs';
// ── Lazy real-impl loaders ────────────────────────────────────────────────────
async function loadWalkFn() {
    const m = await import('@olam/core/src/setup/project-sweep.js');
    return m.walkProjectRoot;
}
async function loadEagerBuildFn() {
    const m = await import('@olam/core/src/setup/kg-eager.js');
    return m.runEagerKgBuild;
}
async function loadAddRepoFn() {
    const m = await import('@olam/core/src/global-config/index.js');
    return m.addRepo;
}
async function loadAppendTrustAuditFn() {
    const m = await import('@olam/core/src/skill-sources/index.js');
    return m.appendTrustAudit;
}
// ── Repo name derivation ──────────────────────────────────────────────────────
/**
 * Derive a registry-safe name from a repo path.
 * Uses the basename, lowercased. If that would collide, appends the parent basename.
 */
function deriveRepoName(repoPath, existingNames) {
    const base = path.basename(repoPath).toLowerCase().replace(/[^a-z0-9-]/g, '-');
    if (!existingNames.has(base))
        return base;
    const parent = path.basename(path.dirname(repoPath)).toLowerCase().replace(/[^a-z0-9-]/g, '-');
    const composite = `${parent}-${base}`;
    if (!existingNames.has(composite))
        return composite;
    // Last resort: append a suffix
    let suffix = 2;
    while (existingNames.has(`${composite}-${suffix}`))
        suffix += 1;
    return `${composite}-${suffix}`;
}
// ── Main export ───────────────────────────────────────────────────────────────
export async function runProjectSweepPhase(opts, deps, sweepDeps = {}) {
    // 1. Defensive skip-on-flag
    if (opts.skipProjectSweep) {
        return { ok: true, skipped: true, message: 'skipped via --skip-project-sweep' };
    }
    // 2. Compute project root
    const home = deps.home ?? homedir();
    const projectsRoot = opts.projects ?? path.join(home, 'Projects');
    // 3. Projects root not found
    const existsSyncFn = sweepDeps.existsSync ?? fs.existsSync;
    if (!existsSyncFn(projectsRoot)) {
        return {
            ok: true,
            skipped: true,
            message: `${projectsRoot} not found; pass --projects <path> to specify a project root`,
        };
    }
    // 4. Walk
    const walkFn = sweepDeps.walkFn ?? (await loadWalkFn());
    let matches;
    try {
        matches = walkFn(projectsRoot, { exclude: opts.exclude ?? [] });
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return {
            ok: false,
            message: `project sweep walk failed: ${msg}`,
            remedy: `Pass --projects <path> to specify a valid project root.`,
        };
    }
    // 5. Dry-run: print matches, no side effects
    if (opts.dryRun) {
        const printInfo = sweepDeps.printInfo ?? ((scope, msg) => process.stdout.write(`[${scope}] ${msg}\n`));
        printInfo('sweep', `${projectsRoot}: ${matches.length} repo(s) found (dry-run)`);
        for (const m of matches) {
            printInfo('sweep', `  ${m.manifestType}: ${m.path}`);
        }
        return {
            ok: true,
            skipped: true,
            message: `dry-run: ${matches.length} repo(s) found in ${projectsRoot}`,
        };
    }
    if (matches.length === 0) {
        return { ok: true, message: `${projectsRoot}: 0 repos found; nothing to register` };
    }
    // 6. Add each repo via addRepo (--from-sweep bypasses confirm)
    const addRepoFn = sweepDeps.addRepoFn ?? (await loadAddRepoFn());
    const addedNames = [];
    const repoErrors = [];
    const existingNames = new Set();
    for (const match of matches) {
        const name = deriveRepoName(match.path, existingNames);
        existingNames.add(name);
        try {
            addRepoFn({ name, path: match.path });
            addedNames.push(name);
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            // "already registered" is idempotent — don't count as error
            if (/already registered/i.test(msg)) {
                addedNames.push(name);
            }
            else {
                repoErrors.push(`${match.path}: ${msg}`);
            }
        }
    }
    // 7. KG eager-build
    let builtCount = 0;
    let queuedCount = 0;
    if (!opts.skipKg) {
        const eagerBuildFn = sweepDeps.eagerBuildFn ?? (await loadEagerBuildFn());
        try {
            const kgResult = await eagerBuildFn(matches);
            builtCount = kgResult.built.length;
            queuedCount = kgResult.queued.length;
        }
        catch {
            // KG build failure must not gate the setup phase
            queuedCount = matches.length;
        }
    }
    // 8. Append trust-audit-log entry
    try {
        const auditFn = sweepDeps.appendTrustAuditFn ?? (await loadAppendTrustAuditFn());
        auditFn({
            action: 'setup-project-sweep-completed',
            // gitUrl is required by schema; file:// is in the URL_PATTERN allow-list.
            gitUrl: `file://${projectsRoot}`,
            trustMethod: 'none',
            // Extra passthrough fields (rootPath, matchedCount, etc.) are
            // accepted via TrustAuditEntrySchema's `.passthrough()` — the
            // inferred type carries an index signature for unknown keys, so
            // TS accepts these without a cast. Schema discriminated-union to
            // land in follow-up plan-hard pass; see Phase D CP3 finding ADV-D-03.
            rootPath: projectsRoot,
            matchedCount: matches.length,
            builtCount,
            queuedCount,
            addedSourceIds: addedNames,
        });
    }
    catch {
        // audit-log failures must not gate a successful phase
    }
    // 9. Return result
    const parts = [
        `${projectsRoot}: ${matches.length} repo(s) found`,
        `${addedNames.length} added`,
    ];
    if (!opts.skipKg) {
        parts.push(`${builtCount} KG-built`, `${queuedCount} queued`);
    }
    if (repoErrors.length > 0) {
        parts.push(`${repoErrors.length} error(s): ${repoErrors.join('; ')}`);
    }
    return { ok: true, message: parts.join(', ') };
}
//# sourceMappingURL=setup-phase-5b-project-sweep.js.map