/**
 * Conflict Resolution Service
 *
 * Orchestrates agent-powered conflict resolution during git rebase.
 * Follows the CI watch/fix loop pattern: detect conflict → invoke agent →
 * validate resolution → stage files → continue rebase, with retry logic.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { injectable, inject } from 'tsyringe';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { GitPrError, GitPrErrorCode, } from '../../../../application/ports/output/services/git-pr-service.interface.js';
import { buildConflictResolutionPrompt, buildStashPopResolutionPrompt, } from './conflict-resolution.prompt.js';
const MAX_RETRIES_PER_COMMIT = 3;
/** Conflict markers that indicate unresolved conflicts */
const CONFLICT_MARKERS = ['<<<<<<<', '=======', '>>>>>>>'];
let ConflictResolutionService = class ConflictResolutionService {
    agentProvider;
    gitPrService;
    constructor(agentProvider, gitPrService) {
        this.agentProvider = agentProvider;
        this.gitPrService = gitPrService;
    }
    /**
     * Resolve all conflicts during a rebase operation.
     *
     * Handles multi-commit rebases where each commit may introduce conflicts.
     * For each conflicted commit: reads conflicted files, invokes agent to
     * resolve, validates no remaining markers, stages resolved files, and
     * continues the rebase. Retries up to MAX_RETRIES_PER_COMMIT times.
     *
     * If retries are exhausted, aborts the rebase and throws REBASE_CONFLICT.
     *
     * @param cwd - Working directory (repo root or worktree path)
     * @param featureBranch - Feature branch being rebased
     * @param baseBranch - Base branch being rebased onto
     */
    async resolve(cwd, featureBranch, baseBranch) {
        const executor = await this.agentProvider.getExecutor();
        // Outer loop: handle multi-commit rebases where rebaseContinue
        // may reveal conflicts on the next commit
        while (true) {
            const conflictedFiles = await this.gitPrService.getConflictedFiles(cwd);
            if (conflictedFiles.length === 0) {
                return; // No conflicts — rebase is complete
            }
            // Inner loop: retry agent resolution for the current commit's conflicts
            let resolved = false;
            for (let attempt = 1; attempt <= MAX_RETRIES_PER_COMMIT; attempt++) {
                const fileContents = this.readConflictedFileContents(cwd, conflictedFiles);
                const previousFeedback = attempt > 1 ? this.buildFeedbackFromRemainingMarkers(cwd, conflictedFiles) : undefined;
                const prompt = buildConflictResolutionPrompt({
                    conflictedFiles: fileContents,
                    featureBranch,
                    baseBranch,
                    attemptNumber: attempt,
                    maxAttempts: MAX_RETRIES_PER_COMMIT,
                    previousFeedback,
                });
                await executor.execute(prompt, { cwd });
                // Validate: check no conflict markers remain
                if (this.validateResolution(cwd, conflictedFiles)) {
                    resolved = true;
                    break;
                }
            }
            if (!resolved) {
                // Exhausted retries — abort and report
                await this.gitPrService.rebaseAbort(cwd);
                const remaining = conflictedFiles.join(', ');
                throw new GitPrError(`Failed to resolve conflicts after ${MAX_RETRIES_PER_COMMIT} attempts. ` +
                    `Unresolved files: ${remaining}. The rebase has been aborted.`, GitPrErrorCode.REBASE_CONFLICT);
            }
            // Resolution succeeded — stage files and continue rebase
            await this.gitPrService.stageFiles(cwd, conflictedFiles);
            try {
                await this.gitPrService.rebaseContinue(cwd);
                // rebaseContinue succeeded — loop back to check if subsequent
                // commits have conflicts (getConflictedFiles at top of while loop)
                continue;
            }
            catch (error) {
                if (error instanceof GitPrError && error.code === GitPrErrorCode.REBASE_CONFLICT) {
                    // Next commit has conflicts — loop back to resolve them
                    continue;
                }
                throw error; // Unexpected error
            }
        }
    }
    /**
     * Resolve conflicts from a failed stash pop.
     *
     * After a rebase, stash pop may fail if the rebased code conflicts with
     * stashed uncommitted changes. This method invokes the agent to resolve
     * those conflicts, validates, and stages the resolved files.
     *
     * Unlike resolve(), this does NOT call rebaseContinue — the rebase is
     * already complete. On success, the caller is responsible for dropping
     * the stash entry.
     *
     * @param cwd - Working directory (repo root or worktree path)
     * @param featureBranch - Feature branch name
     * @param baseBranch - Base branch name
     */
    async resolveStashPop(cwd, featureBranch, baseBranch) {
        const conflictedFiles = await this.gitPrService.getConflictedFiles(cwd);
        if (conflictedFiles.length === 0) {
            return;
        }
        const executor = await this.agentProvider.getExecutor();
        for (let attempt = 1; attempt <= MAX_RETRIES_PER_COMMIT; attempt++) {
            const fileContents = this.readConflictedFileContents(cwd, conflictedFiles);
            const previousFeedback = attempt > 1 ? this.buildFeedbackFromRemainingMarkers(cwd, conflictedFiles) : undefined;
            const prompt = buildStashPopResolutionPrompt({
                conflictedFiles: fileContents,
                featureBranch,
                baseBranch,
                attemptNumber: attempt,
                maxAttempts: MAX_RETRIES_PER_COMMIT,
                previousFeedback,
            });
            await executor.execute(prompt, { cwd });
            if (this.validateResolution(cwd, conflictedFiles)) {
                await this.gitPrService.stageFiles(cwd, conflictedFiles);
                return;
            }
        }
        const remaining = conflictedFiles.join(', ');
        throw new Error(`Failed to resolve stash pop conflicts after ${MAX_RETRIES_PER_COMMIT} attempts. ` +
            `Unresolved files: ${remaining}. Your stash entry is preserved — ` +
            `run \`git stash pop\` manually to resolve.`);
    }
    /**
     * Read the contents of conflicted files from the working directory.
     */
    readConflictedFileContents(cwd, filePaths) {
        return filePaths.map((filePath) => {
            try {
                const content = readFileSync(join(cwd, filePath), 'utf-8');
                return { path: filePath, content };
            }
            catch {
                return { path: filePath, content: '(unable to read file)' };
            }
        });
    }
    /**
     * Validate that no conflict markers remain in the previously-conflicted files.
     */
    validateResolution(cwd, filePaths) {
        for (const filePath of filePaths) {
            try {
                const content = readFileSync(join(cwd, filePath), 'utf-8');
                for (const marker of CONFLICT_MARKERS) {
                    if (content.includes(marker)) {
                        return false;
                    }
                }
            }
            catch {
                return false;
            }
        }
        return true;
    }
    /**
     * Build feedback about remaining conflict markers for retry attempts.
     */
    buildFeedbackFromRemainingMarkers(cwd, filePaths) {
        const remaining = [];
        for (const filePath of filePaths) {
            try {
                const content = readFileSync(join(cwd, filePath), 'utf-8');
                const lines = content.split('\n');
                const markerLines = [];
                lines.forEach((line, index) => {
                    if (CONFLICT_MARKERS.some((marker) => line.includes(marker))) {
                        markerLines.push(index + 1);
                    }
                });
                if (markerLines.length > 0) {
                    remaining.push(`- \`${filePath}\`: conflict markers remain on lines ${markerLines.join(', ')}`);
                }
            }
            catch {
                remaining.push(`- \`${filePath}\`: unable to read file`);
            }
        }
        return remaining.length > 0
            ? remaining.join('\n')
            : 'No specific markers found, but validation failed.';
    }
};
ConflictResolutionService = __decorate([
    injectable(),
    __param(0, inject('IAgentExecutorProvider')),
    __param(1, inject('IGitPrService')),
    __metadata("design:paramtypes", [Object, Object])
], ConflictResolutionService);
export { ConflictResolutionService };
