/**
 * `olam kg build` — pristine workspace KG build via kg-service `/build`.
 *
 * kg-service-container-v2 Phase F (2026-05-14) migrates this command
 * from the legacy host-side scratch-copy + docker-run-graphify pattern
 * to a single HTTP POST to the always-running `olam-kg-service`
 * container. The scratch-copy now happens INSIDE the container as root
 * (no host UID permission issues), so multi-submodule workspaces with
 * non-operator-owned `.git/objects/pack/*` files and broken symlinks
 * build cleanly — closes Samuel's atlas-core failure from 2026-05-13.
 *
 * Flow:
 *   1. Resolve workspace name + source path (cwd by default).
 *   2. Translate cwd to container-side path under `/host-home/…`
 *      (kg-service container mounts $HOME read-only at /host-home).
 *   3. POST {workspace, repo_path} to localhost:9997/build.
 *   4. Server-side: cp source → /tmp/scratch (root-perms), graphify
 *      update, move output to /kg-data/<workspace>/graphify-out/, clean.
 *   5. Write host-side freshness.json mirroring the server's response.
 *
 * Failure modes surfaced with remedies:
 *   - kg-service not reachable → "run `olam services up`".
 *   - cwd not under $HOME → operator hint (mount path is $HOME:/host-home).
 *   - graphify timeout / failure / scratch_copy_failed → pass-through error.
 *
 * Out of scope (v1 behaviour retained):
 *   - Workspace-catalog lookup (deferred follow-on); cwd-as-source.
 *   - Watching / progress streaming — `/build` is synchronous, blocking.
 */
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { kgPristinePath } from '@olam/core/src/kg/storage-paths.js';
import { validateWorkspaceName } from '@olam/core/src/world/workspace-name.js';
import { build as kgServiceBuild, KgServiceUnreachableError, } from '@olam/core/src/kg/kg-service-client.js';
import { printError, printInfo, printSuccess } from '../output.js';
import { registerKgStatusCommand } from './kg-status.js';
import { registerKgWatchCommand } from './kg-watch.js';
import { registerKgClassifyCommand } from './kg-classify.js';
import { registerKgDoctorCommand } from './kg-doctor.js';
import { registerKgInstallHookCommand } from './kg-install-hook.js';
import { registerKgUninstallHookCommand } from './kg-uninstall-hook.js';
import { registerKgSavingsCommand } from './kg-savings.js';
/** Read JSONL queue entries from disk (real implementation). */
function readQueueFromDisk(queuePath) {
    if (!fs.existsSync(queuePath))
        return [];
    const raw = fs.readFileSync(queuePath, 'utf-8');
    const entries = [];
    for (const line of raw.split('\n')) {
        const t = line.trim();
        if (!t)
            continue;
        try {
            entries.push(JSON.parse(t));
        }
        catch { /* skip malformed */ }
    }
    return entries;
}
/** Write JSONL queue entries to disk (real implementation). */
function writeQueueToDisk(queuePath, entries) {
    fs.mkdirSync(path.dirname(queuePath), { recursive: true });
    const content = entries.map((e) => JSON.stringify(e)).join('\n') + (entries.length > 0 ? '\n' : '');
    fs.writeFileSync(queuePath, content, 'utf-8');
}
/**
 * Drain the kg-pending.jsonl queue: build each entry sequentially via kg-service,
 * remove successfully-built entries from the queue, leave failures with failedAt/failedReason.
 */
