// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
import { spawnSync } from 'node:child_process';
import { EXIT_CODES, EMOJI } from '@lumenflow/core/wu-constants';
import { findMissingCodePathCoverage as findMissingCodePathCoverageFromRules, isCodePathCoveredByChangedFiles, } from '@lumenflow/core/wu-rules-engine';
const DEFAULT_BASE_REF = 'main';
const DEFAULT_HEAD_REF = 'HEAD';
const GIT_DIFF_STDIO = ['pipe', 'pipe', 'pipe'];
/**
 * Shared code_paths coverage helpers used by wu:prep and wu:edit.
 *
 * Keeping this logic out of command entrypoints avoids command-to-command coupling
 * (hexagonal boundary: orchestration commands depend on shared adapters, not each other).
 */
export function isCodePathCoveredByChanges(options) {
    return isCodePathCoveredByChangedFiles(options);
}
export function findMissingCodePathCoverage(options) {
    return findMissingCodePathCoverageFromRules(options);
}
function parsePorcelainPaths(output) {
    return output
        .split('\n')
        .map((line) => line.trimEnd())
        .filter(Boolean)
        .map((line) => {
        const payload = line.slice(3).trim();
        if (payload.includes(' -> ')) {
            return payload.split(' -> ').pop() ?? '';
        }
        return payload;
    })
        .filter(Boolean);
}
export function checkCodePathCoverageBeforeGates(options) {
    const { codePaths = [], cwd, baseRef = DEFAULT_BASE_REF, headRef = DEFAULT_HEAD_REF, spawnSyncFn = spawnSync, statusSpawnSyncFn = spawnSync, } = options;
    const scopedCodePaths = codePaths
        .filter((codePath) => typeof codePath === 'string')
        .map((codePath) => codePath.trim())
        .filter(Boolean);
    if (scopedCodePaths.length === 0) {
        return {
            valid: true,
            missingCodePaths: [],
            changedFiles: [],
        };
    }
    const range = `${baseRef}...${headRef}`;
    const diffResult = spawnSyncFn('git', ['diff', '--name-only', range], {
        cwd,
        encoding: 'utf-8',
        stdio: GIT_DIFF_STDIO,
    });
    if ((diffResult.status ?? EXIT_CODES.ERROR) !== EXIT_CODES.SUCCESS) {
        const stderrText = String(diffResult.stderr ?? '').trim();
        const errorText = stderrText || (diffResult.error instanceof Error ? diffResult.error.message : '');
        return {
            valid: false,
            missingCodePaths: scopedCodePaths,
            changedFiles: [],
            error: errorText || `git diff --name-only ${range} failed`,
        };
    }
    const changedFiles = String(diffResult.stdout ?? '')
        .split('\n')
        .map((line) => line.trim())
        .filter(Boolean);
    const missingCodePaths = findMissingCodePathCoverage({
        codePaths: scopedCodePaths,
        changedFiles,
    });
    let uncommittedCodePaths = [];
    if (missingCodePaths.length > 0) {
        const statusResult = statusSpawnSyncFn('git', ['status', '--porcelain', '--untracked-files=all'], {
            cwd,
            encoding: 'utf-8',
            stdio: GIT_DIFF_STDIO,
        });
        if ((statusResult.status ?? EXIT_CODES.ERROR) === EXIT_CODES.SUCCESS) {
            const uncommittedFiles = parsePorcelainPaths(String(statusResult.stdout ?? ''));
            uncommittedCodePaths = missingCodePaths.filter((codePath) => isCodePathCoveredByChanges({
                codePath,
                changedFiles: uncommittedFiles,
            }));
        }
    }
    return {
        valid: missingCodePaths.length === 0,
        missingCodePaths,
        changedFiles,
        uncommittedCodePaths,
    };
}
export function formatCodePathCoverageFailure(options) {
    const { wuId, missingCodePaths, changedFiles, uncommittedCodePaths = [], error } = options;
    const missingSection = missingCodePaths.map((filePath) => `  - ${filePath}`).join('\n');
    const changedSection = changedFiles.length > 0
        ? changedFiles.map((filePath) => `  - ${filePath}`).join('\n')
        : '  - (none)';
    const uncommittedSection = uncommittedCodePaths.length > 0
        ? `\nDetected uncommitted code_paths:\n${uncommittedCodePaths
            .map((filePath) => `  - ${filePath}`)
            .join('\n')}\n\n` +
            `If you already created or edited these files locally, commit them first:\n` +
            `  git add ${uncommittedCodePaths.join(' ')}\n` +
            `  git commit -m "wu(${wuId.toLowerCase()}): add scoped changes"\n`
        : '';
    const diffErrorSection = error ? `\nUnable to evaluate branch diff:\n  ${error}\n` : '';
    return (`${EMOJI.FAILURE} code_paths preflight failed for ${wuId}.\n` +
        `${diffErrorSection}\n` +
        `The following code_paths are not modified on this branch (vs main):\n` +
        `${missingSection}\n\n` +
        `Changed files detected on branch:\n` +
        `${changedSection}\n` +
        `${uncommittedSection}\n` +
        `Fix options:\n` +
        `  1. Commit changes that touch each missing code_path\n` +
        `  2. Update WU scope to match actual branch work:\n` +
        `     pnpm wu:edit --id ${wuId} --replace-code-paths --code-paths "<path1>" --code-paths "<path2>"\n` +
        `  3. Re-run: pnpm wu:prep --id ${wuId}`);
}
//# sourceMappingURL=wu-code-path-coverage.js.map