/**
 * GitHub Review Service
 *
 * Implements IPlatformReviewService using the gh CLI (gh api) for all
 * GitHub API interactions. Uses modern line + side parameters (not
 * deprecated position) for inline comments.
 *
 * Includes retry logic with exponential backoff for transient errors.
 */
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 { PlatformReviewError, PlatformReviewErrorCode, } from '../../../application/ports/output/services/platform-review-service.interface.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;
const RETRYABLE_STATUS_CODES = [429, 500, 502, 503];
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Sleep for a given number of milliseconds.
 */
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
 * Check if an error is a retryable HTTP error based on status code.
 */
function isRetryableError(error) {
    if (!(error instanceof Error))
        return false;
    const message = error.message;
    return RETRYABLE_STATUS_CODES.some((code) => message.includes(`${code}`));
}
/**
 * Classify error into PlatformReviewErrorCode.
 */
function classifyError(error) {
    if (!(error instanceof Error))
        return PlatformReviewErrorCode.UNKNOWN;
    const msg = error.message;
    if (msg.includes('404') || msg.includes('Not Found'))
        return PlatformReviewErrorCode.PR_NOT_FOUND;
    if (msg.includes('401') || msg.includes('403') || msg.includes('auth'))
        return PlatformReviewErrorCode.AUTH_FAILURE;
    if (msg.includes('429') || msg.includes('rate limit'))
        return PlatformReviewErrorCode.RATE_LIMITED;
    if (msg.includes('422') || msg.includes('Validation'))
        return PlatformReviewErrorCode.SUBMISSION_FAILED;
    if (RETRYABLE_STATUS_CODES.some((code) => msg.includes(`${code}`)))
        return PlatformReviewErrorCode.NETWORK_ERROR;
    return PlatformReviewErrorCode.UNKNOWN;
}
/**
 * Parse a GitHub API patch string into DiffHunk[]/DiffLine[] structures.
 * Reuses the same parsing logic as GitPrService.parseUnifiedDiff.
 */
function parsePatch(patch) {
    if (!patch?.trim()) {
        return { hunks: [], additions: 0, deletions: 0 };
    }
    const hunks = [];
    let currentHunk = null;
    let oldLineNum = 0;
    let newLineNum = 0;
    let additions = 0;
    let deletions = 0;
    for (const line of patch.split('\n')) {
        const hunkHeaderMatch = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@(.*)$/);
        if (hunkHeaderMatch) {
            currentHunk = { header: line, lines: [] };
            hunks.push(currentHunk);
            oldLineNum = parseInt(hunkHeaderMatch[1], 10);
            newLineNum = parseInt(hunkHeaderMatch[2], 10);
            continue;
        }
        if (!currentHunk)
            continue;
        if (line.startsWith('+')) {
            const diffLine = {
                type: 'added',
                content: line.slice(1),
                newNumber: newLineNum,
            };
            currentHunk.lines.push(diffLine);
            newLineNum++;
            additions++;
        }
        else if (line.startsWith('-')) {
            const diffLine = {
                type: 'removed',
                content: line.slice(1),
                oldNumber: oldLineNum,
            };
            currentHunk.lines.push(diffLine);
            oldLineNum++;
            deletions++;
        }
        else if (line.startsWith(' ')) {
            const diffLine = {
                type: 'context',
                content: line.slice(1),
                oldNumber: oldLineNum,
                newNumber: newLineNum,
            };
            currentHunk.lines.push(diffLine);
            oldLineNum++;
            newLineNum++;
        }
        // Skip lines like "\ No newline at end of file"
    }
    return { hunks, additions, deletions };
}
/**
 * Format a suggestion as a GitHub suggestion block for the review body.
 */
