"use strict";
/**
 * Git worktree management
 * Scans and manages git worktrees
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateWorktreeId = generateWorktreeId;
exports.parseWorktreeOutput = parseWorktreeOutput;
exports.getRepositoryPaths = getRepositoryPaths;
exports.scanWorktrees = scanWorktrees;
exports.scanMultipleRepositories = scanMultipleRepositories;
exports.syncWorktreesToDB = syncWorktreesToDB;
const child_process_1 = require("child_process");
const util_1 = require("util");
const path_1 = __importDefault(require("path"));
const db_1 = require("../../lib/db");
const env_1 = require("../../lib/env");
const logger_1 = require("../../lib/logger");
const logger = (0, logger_1.createLogger)('worktrees');
/**
 * Generate URL-safe ID from repository name and branch name
 *
 * @param branchName - Git branch name (e.g., "feature/foo", "main")
 * @param repositoryName - Repository name (e.g., "MyRepo")
 * @returns URL-safe ID (e.g., "myrepo-feature-foo", "myrepo-main")
 *
 * @example
 * ```typescript
 * generateWorktreeId('feature/foo', 'MyRepo') // => 'myrepo-feature-foo'
 * generateWorktreeId('main', 'MyRepo') // => 'myrepo-main'
 * generateWorktreeId('Feature/Foo', 'Repo') // => 'repo-feature-foo'
 * ```
 */
function generateWorktreeId(branchName, repositoryName) {
    if (!branchName) {
        return '';
    }
    const sanitize = (str) => str
        .toLowerCase()
        .replace(/[^a-z0-9-]/g, '-') // Replace non-alphanumeric (except hyphen) with hyphen
        .replace(/-+/g, '-') // Replace consecutive hyphens with single hyphen
        .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
    const sanitizedBranch = sanitize(branchName);
    if (repositoryName) {
        const sanitizedRepo = sanitize(repositoryName);
        return `${sanitizedRepo}-${sanitizedBranch}`;
    }
    return sanitizedBranch;
}
/**
 * Parse git worktree list output
 *
 * @param output - Output from `git worktree list`
 * @returns Array of parsed worktree information
 *
 * @example
 * ```typescript
 * const output = `
 * /path/to/main abc123 [main]
 * /path/to/feature def456 [feature/foo]
 * `;
 * parseWorktreeOutput(output);
 * // => [
 * //   { path: '/path/to/main', branch: 'main', commit: 'abc123' },
 * //   { path: '/path/to/feature', branch: 'feature/foo', commit: 'def456' }
 * // ]
 * ```
 */
function parseWorktreeOutput(output) {
    if (!output || output.trim() === '') {
        return [];
    }
    const lines = output.trim().split('\n');
    const worktrees = [];
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed)
            continue;
        // Format: /path/to/worktree commit [branch]
        // or:     /path/to/worktree commit (detached HEAD)
        const match = trimmed.match(/^(.+?)\s+([a-z0-9]+)\s+(?:\[(.+?)\]|\(detached HEAD\))/);
        if (match) {
            const [, pathStr, commit, branch] = match;
            worktrees.push({
                path: pathStr.trim(),
                branch: branch || `detached-${commit}`,
                commit,
            });
        }
    }
    return worktrees;
}
/**
 * Get repository paths from environment variables
 * Supports both WORKTREE_REPOS (comma-separated) and CM_ROOT_DIR (single path)
 *
 * @returns Array of repository root paths
 *
 * @example
 * ```typescript
 * // WORKTREE_REPOS="/path/to/repo1,/path/to/repo2"
 * getRepositoryPaths(); // => ['/path/to/repo1', '/path/to/repo2']
 *
 * // CM_ROOT_DIR="/path/to/repo"
 * getRepositoryPaths(); // => ['/path/to/repo']
 * ```
 */
function getRepositoryPaths() {
    // Try WORKTREE_REPOS first (supports multiple repos)
    const worktreeRepos = process.env.WORKTREE_REPOS;
    if (worktreeRepos && worktreeRepos.trim()) {
        return worktreeRepos
            .split(',')
            .map(p => p.trim())
            .filter(p => p.length > 0);
    }
    // Fallback to CM_ROOT_DIR / MCBD_ROOT_DIR (Issue #76: env fallback support)
    const rootDir = (0, env_1.getEnvByKey)('CM_ROOT_DIR');
    if (rootDir && rootDir.trim()) {
        return [rootDir.trim()];
    }
    return [];
}
/**
 * Scan git worktrees in a single repository
 *
 * @param rootDir - Root directory to scan for worktrees
 * @returns Array of Worktree objects
 *
 * @throws {Error} If git command fails (except for "not a git repository")
 *
 * @example
 * ```typescript
 * const worktrees = await scanWorktrees('/path/to/repo');
 * console.log(worktrees[0].id); // => "main"
 * ```
 */