export async function runKgBuildPending(opts = {}) {
    const queuePath = opts.queuePath ??
        (() => {
            const stateDir = process.env['OLAM_STATE_DIR'] ??
                path.join(os.homedir(), '.olam', 'state');
            return path.join(stateDir, 'kg-pending.jsonl');
        })();
    const readQueue = opts.readQueueFn ?? readQueueFromDisk;
    const writeQueue = opts.writeQueueFn ?? writeQueueToDisk;
    const rawEntries = readQueue(queuePath);
    // Dedupe at drain-time: collapse duplicates by path, keeping the latest entry
    // (Map insertion order: later entries overwrite earlier ones for the same key).
    const deduped = [...new Map(rawEntries.map((e) => [e.path, e])).values()];
    if (deduped.length === 0) {
        printInfo('kg build --pending', 'queue is empty');
        return { exitCode: 0, built: [], failed: [] };
    }
    printInfo('kg build --pending', `draining ${deduped.length} queued repo(s)`);
    const built = [];
    const failed = [];
    const remaining = [];
    for (const entry of deduped) {
        const repoPath = entry.path;
        const workspaceName = path.basename(repoPath).toLowerCase();
        printInfo('kg build --pending', `building ${repoPath} (workspace=${workspaceName})`);
        let containerPath;
        try {
            containerPath = toContainerPath(repoPath);
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            printError(`skipping ${repoPath}: ${msg}`);
            failed.push(repoPath);
            remaining.push({ ...entry, failedAt: Date.now(), failedReason: msg });
            continue;
        }
        let resp;
        try {
            resp = await kgServiceBuild({ workspace: workspaceName, repo_path: containerPath });
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            printError(`build failed for ${repoPath}: ${msg}`);
            failed.push(repoPath);
            remaining.push({ ...entry, failedAt: Date.now(), failedReason: msg });
            continue;
        }
        if (!resp.ok) {
            const msg = resp.error ?? 'unknown error';
            printError(`build failed for ${repoPath}: ${msg}`);
            failed.push(repoPath);
            remaining.push({ ...entry, failedAt: Date.now(), failedReason: msg });
            continue;
        }
        built.push(repoPath);
        printSuccess(`built ${repoPath} (${resp.nodes ?? '?'} nodes / ${resp.edges ?? '?'} edges)`);
        // Do NOT add to remaining — successful builds are removed from queue
    }
    writeQueue(queuePath, remaining);
    printInfo('kg build --pending', `done: ${built.length} built, ${failed.length} failed, ${remaining.length} remaining in queue`);
    return { exitCode: failed.length > 0 && built.length === 0 ? 1 : 0, built, failed };
}
/**
 * Resolve workspace name + source path. `name` defaults to the basename
 * of the operator's cwd — the "build KG for the repo I'm in" common case.
 * Validates the workspace name regex via @olam/core's single source of truth.
 */
function resolveWorkspace(arg) {
    const cwd = process.cwd();
    const name = arg ?? path.basename(cwd).toLowerCase();
    validateWorkspaceName(name);
    return { name, sourcePath: cwd };
}
/**
 * Translate a host-side absolute path to the container's view of the same
 * file, given that kg-service mounts `$HOME:/host-home:ro`.
 *
 *   /Users/ernie/Projects/atlas-core   →   /host-home/Projects/atlas-core
 *
 * Throws when the source path doesn't live under $HOME (kg-service can't
 * see it). Operator hint surfaces the mount layout in the error message.
 */
function toContainerPath(hostPath) {
    const home = os.homedir();
    const resolved = path.resolve(hostPath);
    if (!resolved.startsWith(home + path.sep) && resolved !== home) {
        throw new Error(`source path "${resolved}" is not under $HOME (${home}). kg-service can only build ` +
            `repos that live under your home dir (it bind-mounts $HOME:/host-home:ro at start). ` +
            `Move the repo or set OLAM_HOME if you need a different root.`);
    }
    const rel = path.relative(home, resolved);
    return rel === '' ? '/host-home' : path.posix.join('/host-home', rel.split(path.sep).join('/'));
}
/**
 * Programmatic entry point — exported so tests can drive without spawning
 * a sub-process.
 */