export function formatSuggestion(suggestion) {
    return `\`\`\`suggestion\n${suggestion}\n\`\`\``;
}
// ---------------------------------------------------------------------------
// Service
// ---------------------------------------------------------------------------
let GitHubReviewService = class GitHubReviewService {
    execFile;
    constructor(execFile) {
        this.execFile = execFile;
    }
    async fetchPrMetadata(owner, repo, prNumber) {
        const result = await this.ghApi('GET', `/repos/${owner}/${repo}/pulls/${prNumber}`);
        // Fetch commit messages
        const commits = await this.fetchCommitMessages(owner, repo, prNumber);
        return {
            title: result.title,
            description: result.body ?? '',
            baseBranch: result.base.ref,
            headBranch: result.head.ref,
            commits,
            owner,
            repo,
            prNumber,
            prUrl: result.html_url,
        };
    }
    async fetchPrDiff(owner, repo, prNumber) {
        // GitHub API paginates at 100 files per page, max 3000 files
        const allFiles = [];
        let page = 1;
        while (true) {
            const files = await this.ghApi('GET', `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100&page=${page}`);
            if (files.length === 0)
                break;
            for (const file of files) {
                const status = this.mapGitHubFileStatus(file.status);
                const { hunks, additions, deletions } = parsePatch(file.patch ?? '');
                allFiles.push({
                    path: file.filename,
                    oldPath: file.previous_filename,
                    additions: file.additions ?? additions,
                    deletions: file.deletions ?? deletions,
                    status,
                    hunks,
                });
            }
            if (files.length < 100)
                break;
            page++;
        }
        return allFiles;
    }
    async postReview(owner, repo, prNumber, body, comments) {
        const reviewPayload = {
            body,
            event: 'COMMENT',
            comments: comments.map((c) => {
                const comment = {
                    path: c.path,
                    line: c.line,
                    body: c.body,
                    side: c.side,
                };
                if (c.startLine !== undefined) {
                    comment.start_line = c.startLine;
                    comment.start_side = c.startSide ?? c.side;
                }
                return comment;
            }),
        };
        const result = await this.ghApi('POST', `/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, reviewPayload);
        return { reviewUrl: result.html_url };
    }
    async fetchExistingComments(owner, repo, prNumber) {
        const comments = await this.ghApi('GET', `/repos/${owner}/${repo}/pulls/${prNumber}/comments?per_page=100`);
        return comments
            .filter((c) => (c.line ?? c.original_line) !== null)
            .map((c) => ({
            path: c.path,
            line: c.line ?? c.original_line ?? 0,
            body: c.body,
            author: c.user.login,
        }));
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    /**
     * Execute a GitHub API request via gh api with retry logic.
     */
    async ghApi(method, endpoint, body) {
        let lastError;
        for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
            try {
                const args = ['api', endpoint, '--method', method];
                if (body) {
                    args.push('--input', '-');
                }
                const options = {};
                if (body) {
                    options.input = JSON.stringify(body);
                }
                const { stdout } = await this.execFile('gh', args, options);
                return JSON.parse(stdout);
            }
            catch (error) {
                lastError = error instanceof Error ? error : new Error(String(error));
                if (isRetryableError(error) && attempt < MAX_RETRIES - 1) {
                    const delay = BASE_DELAY_MS * Math.pow(2, attempt);
                    await sleep(delay);
                    continue;
                }
                break;
            }
        }
        const code = classifyError(lastError);
        throw new PlatformReviewError(`GitHub API ${method} ${endpoint} failed: ${lastError?.message ?? 'Unknown error'}`, code, lastError);
    }
    /**
     * Fetch commit messages for a PR.
     */
    async fetchCommitMessages(owner, repo, prNumber) {
        try {
            const commits = await this.ghApi('GET', `/repos/${owner}/${repo}/pulls/${prNumber}/commits?per_page=100`);
            return commits.map((c) => c.commit.message.split('\n')[0]);
        }
        catch {
            // Non-critical — return empty array if commit fetch fails
            return [];
        }
    }
    /**
     * Map GitHub file status string to FileDiff status.
     */
    mapGitHubFileStatus(status) {
        switch (status) {
            case 'added':
                return 'added';
            case 'removed':
                return 'deleted';
            case 'renamed':
                return 'renamed';
            default:
                return 'modified';
        }
    }
};
GitHubReviewService = __decorate([
    injectable(),
    __param(0, inject('ExecFunction')),
    __metadata("design:paramtypes", [Function])
], GitHubReviewService);
export { GitHubReviewService };