async function scanWorktrees(rootDir) {
    const execAsync = (0, util_1.promisify)(child_process_1.exec);
    try {
        const { stdout } = await execAsync('git worktree list', {
            cwd: rootDir,
        });
        const parsed = parseWorktreeOutput(stdout);
        // Get repository name from path
        const repositoryPath = path_1.default.resolve(rootDir);
        const repositoryName = path_1.default.basename(repositoryPath);
        // Filter and validate worktree paths
        return parsed
            .map((wt) => ({
            id: generateWorktreeId(wt.branch, repositoryName),
            name: wt.branch,
            path: path_1.default.resolve(wt.path),
            repositoryPath,
            repositoryName,
        }))
            .filter((wt) => {
            // Git worktrees can be outside the repo root, so we use a more lenient validation
            // Only filter out obviously dangerous system paths
            const dangerousPaths = ['/etc', '/root', '/sys', '/proc', '/dev', '/boot', '/bin', '/sbin', '/usr/bin', '/usr/sbin'];
            const isDangerous = dangerousPaths.some(danger => wt.path.startsWith(danger));
            if (isDangerous) {
                logger.warn('worktree:unsafe-path-skipped', { path: wt.path });
                return false;
            }
            // Check for path traversal attempts in the path itself
            if (wt.path.includes('\x00') || wt.path.includes('..')) {
                logger.warn('worktree:malicious-path-skipped', { path: wt.path });
                return false;
            }
            return true;
        });
    }
    catch (error) {
        // If not a git repository, return empty array
        const errorMessage = error instanceof Error ? error.message : String(error);
        const errorCode = error.code;
        if (errorMessage?.includes('not a git repository') ||
            errorCode === 128) {
            return [];
        }
        // Re-throw other errors
        throw error;
    }
}
/**
 * Scan git worktrees in multiple repositories
 *
 * @param repositoryPaths - Array of repository root paths
 * @returns Array of all Worktree objects from all repositories
 *
 * @example
 * ```typescript
 * const repos = ['/path/to/repo1', '/path/to/repo2'];
 * const worktrees = await scanMultipleRepositories(repos);
 * ```
 */
async function scanMultipleRepositories(repositoryPaths) {
    const allWorktrees = [];
    for (const repoPath of repositoryPaths) {
        try {
            logger.info('repository:scan-start', { repoPath });
            const worktrees = await scanWorktrees(repoPath);
            allWorktrees.push(...worktrees);
            logger.info('repository:scan-complete', { repoPath, worktreeCount: worktrees.length });
        }
        catch (error) {
            logger.error('repository:scan-failed', { repoPath, error: error instanceof Error ? error.message : String(error) });
            // Continue with other repositories even if one fails
        }
    }
    return allWorktrees;
}
/**
 * Sync scanned worktrees to database
 *
 * This function performs a true sync:
 * 1. Groups worktrees by repository
 * 2. For each repository, removes worktrees that no longer exist
 * 3. Upserts all current worktrees
 *
 * @param db - Database instance
 * @param worktrees - Array of worktrees to sync
 *
 * @example
 * ```typescript
 * const worktrees = await scanWorktrees('/path/to/repo');
 * syncWorktreesToDB(db, worktrees);
 * ```
 */
function syncWorktreesToDB(db, worktrees) {
    // If no worktrees provided, do nothing (avoid accidentally deleting all data)
    // SF-C01: Return empty SyncResult instead of void
    if (worktrees.length === 0) {
        return { deletedIds: [], upsertedCount: 0 };
    }
    const allDeletedIds = [];
    let upsertedCount = 0;
    // Group worktrees by repository path
    const worktreesByRepo = new Map();
    for (const worktree of worktrees) {
        const repoPath = worktree.repositoryPath || '';
        if (!worktreesByRepo.has(repoPath)) {
            worktreesByRepo.set(repoPath, []);
        }
        worktreesByRepo.get(repoPath).push(worktree);
    }
    // Process each repository
    for (const [repoPath, repoWorktrees] of worktreesByRepo) {
        if (!repoPath)
            continue;
        // Get current worktree IDs in DB for this repository
        const existingIds = (0, db_1.getWorktreeIdsByRepository)(db, repoPath);
        // Get new worktree IDs from scan
        const newIds = new Set(repoWorktrees.map(wt => wt.id));
        // Find worktrees that no longer exist (deleted)
        const deletedIds = existingIds.filter(id => !newIds.has(id));
        // Delete removed worktrees from DB
        if (deletedIds.length > 0) {
            const result = (0, db_1.deleteWorktreesByIds)(db, deletedIds);
            allDeletedIds.push(...deletedIds);
            logger.info('worktree:cleanup', { deletedCount: result.deletedCount });
        }
        // Upsert current worktrees
        for (const worktree of repoWorktrees) {
            (0, db_1.upsertWorktree)(db, worktree);
            upsertedCount++;
        }
    }
    return { deletedIds: allDeletedIds, upsertedCount };
}
