/**
 * GitHub Repository Service Implementation
 *
 * Implements IGitHubRepositoryService using the gh CLI for authentication
 * checks, repository listing, cloning (with progress streaming), and URL parsing.
 */
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 { resolve, normalize } from 'node:path';
import { rm } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { GitHubAuthError, GitHubCloneError, GitHubForkError, GitHubPermissionError, GitHubRepoListError, GitHubUrlParseError, } from '../../../application/ports/output/services/github-repository-service.interface.js';
// ---------------------------------------------------------------------------
// URL regex patterns
// ---------------------------------------------------------------------------
/** Matches https://github.com/owner/repo or https://github.com/owner/repo.git */
const HTTPS_PATTERN = /^https:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/;
/** Matches git@github.com:owner/repo.git */
const SSH_PATTERN = /^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/;
/** Matches owner/repo shorthand (no slashes except the one separating owner/repo) */
const SHORTHAND_PATTERN = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/;
/** Default limit for listing repositories */
const DEFAULT_LIST_LIMIT = 30;
let GitHubRepositoryService = class GitHubRepositoryService {
    execFile;
    constructor(execFile) {
        this.execFile = execFile;
    }
    async checkAuth() {
        try {
            // Use `gh auth token` instead of `gh auth status` because `gh auth status`
            // exits non-zero when *any* configured account has a stale token, even if
            // the active account is fully authenticated. `gh auth token` only checks the
            // active account and exits 0 as long as it has a valid token.
            await this.execFile('gh', ['auth', 'token']);
        }
        catch (error) {
            const cause = error instanceof Error ? error : undefined;
            const errnoCode = error?.code;
            if (errnoCode === 'ENOENT') {
                throw new GitHubAuthError('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/', cause);
            }
            throw new GitHubAuthError('GitHub CLI is not authenticated. Run `gh auth login` to sign in.', cause);
        }
    }
    async listUserRepositories(options) {
        const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
        const args = [
            'repo',
            'list',
            ...(options?.owner ? [options.owner] : []),
            '--json',
            'name,nameWithOwner,description,isPrivate,pushedAt',
            '--limit',
            String(limit),
        ];
        if (options?.search) {
            // gh repo list does not have a --match flag; use jq to filter by name
            const escaped = options.search.replace(/"/g, '\\"');
            args.push('-q', `[.[] | select(.name | test("${escaped}"; "i"))]`);
        }
        try {
            const { stdout } = await this.execFile('gh', args);
            return JSON.parse(stdout);
        }
        catch (error) {
            const cause = error instanceof Error ? error : undefined;
            const errnoCode = error?.code;
            if (errnoCode === 'ENOENT') {
                throw new GitHubRepoListError('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/', cause);
            }
            throw new GitHubRepoListError(`Failed to list GitHub repositories: ${cause?.message ?? String(error)}`, cause);
        }
    }
    async listOrganizations() {
        try {
            const { stdout } = await this.execFile('gh', [
                'api',
                '/user/orgs',
                '--jq',
                '[.[] | {login: .login, description: (.description // "")}]',
            ]);
            return JSON.parse(stdout);
        }
        catch (error) {
            const cause = error instanceof Error ? error : undefined;
            const errnoCode = error?.code;
            if (errnoCode === 'ENOENT') {
                throw new GitHubRepoListError('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/', cause);
            }
            throw new GitHubRepoListError(`Failed to list GitHub organizations: ${cause?.message ?? String(error)}`, cause);
        }
    }
    async cloneRepository(nameWithOwner, destination, options) {
        // Validate destination path — reject path traversal
        const resolved = resolve(destination);
        const normalized = normalize(resolved);
        if (normalized !== resolved || destination.includes('..')) {
            throw new GitHubCloneError(`Invalid clone destination: path traversal detected in "${destination}"`);
        }
        return new Promise((resolvePromise, reject) => {
            const child = spawn('gh', ['repo', 'clone', nameWithOwner, resolved], {
                stdio: ['ignore', 'pipe', 'pipe'],
            });
            let stderrOutput = '';
            child.stderr?.on('data', (chunk) => {
                const data = chunk.toString();
                stderrOutput += data;
                options?.onProgress?.(data);
            });
            child.stdout?.on('data', (chunk) => {
                const data = chunk.toString();
                options?.onProgress?.(data);
            });
            child.on('error', async (error) => {
                await this.cleanupPartialClone(resolved);
                reject(new GitHubCloneError(`Failed to clone ${nameWithOwner}: ${error.message}`, error));
            });
            child.on('close', async (code) => {
                if (code === 0) {
                    resolvePromise();
                }
                else {
                    await this.cleanupPartialClone(resolved);
                    reject(new GitHubCloneError(`Clone of ${nameWithOwner} failed with exit code ${code}: ${stderrOutput.trim()}`));
                }
            });
        });
    }
    parseGitHubUrl(url) {
        const trimmed = url.trim();
        if (!trimmed) {
            throw new GitHubUrlParseError('URL cannot be empty');
        }
        // Try HTTPS format
        const httpsMatch = trimmed.match(HTTPS_PATTERN);
        if (httpsMatch) {
            return {
                owner: httpsMatch[1],
                repo: httpsMatch[2],
                nameWithOwner: `${httpsMatch[1]}/${httpsMatch[2]}`,
            };
        }
        // Try SSH format
        const sshMatch = trimmed.match(SSH_PATTERN);
        if (sshMatch) {
            return {
                owner: sshMatch[1],
                repo: sshMatch[2],
                nameWithOwner: `${sshMatch[1]}/${sshMatch[2]}`,
            };
        }
        // Try shorthand format (must be last — it's the loosest pattern)
        const shorthandMatch = trimmed.match(SHORTHAND_PATTERN);
        if (shorthandMatch) {
            return {
                owner: shorthandMatch[1],
                repo: shorthandMatch[2],
                nameWithOwner: `${shorthandMatch[1]}/${shorthandMatch[2]}`,
            };
        }
        throw new GitHubUrlParseError(`Unrecognized GitHub URL format: "${trimmed}". ` +
            'Supported formats: https://github.com/owner/repo, ' +
            'git@github.com:owner/repo.git, or owner/repo shorthand.');
    }
    async getViewerPermission(repoPath) {
        try {
            const { stdout } = await this.execFile('gh', ['repo', 'view', '--json', 'viewerPermission'], {
                cwd: repoPath,
            });
            const parsed = JSON.parse(stdout);
            return parsed.viewerPermission;
        }
        catch (error) {
            const cause = error instanceof Error ? error : undefined;
            const errnoCode = error?.code;
            if (errnoCode === 'ENOENT') {
                throw new GitHubPermissionError('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/', cause);
            }
            throw new GitHubPermissionError(`Failed to check repository permission: ${cause?.message ?? String(error)}`, cause);
        }
    }
    async getAuthenticatedUser() {
        try {
            const { stdout } = await this.execFile('gh', ['api', 'user', '--jq', '.login']);
            return stdout.trim();
        }
        catch (error) {
            const err = error instanceof Error ? error : new Error(String(error));
            throw new GitHubAuthError(`Failed to get authenticated user: ${err.message}`, err);
        }
    }
    async checkPushAccess(nameWithOwner) {
        try {
            const viewerLogin = await this.getAuthenticatedUser();
            const { stdout } = await this.execFile('gh', [
                'api',
                `repos/${nameWithOwner}`,
                '--jq',
                '.permissions.push',
            ]);
            return {
                hasPushAccess: stdout.trim() === 'true',
                viewerLogin,
            };
        }
        catch (error) {
            if (error instanceof GitHubAuthError)
                throw error;
            const err = error instanceof Error ? error : new Error(String(error));
            throw new GitHubPermissionError(`Failed to check push access for ${nameWithOwner}: ${err.message}`, err);
        }
    }
    async forkRepository(nameWithOwner, options) {
        try {
            options?.onProgress?.(`Forking ${nameWithOwner}...`);
            const { stdout, stderr } = await this.execFile('gh', [
                'repo',
                'fork',
                nameWithOwner,
                '--clone=false',
            ]);
            const combined = `${stdout}\n${stderr}`;
            const alreadyExisted = combined.toLowerCase().includes('already exists');
            // Extract fork nameWithOwner from output
            // gh repo fork outputs the fork URL like "https://github.com/user/repo"
            const urlMatch = combined.match(/github\.com\/([^\s/]+\/[^\s/]+)/);
            const forkNwo = urlMatch ? urlMatch[1].replace(/\.git$/, '') : nameWithOwner;
            options?.onProgress?.(alreadyExisted ? 'Using existing fork' : 'Fork created');
            return { nameWithOwner: forkNwo, alreadyExisted };
        }
        catch (error) {
            const err = error instanceof Error ? error : new Error(String(error));
            throw new GitHubForkError(`Failed to fork ${nameWithOwner}: ${err.message}`, err);
        }
    }
    async cleanupPartialClone(destination) {
        try {
            await rm(destination, { recursive: true, force: true });
        }
        catch {
            // Cleanup is best-effort
        }
    }
};
GitHubRepositoryService = __decorate([
    injectable(),
    __param(0, inject('ExecFunction')),
    __metadata("design:paramtypes", [Function])
], GitHubRepositoryService);
export { GitHubRepositoryService };
