/**
 * GitHub Issue Writer Service
 *
 * Octokit-backed implementation of `IGitHubIssueWriter`. Reuses the
 * existing GitHub-integration token (resolved by `gh auth token` — same path
 * `IGitHubRepositoryService` uses) so no new auth flow or token storage is
 * introduced (NFR-10).
 *
 * Resilience: the production factory composes `@octokit/rest` with the
 * retry and throttling plugins. Retry handles transient 5xx and abuse-rate-
 * limit (Retry-After). Throttling pauses requests proactively when primary
 * or secondary rate-limit budgets are near exhaustion.
 */
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);
};
import { promisify } from 'node:util';
import { execFile as execFileCb } from 'node:child_process';
import { injectable } from 'tsyringe';
import { Octokit } from '@octokit/rest';
import { retry } from '@octokit/plugin-retry';
import { throttling } from '@octokit/plugin-throttling';
import { GitHubIssueWriterError, } from '../../../application/ports/output/services/github-issue-writer.interface.js';
const NOOP_LOG = () => undefined;
const RETRY_THROTTLED_OCTOKIT = Octokit.plugin(retry, throttling);
/**
 * Default production factory — composes retry + throttling. Tests inject a
 * stub factory instead so they never hit the network.
 */
export const defaultOctokitFactory = ({ auth }) => new RETRY_THROTTLED_OCTOKIT({
    auth,
    throttle: {
        onRateLimit: () => true,
        onSecondaryRateLimit: () => true,
    },
});
/**
 * Default token resolver — shells out to `gh auth token` exactly like
 * `IGitHubRepositoryService.checkAuth` does. Reuses any token already
 * configured by the user (no new auth flow per NFR-10).
 */
export const defaultTokenResolver = async () => {
    const execFile = promisify(execFileCb);
    const { stdout } = await execFile('gh', ['auth', 'token']);
    const token = stdout.trim();
    if (!token) {
        throw new Error('gh auth token returned empty output — run `gh auth login`');
    }
    return token;
};
function isNotFound(err) {
    return typeof err === 'object' && err !== null && err.status === 404;
}
function wrap(message, cause) {
    const causeError = cause instanceof Error ? cause : new Error(String(cause));
    return new GitHubIssueWriterError(message, causeError);
}
let GitHubIssueWriter = class GitHubIssueWriter {
    tokenResolver;
    octokitFactory;
    clientPromise = null;
    constructor(tokenResolver = defaultTokenResolver, octokitFactory = defaultOctokitFactory) {
        this.tokenResolver = tokenResolver;
        this.octokitFactory = octokitFactory;
    }
    async addLabels(ref, labels) {
        if (labels.length === 0)
            return;
        const client = await this.client();
        try {
            await client.issues.addLabels({
                owner: ref.owner,
                repo: ref.repo,
                issue_number: ref.issueNumber,
                labels,
            });
        }
        catch (err) {
            throw wrap(`Failed to add labels to ${ref.owner}/${ref.repo}#${ref.issueNumber}`, err);
        }
    }
    async removeLabels(ref, labels) {
        if (labels.length === 0)
            return;
        const client = await this.client();
        for (const name of labels) {
            try {
                await client.issues.removeLabel({
                    owner: ref.owner,
                    repo: ref.repo,
                    issue_number: ref.issueNumber,
                    name,
                });
            }
            catch (err) {
                if (isNotFound(err))
                    continue;
                throw wrap(`Failed to remove label "${name}" from ${ref.owner}/${ref.repo}#${ref.issueNumber}`, err);
            }
        }
    }
    async addComment(ref, body) {
        const client = await this.client();
        try {
            await client.issues.createComment({
                owner: ref.owner,
                repo: ref.repo,
                issue_number: ref.issueNumber,
                body,
            });
        }
        catch (err) {
            throw wrap(`Failed to comment on ${ref.owner}/${ref.repo}#${ref.issueNumber}`, err);
        }
    }
    async assignUsers(ref, logins) {
        if (logins.length === 0)
            return;
        const client = await this.client();
        try {
            await client.issues.addAssignees({
                owner: ref.owner,
                repo: ref.repo,
                issue_number: ref.issueNumber,
                assignees: logins,
            });
        }
        catch (err) {
            throw wrap(`Failed to assign users to ${ref.owner}/${ref.repo}#${ref.issueNumber}`, err);
        }
    }
    client() {
        this.clientPromise ??= this.tokenResolver()
            .then((auth) => this.octokitFactory({ auth }))
            .catch((err) => {
            this.clientPromise = null;
            throw wrap('Failed to resolve GitHub auth token for issue writer', err);
        });
        return this.clientPromise;
    }
};
GitHubIssueWriter = __decorate([
    injectable(),
    __metadata("design:paramtypes", [Function, Function])
], GitHubIssueWriter);
export { GitHubIssueWriter };
// Reference NOOP_LOG so tree-shakers leave it intact when an adapter wants
// silent debug hooks; not used in the default code path.
void NOOP_LOG;
