import { existsSync } from 'fs';
import { join } from 'path';
import { simpleGit } from 'simple-git';
import { GitService } from '../git/service.js';
import { logger } from '../utils/logger.js';
import { summarizeError } from '../utils/error-display.js';
/**
 * AgentWorktree — Manages git worktree lifecycle for spawned agents.
 *
 * Each agent works in an isolated git worktree so its file changes are
 * sandboxed from the main working tree.
 *
 * Lifecycle:
 *   create()  — create worktree + branch, return path
 *   merge()   — merge agent branch back to current branch, remove worktree
 *   cleanup() — remove worktree without merging (cancel/error path)
 */
export class AgentWorktree {
    git;
    constructor(cwd) {
        this.git = new GitService(cwd);
    }
    /**
     * Create a new worktree for the given agent.
     * Returns the absolute path to the worktree directory.
     */
    async create(agentId) {
        const worktreePath = this._worktreePath(agentId);
        const branch = this._branchName(agentId);
        logger.debug('AgentWorktree.create', { agentId, worktreePath, branch });
        await this.git.worktreeAdd(worktreePath, branch);
        return worktreePath;
    }
    /**
     * Merge the agent's branch back into the current branch and remove the worktree.
     * Returns true if a merge was performed, false if no changes were found.
     */
    async merge(agentId) {
        const worktreePath = this._worktreePath(agentId);
        const branch = this._branchName(agentId);
        logger.debug('AgentWorktree.merge', { agentId, worktreePath, branch });
        // Check if the agent branch has any commits beyond base
        const hasChanges = await this._hasChanges(worktreePath, branch);
        if (!hasChanges) {
            logger.debug('AgentWorktree.merge: no changes, skipping merge', { agentId });
            await this._removeWorktree(worktreePath);
            await this._deleteBranch(branch);
            return false;
        }
        // Remove worktree first (required before merging the branch)
        await this._removeWorktree(worktreePath);
        // Merge the branch
        await this.git.merge(branch);
        // Clean up the agent branch
        await this._deleteBranch(branch);
        logger.debug('AgentWorktree.merge: complete', { agentId });
        return true;
    }
    /**
     * Remove the worktree without merging (cancel/error path).
     */
    async cleanup(agentId) {
        const worktreePath = this._worktreePath(agentId);
        const branch = this._branchName(agentId);
        logger.debug('AgentWorktree.cleanup', { agentId, worktreePath });
        if (existsSync(worktreePath)) {
            await this._removeWorktree(worktreePath);
        }
        await this._deleteBranch(branch).catch(() => {
            // Branch may not exist if create() never completed
        });
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    _worktreePath(agentId) {
        return join(this.git.getCwd(), '.goodvibes', '.worktrees', `agent-${agentId}`);
    }
    _branchName(agentId) {
        return `agent/${agentId}`;
    }
    /**
     * Check whether the agent branch has any commits that differ from
     * the current branch (the main working tree HEAD).
     */
    async _hasChanges(worktreePath, branch) {
        try {
            // Use a simple-git instance pointed at the worktree to check status
            const wgit = simpleGit({ baseDir: this.git.getCwd() });
            // Count commits on the branch that aren't on the current branch
            const result = await wgit.raw(['rev-list', '--count', `HEAD..${branch}`]);
            const count = parseInt(result.trim(), 10);
            return count > 0;
        }
        catch (err) {
            // If rev-list fails (e.g. brand-new worktree with no commits), treat as no changes
            logger.warn('AgentWorktree._hasChanges: rev-list failed, treating as no changes', { branch, worktreeDir: worktreePath, error: summarizeError(err) });
            return false;
        }
    }
    /**
     * Force-remove a worktree directory.
     */
    async _removeWorktree(worktreePath) {
        try {
            await this.git.worktreeRemove(worktreePath);
        }
        catch (err) {
            // Try with --force flag via raw if normal remove fails
            logger.debug('AgentWorktree._removeWorktree: first attempt failed, retrying with --force', { worktreePath, error: summarizeError(err) });
            try {
                const wgit = simpleGit({ baseDir: this.git.getCwd() });
                await wgit.raw(['worktree', 'remove', '--force', worktreePath]);
            }
            catch (err) {
                logger.error('AgentWorktree._removeWorktree failed', { worktreePath, error: summarizeError(err) });
                throw err;
            }
        }
    }
    /**
     * Delete a branch (no-op if it doesn't exist).
     */
    async _deleteBranch(branch) {
        try {
            const wgit = simpleGit({ baseDir: this.git.getCwd() });
            await wgit.raw(['branch', '-D', branch]);
        }
        catch (err) {
            logger.warn('AgentWorktree._deleteBranch failed during cleanup', { branch, error: summarizeError(err) });
        }
    }
}
