/**
 * Import GitHub Repository Use Case
 *
 * Orchestrates importing a GitHub repository: validates the URL, checks auth,
 * detects duplicates by remoteUrl/upstreamUrl, checks push access, and either
 * clones directly or auto-forks when the user lacks push access.
 */
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 { randomBytes } from 'node:crypto';
import { existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { normalizePath } from '../../../domain/shared/normalize-path.js';
import { AddRepositoryUseCase } from './add-repository.use-case.js';
/** Name of the git remote used to track the original upstream repo of a fork. */
const UPSTREAM_REMOTE_NAME = 'upstream';
/**
 * Normalizes a GitHub remote URL for storage and duplicate detection.
 * Lowercases and strips trailing .git suffix.
 */
function normalizeRemoteUrl(nameWithOwner) {
    return `https://github.com/${nameWithOwner.toLowerCase()}`;
}
let ImportGitHubRepositoryUseCase = class ImportGitHubRepositoryUseCase {
    gitHubService;
    repositoryRepo;
    addRepositoryUseCase;
    gitPrService;
    constructor(gitHubService, repositoryRepo, addRepositoryUseCase, gitPrService) {
        this.gitHubService = gitHubService;
        this.repositoryRepo = repositoryRepo;
        this.addRepositoryUseCase = addRepositoryUseCase;
        this.gitPrService = gitPrService;
    }
    async execute(input) {
        // 1. Validate URL — throws GitHubUrlParseError for invalid formats
        const parsed = this.gitHubService.parseGitHubUrl(input.url);
        // 2. Normalize the remote URL for storage and duplicate detection
        const normalizedUrl = normalizeRemoteUrl(parsed.nameWithOwner);
        // 3. Check for duplicate by remoteUrl — skip clone if already tracked
        const existing = await this.repositoryRepo.findByRemoteUrl(normalizedUrl);
        if (existing) {
            return existing;
        }
        // 3b. Check for duplicate by upstreamUrl (fork of this repo already imported)
        const existingFork = await this.repositoryRepo.findByUpstreamUrl(normalizedUrl);
        if (existingFork) {
            return existingFork;
        }
        // 4. Check auth — throws GitHubAuthError if not authenticated
        await this.gitHubService.checkAuth();
        // 5. Check push access to determine if we need to fork
        const { hasPushAccess } = await this.gitHubService.checkPushAccess(parsed.nameWithOwner);
        if (hasPushAccess) {
            return this.cloneDirect(input, parsed.nameWithOwner, parsed.repo, normalizedUrl);
        }
        return this.forkAndClone(input, parsed.nameWithOwner, parsed.repo, normalizedUrl);
    }
    /**
     * Direct clone path — user has push access to the repository.
     */
    async cloneDirect(input, nameWithOwner, repoName, normalizedUrl) {
        const { destination, alreadyUpToDate } = await this.resolveCloneDestination(input, repoName, normalizedUrl);
        if (!alreadyUpToDate) {
            await this.gitHubService.cloneRepository(nameWithOwner, destination, input.cloneOptions);
        }
        const repository = await this.addRepositoryUseCase.execute({
            path: destination,
            name: repoName,
        });
        await this.repositoryRepo.update(repository.id, {
            remoteUrl: normalizedUrl,
        });
        return { ...repository, remoteUrl: normalizedUrl };
    }
    /**
     * Fork-and-clone path — user lacks push access, so we auto-fork first.
     */
    async forkAndClone(input, originalNameWithOwner, repoName, normalizedOriginalUrl) {
        // Fork the repository
        const forkResult = await this.gitHubService.forkRepository(originalNameWithOwner, input.forkOptions);
        // Check if fork was already tracked
        const normalizedForkUrl = normalizeRemoteUrl(forkResult.nameWithOwner);
        const existingForkByRemote = await this.repositoryRepo.findByRemoteUrl(normalizedForkUrl);
        if (existingForkByRemote) {
            return existingForkByRemote;
        }
        // Clone the fork
        const { destination, alreadyUpToDate } = await this.resolveCloneDestination(input, repoName, normalizedForkUrl);
        if (!alreadyUpToDate) {
            await this.gitHubService.cloneRepository(forkResult.nameWithOwner, destination, input.cloneOptions);
        }
        // Configure the upstream remote so users can `git fetch upstream` / sync
        // with the original repo. Without this, the fork has no knowledge of its
        // upstream — breaking PR workflows that rely on upstream as the merge base.
        await this.gitPrService.addRemote(destination, UPSTREAM_REMOTE_NAME, normalizedOriginalUrl);
        // Register the cloned fork
        const repository = await this.addRepositoryUseCase.execute({
            path: destination,
            name: repoName,
        });
        // Update with fork metadata
        await this.repositoryRepo.update(repository.id, {
            remoteUrl: normalizedForkUrl,
            isFork: true,
            upstreamUrl: normalizedOriginalUrl,
        });
        return {
            ...repository,
            remoteUrl: normalizedForkUrl,
            isFork: true,
            upstreamUrl: normalizedOriginalUrl,
        };
    }
    /**
     * Resolve the clone destination, handling the case where the directory already exists.
     *
     * If the destination exists and is a git repo with the same remote origin → git pull and
     * signal that clone can be skipped. If it's a different remote or not a git repo → suffix
     * with a random short hash so the clone proceeds to a new directory.
     */
    async resolveCloneDestination(input, repoName, expectedRemoteUrl) {
        const destination = this.resolveBasePath(input, repoName);
        if (!existsSync(destination)) {
            return { destination, alreadyUpToDate: false };
        }
        // Directory exists — check its remote origin
        const existingRemote = await this.gitPrService.getRemoteUrl(destination);
        if (existingRemote && this.isSameRemote(existingRemote, expectedRemoteUrl)) {
            // Same remote — pull to update, skip clone
            await this.gitPrService.pull(destination);
            return { destination, alreadyUpToDate: true };
        }
        // Different remote or not a git repo — use a suffixed directory name
        const hash = randomBytes(3).toString('hex');
        return { destination: normalizePath(`${destination}-${hash}`), alreadyUpToDate: false };
    }
    resolveBasePath(input, repoName) {
        if (input.dest) {
            return normalizePath(input.dest);
        }
        let baseDir = input.defaultCloneDir ?? join(homedir(), 'repos');
        if (baseDir.startsWith('~/')) {
            baseDir = join(homedir(), baseDir.slice(2));
        }
        return normalizePath(join(baseDir, repoName));
    }
    /**
     * Compare two remote URLs for equivalence, normalizing case, .git suffix, and protocol.
     */
    isSameRemote(a, b) {
        const normalize = (url) => url
            .toLowerCase()
            .replace(/\.git$/, '')
            .replace(/\/$/, '');
        return normalize(a) === normalize(b);
    }
};
ImportGitHubRepositoryUseCase = __decorate([
    injectable(),
    __param(0, inject('IGitHubRepositoryService')),
    __param(1, inject('IRepositoryRepository')),
    __param(2, inject(AddRepositoryUseCase)),
    __param(3, inject('IGitPrService')),
    __metadata("design:paramtypes", [Object, Object, AddRepositoryUseCase, Object])
], ImportGitHubRepositoryUseCase);
export { ImportGitHubRepositoryUseCase };