export async function runKgBuild(workspaceArg, options = {}) {
    let workspace;
    try {
        workspace = resolveWorkspace(workspaceArg);
    }
    catch (err) {
        printError(err instanceof Error ? err.message : String(err));
        return { exitCode: 2 };
    }
    let containerRepoPath;
    try {
        containerRepoPath = toContainerPath(workspace.sourcePath);
    }
    catch (err) {
        printError(err instanceof Error ? err.message : String(err));
        return { exitCode: 2 };
    }
    // Resolve / pre-create the host-side output directory. kg-service writes
    // graphify-out/ into /kg-data/<workspace>/, which is bind-mounted at
    // start time to the OLAM_HOME-aware kgRoot(). We mirror that here so
    // host-side `olam kg status` can read freshness.json without reaching
    // into the container.
    const outDir = kgPristinePath(workspace.name);
    fs.mkdirSync(outDir, { recursive: true });
    const human = !options.json;
    if (human) {
        printInfo('kg build', `workspace=${workspace.name} source=${workspace.sourcePath}`);
        printInfo('kg build', `→ kg-service /build (container path ${containerRepoPath}) → ${outDir}/graphify-out/`);
    }
    let resp;
    try {
        resp = await kgServiceBuild({
            workspace: workspace.name,
            repo_path: containerRepoPath,
        });
    }
    catch (err) {
        if (err instanceof KgServiceUnreachableError) {
            printError(err.message);
            printInfo('remedy', 'run `olam services up` to start kg-service, then re-run `olam kg build`.');
            return { exitCode: 3 };
        }
        printError(`kg-service /build failed: ${err instanceof Error ? err.message : String(err)}`);
        return { exitCode: 1 };
    }
    if (!resp.ok) {
        printError(`kg-service /build returned error: ${resp.error ?? 'unknown'}`);
        return { exitCode: 1 };
    }
    // Mirror the server-authoritative freshness onto the host. The server
    // already wrote /kg-data/<ws>/freshness.json, but writing again here
    // keeps the host-side freshness contract identical to v1's shape for
    // downstream consumers (kg-status, kg-watch) that haven't migrated.
    const freshness = {
        built_at: resp.built_at ?? new Date().toISOString(),
        duration_ms: resp.duration_ms ?? 0,
        node_count: resp.nodes ?? null,
        edge_count: resp.edges ?? null,
        workspace: workspace.name,
        graphify_path: 'container',
    };
    fs.writeFileSync(path.join(outDir, 'freshness.json'), JSON.stringify(freshness, null, 2) + '\n', 'utf-8');
    const finalOut = path.join(outDir, 'graphify-out');
    if (options.json) {
        process.stdout.write(JSON.stringify(freshness) + '\n');
    }
    else {
        printSuccess(`kg build ${workspace.name} — ${resp.nodes ?? '?'} nodes / ${resp.edges ?? '?'} edges in ${((resp.duration_ms ?? 0) / 1000).toFixed(1)}s (via kg-service container)`);
        printInfo('output', `${finalOut}/graph.json`);
    }
    return { exitCode: 0, freshness, outputDir: finalOut };
}
/**
 * `olam kg <subcommand>` group — registers the kg subcommand tree.
 */
export function registerKg(program) {
    const kg = program.command('kg').description('Knowledge-graph operations (kg-service container)');
    const buildCmd = kg.command('build');
    buildCmd
        .description('Build pristine KG for a workspace (default: current dir). Routes through olam-kg-service /build endpoint. Use --pending to drain the pending queue.')
        .argument('[workspace]', 'workspace name (lowercase alphanumeric + hyphens/underscores)')
        .option('--json', 'emit freshness record as JSON instead of human-readable status')
        .option('--pending', 'drain the kg-pending.jsonl queue (mutually exclusive with [workspace])')
        .action(async (workspaceArg, opts) => {
        if (opts.pending && workspaceArg !== undefined) {
            printError('--pending is mutually exclusive with [workspace] argument');
            process.exitCode = 2;
            return;
        }
        if (opts.pending) {
            const r = await runKgBuildPending();
            if (r.exitCode !== 0)
                process.exitCode = r.exitCode;
            return;
        }
        const r = await runKgBuild(workspaceArg, opts);
        if (r.exitCode !== 0)
            process.exitCode = r.exitCode;
    });
    // Phase C — operational polish (status report + opt-in watcher).
    registerKgStatusCommand(kg);
    registerKgWatchCommand(kg);
    // kg-service-container-v2 Phase C — HTTP-client wrappers around localhost:9997.
    registerKgClassifyCommand(kg);
    registerKgDoctorCommand(kg);
    // kg-service-container-v2 Phase D — settings.json mutation for the PreToolUse hook.
    registerKgInstallHookCommand(kg);
    registerKgUninstallHookCommand(kg);
    // kg-hit-signal — cumulative savings telemetry tallied by /kg-data/savings.jsonl.
    registerKgSavingsCommand(kg);
}
//# sourceMappingURL=kg-build.js.map