/**
 * UAT query handlers — checkpoint rendering and audit scanning.
 *
 * Ported from get-shit-done/bin/lib/uat.cjs.
 * Provides UAT checkpoint rendering for verify-work workflows and
 * audit scanning for UAT/VERIFICATION files across phases.
 *
 * @example
 * ```typescript
 * import { uatRenderCheckpoint, auditUat } from './uat.js';
 *
 * await uatRenderCheckpoint(['--file', 'path/to/UAT.md'], '/project');
 * // { data: { test_number: 1, test_name: 'Login', checkpoint: '...' } }
 *
 * await auditUat([], '/project');
 * // { data: { results: [...], summary: { total_files: 2, total_items: 5 } } }
 * ```
 */
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join, relative } from 'node:path';
import { GSDError, ErrorClassification } from '../errors.js';
import { extractFrontmatter } from './frontmatter.js';
import { planningPaths, resolvePathUnderProject, sanitizeForDisplay, toPosixPath } from './helpers.js';
import { getMilestonePhaseFilter } from './state.js';
/** Same string as `buildCheckpoint` in `get-shit-done/bin/lib/uat.cjs`. */
function buildUatCheckpoint(currentTest) {
    return [
        '╔══════════════════════════════════════════════════════════════╗',
        '║  CHECKPOINT: Verification Required                           ║',
        '╚══════════════════════════════════════════════════════════════╝',
        '',
        `**Test ${currentTest.number}: ${currentTest.name}**`,
        '',
        currentTest.expected,
        '',
        '──────────────────────────────────────────────────────────────',
        'Type `pass` or describe what\'s wrong.',
        '──────────────────────────────────────────────────────────────',
    ].join('\n');
}
// ─── uatRenderCheckpoint ─────────────────────────────────────────────────
/**
 * Render the current UAT checkpoint — reads a UAT file, parses the
 * "Current Test" section, and returns a formatted checkpoint prompt.
 *
 * Port of `cmdRenderCheckpoint` from `uat.cjs` (paths via `requireSafePath`,
 * checkpoint via `buildCheckpoint`, name/expected via `sanitizeForDisplay`).
 *
 * Args: --file <path>
 */
