/**
 * gitignore-drift.mjs — dispatch-time warning when `.aperant/.gitignore`
 * on disk no longer matches the content `buildAperantGitignoreTemplate`
 * would emit for the project's current share policy (C34a).
 *
 * Non-blocking: never fails the command, never mutates exit code, never
 * modifies the file. Single stderr line if drift is detected. Skipped for
 * hot-path / machine-readable commands so router-style stdout stays clean.
 *
 * Silenced by `APT_SUPPRESS_GITIGNORE_DRIFT=1` for scripts + tests.
 *
 * @internal — consumed only from bin/apt-tools.mjs dispatch.
 */
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { detectWorktree } from '../git/worktree-detect.mjs';
import { resolveSharePolicy } from './share-policy.mjs';
import { buildAperantGitignoreTemplate } from './upgrade-gitignore.mjs';
/**
 * Commands that read machine-readable output or run on every turn. Their
 * stderr feeds back into the LLM-facing router / detectors; a chatty drift
 * warning would pollute the signal and flip exit-code-adjacent flags.
 */
const SKIP_COMMANDS = new Set([
    'route',
    'detect-project',
    'detect-worktree',
    'health-check',
    'toolchain-detect',
    'host-detect',
    '--version',
    '--help',
    '-v',
    '-h',
]);
/**
 * BUG-028 — resolve the shared (main-repo) root for the dispatch hook.
 *
 * Mirrors `coordination/agent-identity.mjs::resolveSharedRoot` but does NOT
 * depend on that module. The CoordinationStore seam is `@internal` (R8) —
 * `bin/apt-tools.mjs` and its dispatch hooks must reach git through
 * `git/worktree-detect.mjs` directly, not through `coordination/*`.
 *
 * @internal
 */
function resolveSharedRootForDrift(projectDir) {
    try {
        const wt = detectWorktree(projectDir);
        return wt.is_worktree && wt.main_repo_root ? wt.main_repo_root : projectDir;
    }
    catch {
        return projectDir;
    }
}
/**
 * BUG-028 — pretty-print the first few missing / extra lines as a
 * unified-diff snippet for the gate's `reason` field. Capped at
 * `maxLines` per side so the reason stays short enough for terminal
 * output. Returns the empty string when both sides match.
 */
export function buildGitignoreDiffSummary(expected, actual, maxLines = 3) {
    const expSet = new Set(String(expected).split('\n'));
    const actSet = new Set(String(actual).split('\n'));
    const missing = [];
    for (const line of expSet) {
        if (line === '')
            continue;
        if (!actSet.has(line)) {
            missing.push(line);
            if (missing.length >= maxLines)
                break;
        }
    }
    const extra = [];
    for (const line of actSet) {
        if (line === '')
            continue;
        if (!expSet.has(line)) {
            extra.push(line);
            if (extra.length >= maxLines)
                break;
        }
    }
    if (missing.length === 0 && extra.length === 0)
        return '';
    const parts = [];
    for (const line of missing)
        parts.push(`+ ${line}`);
    for (const line of extra)
        parts.push(`- ${line}`);
    return parts.join('\n');
}
/**
 * Pure comparison primitive — returns whether the on-disk `.aperant/.gitignore`
 * differs from the policy-expected template. Returns `{ drifted: false }` when
 * the `.aperant/` dir or file is absent (nothing to compare).
 *
 * BUG-028: when drifted, embeds a unified-diff summary (≤3 missing/extra
 * lines per side) in `reason` so the user sees exactly what drifted
 * without re-running `apt-tools config upgrade-gitignore --dry-run`.
 *
 * @internal
 */
export function checkGitignoreDrift(projectRoot) {
    const aperantDir = join(projectRoot, '.aperant');
    if (!existsSync(aperantDir))
        return { drifted: false, reason: 'no-aperant-dir' };
    const gitignorePath = join(aperantDir, '.gitignore');
    if (!existsSync(gitignorePath))
        return { drifted: false, reason: 'no-gitignore' };
    let config = {};
    const configPath = join(aperantDir, 'config.json');
    if (existsSync(configPath)) {
        try {
            config = JSON.parse(readFileSync(configPath, 'utf-8'));
        }
        catch {
            /* malformed config → treat as empty (solo defaults) */
        }
    }
    const policy = resolveSharePolicy(config);
    const expected = buildAperantGitignoreTemplate(policy);
    let actual = '';
    try {
        actual = readFileSync(gitignorePath, 'utf-8');
    }
    catch {
        return { drifted: false, reason: 'read-failed' };
    }
    const drifted = actual !== expected;
    if (!drifted)
        return { drifted: false, reason: null };
    const diff = buildGitignoreDiffSummary(expected, actual);
    return {
        drifted: true,
        reason: diff
            ? `.aperant/.gitignore drift detected:\n${diff}`
            : '.aperant/.gitignore drift detected (whitespace-only)',
        diff,
    };
}
/**
 * Dispatch-time hook. Emits the drift warning on stderr if:
 *   - `APT_SUPPRESS_GITIGNORE_DRIFT` is not `1`
 *   - `command` is not in the hot-path allowlist
 *   - `.aperant/.gitignore` exists AND does not match the expected template
 *
 * BUG-028: resolves the shared (main-repo) root internally so the same drift
 * case produces the same verdict regardless of cwd context. Pre-fix this
 * inspected `cwd` directly, producing a warning from one cwd context and a
 * clean envelope from another for the same on-disk drift.
 *
 * Never throws.
 *
 * @internal
 */
export function emitDriftWarningIfAny(cwd, command) {
    try {
        if (process.env.APT_SUPPRESS_GITIGNORE_DRIFT === '1')
            return;
        if (!command || SKIP_COMMANDS.has(command))
            return;
        const sharedRoot = resolveSharedRootForDrift(cwd);
        const { drifted } = checkGitignoreDrift(sharedRoot);
        if (drifted) {
            process.stderr.write('[apt-tools] warning: .aperant/.gitignore is out of sync with share policy — run `apt-tools config upgrade-gitignore . --dry-run` to see the diff.\n');
        }
    }
    catch {
        /* never block user command on drift check */
    }
}
//# sourceMappingURL=gitignore-drift.mjs.map