/**
 * commands/migrate-pr-reviews.mjs — one-shot migration of the legacy
 * .aperant/reviews/pr-{N}-{date}-{hash}/ layout to the per-PR persistent
 * .aperant/pr-reviews/pr-{N}/rounds/{R}/ layout introduced by R2/R3 of the
 * /apt:pr-review hardening task.
 *
 * Idempotent: running on an already-migrated tree detects the target folder
 * and returns { status: "already_migrated" } with no mutations.
 */
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
import { join, resolve } from 'node:path';
import { err, ok } from '../util/result.mjs';
/**
 * Parse a legacy review-id directory name into its components.
 *
 * Examples:
 *   "pr-23-260413-09ba"        -> { pr: 23, round: 1, date: "260413", hash: "09ba" }
 *   "pr-23-r2-260415-5ec5"     -> { pr: 23, round: 2, date: "260415", hash: "5ec5" }
 *   "pr-24-r3-260413-318a"     -> { pr: 24, round: 3, date: "260413", hash: "318a" }
 *
 * Returns null for names that don't match (skipped by migration).
 */
export function parseLegacyReviewId(name) {
    // Accept pr-{N}-[r{R}-]{YYMMDD}-{hash}
    const roundMatch = name.match(/^pr-(\d+)-r(\d+)-(\d{6})-([a-z0-9]+)$/i);
    if (roundMatch) {
        return {
            pr: Number(roundMatch[1]),
            round: Number(roundMatch[2]),
            date: roundMatch[3],
            hash: roundMatch[4],
            source_id: name,
        };
    }
    const plainMatch = name.match(/^pr-(\d+)-(\d{6})-([a-z0-9]+)$/i);
    if (plainMatch) {
        return {
            pr: Number(plainMatch[1]),
            round: 1,
            date: plainMatch[2],
            hash: plainMatch[3],
            source_id: name,
        };
    }
    return null;
}
/**
 * Rewrite prior_review references in a metadata.json-like object to point at
 * the new per-round layout (../{R-1}/) rather than a sibling legacy directory.
 */
function rewritePriorReview(obj, priorRound) {
    if (!obj || typeof obj !== 'object')
        return obj;
    if (typeof obj.prior_review === 'string' && obj.prior_review.length > 0) {
        // Replace value with canonical per-round pointer. The orchestrator can
        // resolve this relative to the current round's path.
        obj.prior_review = `../${priorRound}/`;
    }
    return obj;
}
function rewriteJsonFile(path, priorRound) {
    if (!existsSync(path))
        return false;
    try {
        const raw = readFileSync(path, 'utf-8');
        const parsed = JSON.parse(raw);
        const before = JSON.stringify(parsed);
        rewritePriorReview(parsed, priorRound);
        const after = JSON.stringify(parsed);
        if (before !== after) {
            writeFileSync(path, `${JSON.stringify(parsed, null, 2)}\n`, 'utf-8');
            return true;
        }
    }
    catch {
        // Non-JSON file or malformed — skip
    }
    return false;
}
/**
 * Migrate a single legacy review dir into the new layout.
 * Returns { pr, round, source_id, target_path, rewrites }.
 */
function migrateOne(legacyDir, parsed, reviewsDir, prReviewsDir) {
    const targetPrDir = join(prReviewsDir, `pr-${parsed.pr}`);
    const targetRoundsDir = join(targetPrDir, 'rounds');
    const targetRoundDir = join(targetRoundsDir, String(parsed.round));
    mkdirSync(targetRoundsDir, { recursive: true });
    if (existsSync(targetRoundDir)) {
        return {
            pr: parsed.pr,
            round: parsed.round,
            source_id: parsed.source_id,
            target_path: targetRoundDir,
            status: 'skipped_target_exists',
            rewrites: 0,
        };
    }
    // Copy tree, then remove source. We prefer copy-then-delete over rename
    // across device/filesystem boundaries to stay robust.
    const sourcePath = join(reviewsDir, legacyDir);
    try {
        renameSync(sourcePath, targetRoundDir);
    }
    catch {
        cpSync(sourcePath, targetRoundDir, { recursive: true });
        rmSync(sourcePath, { recursive: true, force: true });
    }
    let rewrites = 0;
    if (parsed.round > 1) {
        const priorRound = parsed.round - 1;
        if (rewriteJsonFile(join(targetRoundDir, 'metadata.json'), priorRound))
            rewrites++;
        if (rewriteJsonFile(join(targetRoundDir, 'review.json'), priorRound))
            rewrites++;
    }
    return {
        pr: parsed.pr,
        round: parsed.round,
        source_id: parsed.source_id,
        target_path: targetRoundDir,
        status: 'migrated',
        rewrites,
    };
}
/**
 * Entry point for `apt-tools migrate-pr-reviews <project-dir>`.
 */
export function cmdMigratePrReviews(projectDir) {
    if (!projectDir)
        return err('Usage: apt-tools migrate-pr-reviews <project-dir>');
    const root = resolve(projectDir);
    const aperant = join(root, '.aperant');
    if (!existsSync(aperant))
        return err(`.aperant/ not found under ${root}`);
    const reviewsDir = join(aperant, 'reviews');
    const prReviewsDir = join(aperant, 'pr-reviews');
    const legacyExists = existsSync(reviewsDir) && statSync(reviewsDir).isDirectory();
    const targetExists = existsSync(prReviewsDir) && statSync(prReviewsDir).isDirectory();
    const legacyEntries = legacyExists
        ? readdirSync(reviewsDir, { withFileTypes: true })
            .filter((d) => d.isDirectory())
            .map((d) => d.name)
        : [];
    // Idempotency: if the legacy folder is empty or gone AND pr-reviews exists,
    // consider this tree already migrated.
    if (legacyEntries.length === 0) {
        const prCount = targetExists
            ? readdirSync(prReviewsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith('pr-')).length
            : 0;
        return ok({
            status: 'already_migrated',
            pr_count: prCount,
            source: reviewsDir,
            target: prReviewsDir,
        });
    }
    mkdirSync(prReviewsDir, { recursive: true });
    const migrated = [];
    const skipped = [];
    for (const name of legacyEntries) {
        const parsed = parseLegacyReviewId(name);
        if (!parsed) {
            skipped.push({ name, reason: 'unrecognized_name_pattern' });
            continue;
        }
        const result = migrateOne(name, parsed, reviewsDir, prReviewsDir);
        migrated.push(result);
    }
    // If the legacy dir is now empty, remove it.
    if (existsSync(reviewsDir) &&
        readdirSync(reviewsDir).filter((n) => !n.startsWith('.')).length === 0) {
        try {
            rmSync(reviewsDir, { recursive: true, force: true });
        }
        catch {
            // leave empty dir on filesystem permission issues
        }
    }
    return ok({
        status: 'migrated',
        pr_count: new Set(migrated.map((m) => m.pr)).size,
        migrated_count: migrated.length,
        skipped_count: skipped.length,
        migrated,
        skipped,
        source: reviewsDir,
        target: prReviewsDir,
    });
}
//# sourceMappingURL=migrate-pr-reviews.mjs.map