export const uatRenderCheckpoint = async (args, projectDir) => {
    const fileIdx = args.indexOf('--file');
    const filePath = fileIdx !== -1 ? args[fileIdx + 1] : null;
    if (!filePath) {
        return { data: { error: 'UAT file required: use uat render-checkpoint --file <path>' } };
    }
    let resolvedPath;
    try {
        resolvedPath = await resolvePathUnderProject(projectDir, filePath);
    }
    catch {
        return { data: { error: `UAT file not found: ${filePath}` } };
    }
    if (!existsSync(resolvedPath)) {
        return { data: { error: `UAT file not found: ${filePath}` } };
    }
    const content = readFileSync(resolvedPath, 'utf-8');
    const currentTestMatch = content.match(/##\s*Current Test\s*(?:\n<!--[\s\S]*?-->)?\n([\s\S]*?)(?=\n##\s|$)/i);
    if (!currentTestMatch) {
        return { data: { error: 'UAT file is missing a Current Test section' } };
    }
    const section = currentTestMatch[1].trimEnd();
    if (!section.trim()) {
        return { data: { error: 'Current Test section is empty' } };
    }
    if (/\[testing complete\]/i.test(section)) {
        return { data: { error: 'UAT session is already complete; no pending checkpoint to render' } };
    }
    const numberMatch = section.match(/^number:\s*(\d+)\s*$/m);
    const nameMatch = section.match(/^name:\s*(.+)\s*$/m);
    const expectedBlockMatch = section.match(/^expected:\s*\|\n([\s\S]*?)(?=^\w[\w-]*:\s)/m)
        || section.match(/^expected:\s*\|\n([\s\S]+)/m);
    const expectedInlineMatch = section.match(/^expected:\s*(.+)\s*$/m);
    if (!numberMatch || !nameMatch || (!expectedBlockMatch && !expectedInlineMatch)) {
        return { data: { error: 'Current Test section is malformed' } };
    }
    let expectedRaw;
    if (expectedBlockMatch) {
        expectedRaw = expectedBlockMatch[1]
            .split('\n')
            .map(line => line.replace(/^ {2}/, ''))
            .join('\n')
            .trim();
    }
    else {
        expectedRaw = expectedInlineMatch[1].trim();
    }
    const currentTest = {
        complete: false,
        number: parseInt(numberMatch[1], 10),
        name: sanitizeForDisplay(nameMatch[1].trim()),
        expected: sanitizeForDisplay(expectedRaw),
    };
    const checkpoint = buildUatCheckpoint(currentTest);
    return {
        data: {
            file_path: toPosixPath(relative(projectDir, resolvedPath)),
            test_number: currentTest.number,
            test_name: currentTest.name,
            checkpoint,
        },
    };
};
// ─── auditUat (cmdAuditUat) ────────────────────────────────────────────────
/** Port of `categorizeItem` from `uat.cjs`. */
function categorizeItem(result, reason, blockedBy) {
    if (result === 'blocked' || blockedBy) {
        if (blockedBy) {
            if (/server/i.test(blockedBy))
                return 'server_blocked';
            if (/device|physical/i.test(blockedBy))
                return 'device_needed';
            if (/build|release|preview/i.test(blockedBy))
                return 'build_needed';
            if (/third.party|twilio|stripe/i.test(blockedBy))
                return 'third_party';
        }
        return 'blocked';
    }
    if (result === 'skipped') {
        if (reason) {
            if (/server|not running|not available/i.test(reason))
                return 'server_blocked';
            if (/simulator|physical|device/i.test(reason))
                return 'device_needed';
            if (/build|release|preview/i.test(reason))
                return 'build_needed';
        }
        return 'skipped_unresolved';
    }
    if (result === 'pending')
        return 'pending';
    if (result === 'human_needed')
        return 'human_uat';
    return 'unknown';
}
/** Port of `parseUatItems` from `uat.cjs`. */
function parseUatItems(content) {
    const items = [];
    const testPattern = /###\s*(\d+)\.\s*([^\n]+)\nexpected:\s*([^\n]+)\nresult:\s*(\w+)(?:\n(?:reported|reason|blocked_by):\s*[^\n]*)?/g;
    let match;
    while ((match = testPattern.exec(content)) !== null) {
        const [, num, name, expected, result] = match;
        if (result === 'pending' || result === 'skipped' || result === 'blocked') {
            const afterMatch = content.slice(match.index);
            const nextHeading = afterMatch.indexOf('\n###', 1);
            const blockText = nextHeading > 0 ? afterMatch.slice(0, nextHeading) : afterMatch;
            const reasonMatch = blockText.match(/reason:\s*(.+)/);
            const blockedByMatch = blockText.match(/blocked_by:\s*(.+)/);
            const item = {
                test: parseInt(num, 10),
                name: name.trim(),
                expected: expected.trim(),
                result,
                category: categorizeItem(result, reasonMatch?.[1], blockedByMatch?.[1]),
            };
            if (reasonMatch)
                item.reason = reasonMatch[1].trim();
            if (blockedByMatch)
                item.blocked_by = blockedByMatch[1].trim();
            items.push(item);
        }
    }
    return items;
}
/**
 * Parse frontmatter human_verification: YAML array entries into audit items.
 *
 * Fixes #2788: when gsd-verifier encodes human items in YAML frontmatter
 * rather than the body, parseVerificationItems was returning [] because it
 * only searched the body for a "## Human Verification" heading.
 */
function parseVerificationFrontmatterItems(fm) {
    const items = [];
    const hvArray = fm.human_verification;
    if (!Array.isArray(hvArray))
        return items;
    let i = 0;
    for (const entry of hvArray) {
        i++;
        if (typeof entry === 'string') {
            const name = entry.trim();
            if (name.length > 0) {
                items.push({ test: i, name, result: 'human_needed', category: 'human_uat' });
            }
        }
        else if (typeof entry === 'object' && entry !== null) {
            const obj = entry;
            // Accept any string property as the item name; prefer 'test' key.
            const name = obj.test || obj.name || '';
            if (name) {
                const item = {
                    test: i,
                    name: String(name).trim(),
                    result: 'human_needed',
                    category: 'human_uat',
                };
                if (obj.expected)
                    item.expected = String(obj.expected).trim();
                if (obj.why_human)
                    item.why_human = String(obj.why_human).trim();
                items.push(item);
            }
        }
    }
    return items;
}
/** Port of `parseVerificationItems` from `uat.cjs`. */
function parseVerificationItems(content, status, fm) {
    const items = [];
    if (status === 'human_needed') {
        // Check frontmatter human_verification: array first (#2788).
        // gsd-verifier writes items here; body-section fallback is secondary.
        if (fm) {
            const fmItems = parseVerificationFrontmatterItems(fm);
            if (fmItems.length > 0)
                return fmItems;
        }
        // Body fallback: match ## human_verification or ## Human Verification
        // (case-insensitive, underscore or space, with optional parenthetical).
        const hvSection = content.match(/##\s*human[_\s-]verification[^\n]*\n([\s\S]*?)(?=\n##\s|\n---\s|$)/i);
        if (hvSection) {
            const lines = hvSection[1].split('\n');
            for (const line of lines) {
                const tableMatch = line.match(/\|\s*(\d+)\s*\|\s*([^|]+)/);
                const bulletMatch = line.match(/^[-*]\s+(.+)/);
                const numberedMatch = line.match(/^(\d+)\.\s+(.+)/);
                if (tableMatch) {
                    items.push({
                        test: parseInt(tableMatch[1], 10),
                        name: tableMatch[2].trim(),
                        result: 'human_needed',
                        category: 'human_uat',
                    });
                }
                else if (numberedMatch) {
                    items.push({
                        test: parseInt(numberedMatch[1], 10),
                        name: numberedMatch[2].trim(),
                        result: 'human_needed',
                        category: 'human_uat',
                    });
                }
                else if (bulletMatch && bulletMatch[1].length > 10) {
                    items.push({
                        name: bulletMatch[1].trim(),
                        result: 'human_needed',
                        category: 'human_uat',
                    });
                }
            }
        }
    }
    return items;
}
/**
 * Cross-phase UAT / VERIFICATION audit — port of `cmdAuditUat` (`uat.cjs`).
 */
export const auditUat = async (_args, projectDir, workstream) => {
    const paths = planningPaths(projectDir, workstream);
    if (!existsSync(paths.phases)) {
        throw new GSDError('No phases directory found in planning directory', ErrorClassification.Blocked);
    }
    const isDirInMilestone = await getMilestonePhaseFilter(projectDir, workstream);
    const results = [];
    const dirs = readdirSync(paths.phases, { withFileTypes: true })
        .filter(e => e.isDirectory())
        .map(e => e.name)
        .filter(isDirInMilestone)
        .sort();
    for (const dir of dirs) {
        const phaseMatch = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
        const phaseNum = phaseMatch ? phaseMatch[1] : dir;
        const phaseDir = join(paths.phases, dir);
        const files = readdirSync(phaseDir);
        for (const file of files.filter(f => f.includes('-UAT') && f.endsWith('.md'))) {
            const content = readFileSync(join(phaseDir, file), 'utf-8');
            const items = parseUatItems(content);
            if (items.length > 0) {
                const fm = extractFrontmatter(content);
                results.push({
                    phase: phaseNum,
                    phase_dir: dir,
                    file,
                    file_path: toPosixPath(relative(projectDir, join(phaseDir, file))),
                    type: 'uat',
                    status: (fm.status || 'unknown'),
                    items,
                });
            }
        }
        for (const file of files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
            const content = readFileSync(join(phaseDir, file), 'utf-8');
            const fm = extractFrontmatter(content);
            const status = (fm.status || 'unknown');
            if (status === 'human_needed' || status === 'gaps_found') {
                const items = parseVerificationItems(content, status, fm);
                if (items.length > 0) {
                    results.push({
                        phase: phaseNum,
                        phase_dir: dir,
                        file,
                        file_path: toPosixPath(relative(projectDir, join(phaseDir, file))),
                        type: 'verification',
                        status,
                        items,
                    });
                }
            }
        }
    }
    const summary = {
        total_files: results.length,
        total_items: results.reduce((sum, r) => sum + r.items.length, 0),
        by_category: {},
        by_phase: {},
    };
    for (const r of results) {
        if (!summary.by_phase[r.phase])
            summary.by_phase[r.phase] = 0;
        for (const item of r.items) {
            summary.by_phase[r.phase]++;
            const cat = item.category || 'unknown';
            summary.by_category[cat] = (summary.by_category[cat] || 0) + 1;
        }
    }
    return { data: { results, summary } };
};
//# sourceMappingURL=uat.js.map