import { z } from "zod";
import { findExecutable } from "../utils/executable.js";
import { resolveGitHubRemote } from "../utils/github-remote.js";
import { runGitCommand } from "../utils/run-git-command.js";
import { execCommand } from "../utils/spawn.js";
const DEFAULT_GITHUB_CACHE_TTL_MS = 30000;
export const GITHUB_POLL_FAST_INTERVAL_MS = 20000;
export const GITHUB_POLL_SLOW_INTERVAL_MS = 120000;
export const GITHUB_POLL_ERROR_BACKOFF_CAP_MS = 300000;
const GITHUB_ENV = {
    GIT_TERMINAL_PROMPT: "0",
};
const LabelSchema = z.object({
    name: z.string().optional(),
});
const GitHubIssueSummarySchema = z.object({
    number: z.number(),
    title: z.string().catch(""),
    url: z.string().catch(""),
    state: z.string().catch(""),
    body: z.string().nullable().catch(null),
    labels: z.array(LabelSchema).catch([]),
    updatedAt: z.string().catch(""),
});
const GitHubPullRequestSummarySchema = z.object({
    number: z.number(),
    title: z.string().catch(""),
    url: z.string().catch(""),
    state: z.string().catch(""),
    body: z.string().nullable().catch(null),
    baseRefName: z.string().catch(""),
    headRefName: z.string().catch(""),
    labels: z.array(LabelSchema).catch([]),
    updatedAt: z.string().catch(""),
});
const PullRequestCheckRunNodeSchema = z.object({
    __typename: z.literal("CheckRun"),
    name: z.string(),
    workflowName: z.string().nullable().optional(),
    conclusion: z.string().nullable().optional(),
    status: z.string().nullable().optional(),
    detailsUrl: z.string().nullable().optional(),
    startedAt: z.string().nullable().optional(),
    completedAt: z.string().nullable().optional(),
    checkSuite: z
        .object({
        workflowRun: z
            .object({
            databaseId: z.number().nullable().optional(),
        })
            .nullable()
            .optional(),
    })
        .nullable()
        .optional(),
});
const PullRequestStatusContextNodeSchema = z.object({
    __typename: z.literal("StatusContext"),
    context: z.string(),
    state: z.string().nullable().optional(),
    targetUrl: z.string().nullable().optional(),
    createdAt: z.string().nullable().optional(),
});
const PullRequestStatusCheckRollupNodeSchema = z.discriminatedUnion("__typename", [
    PullRequestCheckRunNodeSchema,
    PullRequestStatusContextNodeSchema,
]);
const PullRequestStatusCheckRollupArraySchema = z.array(z.unknown());
const LegacyPullRequestStatusCheckRollupSchema = z.object({
    contexts: z.array(z.unknown()),
});
const PullRequestReviewDecisionSchema = z
    .enum(["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"])
    .nullable()
    .catch(null);
const HeadRepositoryOwnerSchema = z
    .object({
    login: z.string().optional(),
})
    .nullable()
    .optional();
const PullRequestMergeableSchema = z.enum(["MERGEABLE", "CONFLICTING", "UNKNOWN"]).catch("UNKNOWN");
const GitHubAutoMergeRequestSchema = z
    .object({
    enabledAt: z.string().nullable().optional().catch(null),
    mergeMethod: z.string().nullable().optional().catch(null),
    enabledBy: z
        .object({
        login: z.string().nullable().optional().catch(null),
    })
        .nullable()
        .optional()
        .catch(null),
})
    .nullable()
    .optional()
    .catch(null);
const GitHubPullRequestFactsGraphqlSchema = z.object({
    data: z.object({
        repository: z
            .object({
            autoMergeAllowed: z.boolean().optional().catch(false),
            mergeCommitAllowed: z.boolean().optional().catch(false),
            squashMergeAllowed: z.boolean().optional().catch(false),
            rebaseMergeAllowed: z.boolean().optional().catch(false),
            viewerDefaultMergeMethod: z.string().nullable().optional().catch(null),
            pullRequest: z
                .object({
                mergeStateStatus: z.string().nullable().optional().catch(null),
                autoMergeRequest: GitHubAutoMergeRequestSchema,
                viewerCanEnableAutoMerge: z.boolean().optional().catch(false),
                viewerCanDisableAutoMerge: z.boolean().optional().catch(false),
                viewerCanMergeAsAdmin: z.boolean().optional().catch(false),
                viewerCanUpdateBranch: z.boolean().optional().catch(false),
                isMergeQueueEnabled: z.boolean().optional().catch(false),
                isInMergeQueue: z.boolean().optional().catch(false),
            })
                .nullable()
                .optional()
                .catch(null),
        })
            .nullable()
            .optional()
            .catch(null),
    }),
});
const CurrentPullRequestStatusSchema = z.object({
    number: z.number().optional(),
    url: z.string().catch(""),
    title: z.string().catch(""),
    state: z.string().catch(""),
    isDraft: z.boolean().optional().catch(false),
    baseRefName: z.string().catch(""),
    headRefName: z.string().catch(""),
    mergedAt: z.string().nullable().optional(),
    statusCheckRollup: z.unknown().optional(),
    reviewDecision: z.unknown().optional(),
    mergeable: PullRequestMergeableSchema.optional().default("UNKNOWN"),
    headRepositoryOwner: HeadRepositoryOwnerSchema,
});
const TimelineAuthorSchema = z
    .object({
    login: z.string().optional(),
    url: z.string().nullable().optional(),
})
    .nullable()
    .optional();
const PullRequestTimelineReviewNodeSchema = z.object({
    id: z.string().catch(""),
    state: z.string().catch(""),
    body: z.string().nullable().catch(null),
    url: z.string().catch(""),
    submittedAt: z.string().nullable().catch(null),
    author: TimelineAuthorSchema,
});
const PullRequestTimelineCommentNodeSchema = z.object({
    id: z.string().catch(""),
    body: z.string().nullable().catch(null),
    url: z.string().catch(""),
    createdAt: z.string().nullable().catch(null),
    author: TimelineAuthorSchema,
});
const PullRequestTimelinePageInfoSchema = z.object({
    hasNextPage: z.boolean().catch(false),
});
const PullRequestTimelineGraphqlSchema = z.object({
    data: z
        .object({
        repository: z
            .object({
            pullRequest: z
                .object({
                number: z.number().optional(),
                reviews: z
                    .object({
                    nodes: z.array(PullRequestTimelineReviewNodeSchema).catch([]),
                    pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
                })
                    .catch({ nodes: [], pageInfo: { hasNextPage: false } }),
                comments: z
                    .object({
                    nodes: z.array(PullRequestTimelineCommentNodeSchema).catch([]),
                    pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
                })
                    .catch({ nodes: [], pageInfo: { hasNextPage: false } }),
            })
                .nullable()
                .optional(),
        })
            .nullable()
            .optional(),
    })
        .optional(),
});
const GitHubRepoViewSchema = z.object({
    owner: z
        .object({
        login: z.string().optional(),
    })
        .nullable()
        .optional(),
    name: z.string().optional(),
    parent: z
        .object({
        owner: z
            .object({
            login: z.string().optional(),
        })
            .nullable()
            .optional(),
        name: z.string().optional(),
    })
        .nullable()
        .optional(),
});
const PullRequestCheckoutTargetSchema = z.object({
    data: z.object({
        repository: z.object({
            pullRequest: z
                .object({
                number: z.number(),
                baseRefName: z.string().catch(""),
                headRefName: z.string().catch(""),
                isCrossRepository: z.boolean().catch(false),
                headRepositoryOwner: z
                    .object({
                    login: z.string().catch(""),
                })
                    .nullable()
                    .optional(),
                headRepository: z
                    .object({
                    sshUrl: z.string().nullable().optional(),
                    url: z.string().nullable().optional(),
                })
                    .nullable()
                    .optional(),
            })
                .nullable(),
        }),
    }),
});
const PULL_REQUEST_CHECKOUT_TARGET_QUERY = `
query PullRequestCheckoutTarget($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      number
      baseRefName
      headRefName
      isCrossRepository
      headRepositoryOwner {
        login
      }
      headRepository {
        sshUrl
        url
      }
    }
  }
}`;
const CURRENT_PR_STATUS_BASE_FIELDS = "number,url,title,state,isDraft,baseRefName,headRefName,mergedAt,reviewDecision,mergeable,headRepositoryOwner";
const CURRENT_PR_STATUS_FIELDS = `${CURRENT_PR_STATUS_BASE_FIELDS},statusCheckRollup`;
const PULL_REQUEST_STATUS_FACTS_QUERY = `
query PullRequestStatusFacts($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    autoMergeAllowed
    mergeCommitAllowed
    squashMergeAllowed
    rebaseMergeAllowed
    viewerDefaultMergeMethod
    pullRequest(number: $number) {
      mergeStateStatus
      autoMergeRequest {
        enabledAt
        mergeMethod
        enabledBy {
          login
        }
      }
      viewerCanEnableAutoMerge
      viewerCanDisableAutoMerge
      viewerCanMergeAsAdmin
      viewerCanUpdateBranch
      isMergeQueueEnabled
      isInMergeQueue
    }
  }
}`;
const PULL_REQUEST_TIMELINE_QUERY = `
query PullRequestTimeline($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      number
      reviews(first: 100) {
        nodes {
          id
          state
          body
          url
          submittedAt
          author {
            login
            url
          }
        }
        pageInfo {
          hasNextPage
        }
      }
      comments(first: 100) {
        nodes {
          id
          body
          url
          createdAt
          author {
            login
            url
          }
        }
        pageInfo {
          hasNextPage
        }
      }
    }
  }
}`;
const DIRECT_PULL_REQUEST_MERGE_STATE_ALLOWLIST = new Set(["CLEAN", "HAS_HOOKS"]);
export class GitHubCliMissingError extends Error {
    constructor() {
        super("GitHub CLI (gh) is not installed or not in PATH");
        this.kind = "missing-cli";
        this.name = "GitHubCliMissingError";
    }
}
export class GitHubAuthenticationError extends Error {
    constructor(params) {
        super("GitHub CLI authentication failed");
        this.kind = "auth-failure";
        this.name = "GitHubAuthenticationError";
        this.stderr = params.stderr;
    }
}
export class GitHubCommandError extends Error {
    constructor(params) {
        super(`GitHub CLI command failed: gh ${params.args.join(" ")}`);
        this.kind = "command-error";
        this.name = "GitHubCommandError";
        this.args = [...params.args];
        this.cwd = params.cwd;
        this.exitCode = params.exitCode;
        this.stderr = params.stderr;
    }
}
export function createGitHubService(options = {}) {
    const ttlMs = options.ttlMs ?? DEFAULT_GITHUB_CACHE_TTL_MS;
    const deps = {
        runner: options.runner ?? runGhCommand,
        resolveGhPath: options.resolveGhPath ?? resolveGhPath,
        now: options.now ?? Date.now,
    };
    const cache = new Map();
    const inFlight = new Map();
    const pollTargets = new Map();
    let api;
    async function cached(params) {
        if (params.readOptions?.force && !params.readOptions.reason) {
            throw new Error("GitHubService forced read requires a reason");
        }
        const key = buildCacheKey({
            cwd: params.cwd,
            method: params.method,
            args: params.args,
        });
        const cachedEntry = cache.get(key);
        const now = deps.now();
        if (!params.readOptions?.force && cachedEntry && cachedEntry.expiresAt > now) {
            return cachedEntry.value;
        }
        const existing = inFlight.get(key);
        if (existing && (!params.readOptions?.force || existing.force)) {
            return existing.promise;
        }
        const request = params
            .load()
            .then((value) => {
            if (inFlight.get(key)?.promise === request) {
                cache.set(key, {
                    value,
                    cwd: params.cwd,
                    expiresAt: deps.now() + ttlMs,
                });
            }
            return value;
        })
            .finally(() => {
            if (inFlight.get(key)?.promise === request) {
                inFlight.delete(key);
            }
        });
        inFlight.set(key, {
            cwd: params.cwd,
            promise: request,
            force: params.readOptions?.force === true,
        });
        return request;
    }
    async function run(args, runOptions) {
        const ghPath = await deps.resolveGhPath();
        if (!ghPath) {
            throw new GitHubCliMissingError();
        }
        try {
            const result = await deps.runner(args, runOptions);
            return result.stdout.trim();
        }
        catch (error) {
            throw normalizeGitHubCommandError(error, {
                args,
                cwd: runOptions.cwd,
            });
        }
    }
    function getPollTargetKey(target) {
        return buildCacheKey({
            cwd: target.cwd,
            method: "getCurrentPullRequestStatus",
            args: {
                headRef: target.headRef,
                headRepositoryOwner: target.headRepositoryOwner,
            },
        });
    }
    function updatePollTargetAfterSuccess(update) {
        const target = pollTargets.get(getPollTargetKey(update));
        if (!target) {
            return;
        }
        target.latestStatus = update.status;
        target.consecutiveErrors = 0;
        if (update.notify) {
            for (const callback of target.callbacks) {
                callback(update.status);
            }
        }
        scheduleGitHubPoll(target);
    }
    function scheduleGitHubPoll(target) {
        scheduleGitHubPollAfter(target, computeGithubNextInterval(target.latestStatus, target.consecutiveErrors));
    }
    function scheduleImmediateGitHubPoll(target) {
        scheduleGitHubPollAfter(target, 0);
    }
    function scheduleGitHubPollAfter(target, delayMs) {
        if (target.retainCount <= 0) {
            return;
        }
        if (target.timer) {
            clearTimeout(target.timer);
        }
        target.timer = setTimeout(() => {
            target.timer = null;
            void runGitHubPoll(target);
        }, delayMs);
    }
    async function runGitHubPoll(target) {
        try {
            await api.getCurrentPullRequestStatus({
                cwd: target.cwd,
                headRef: target.headRef,
                headRepositoryOwner: target.headRepositoryOwner,
                reason: "self-heal-github",
            });
        }
        catch (error) {
            target.consecutiveErrors += 1;
            for (const callback of target.errorCallbacks) {
                callback(error);
            }
            scheduleGitHubPoll(target);
        }
    }
    function closeGitHubPollTarget(target) {
        if (target.timer) {
            clearTimeout(target.timer);
            target.timer = null;
        }
        target.retainCount = 0;
        target.callbacks.clear();
        target.errorCallbacks.clear();
    }
    api = {
        listPullRequests(input) {
            return cached({
                cwd: input.cwd,
                method: "listPullRequests",
                args: { query: input.query ?? "", limit: input.limit ?? 20 },
                readOptions: input,
                load: async () => {
                    const stdout = await run([
                        "pr",
                        "list",
                        "--search",
                        input.query ?? "",
                        "--json",
                        "number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
                        "--limit",
                        String(input.limit ?? 20),
                    ], { cwd: input.cwd });
                    return parsePullRequestSummaries(stdout);
                },
            });
        },
        listIssues(input) {
            return cached({
                cwd: input.cwd,
                method: "listIssues",
                args: { query: input.query ?? "", limit: input.limit ?? 20 },
                readOptions: input,
                load: async () => {
                    const stdout = await run([
                        "issue",
                        "list",
                        "--search",
                        input.query ?? "",
                        "--json",
                        "number,title,url,state,body,labels,updatedAt",
                        "--limit",
                        String(input.limit ?? 20),
                    ], { cwd: input.cwd });
                    return parseIssueSummaries(stdout);
                },
            });
        },
        getPullRequest(input) {
            return cached({
                cwd: input.cwd,
                method: "getPullRequest",
                args: { number: input.number },
                readOptions: input,
                load: async () => {
                    const stdout = await run([
                        "pr",
                        "view",
                        String(input.number),
                        "--json",
                        "number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
                    ], { cwd: input.cwd });
                    return parsePullRequestSummary(stdout);
                },
            });
        },
        async getPullRequestHeadRef(input) {
            const pullRequest = await this.getPullRequest(input);
            return pullRequest.headRefName;
        },
        getPullRequestCheckoutTarget(input) {
            return cached({
                cwd: input.cwd,
                method: "getPullRequestCheckoutTarget",
                args: { number: input.number },
                readOptions: input,
                load: async () => {
                    const repo = await getGitHubRepoView({ cwd: input.cwd, run });
                    const owner = repo?.owner?.login;
                    const name = repo?.name;
                    if (!owner || !name) {
                        throw new Error("Unable to resolve GitHub repository for pull request checkout");
                    }
                    const stdout = await run([
                        "api",
                        "graphql",
                        "-f",
                        `query=${PULL_REQUEST_CHECKOUT_TARGET_QUERY}`,
                        "-F",
                        `owner=${owner}`,
                        "-F",
                        `name=${name}`,
                        "-F",
                        `number=${input.number}`,
                    ], { cwd: input.cwd });
                    return parsePullRequestCheckoutTarget(stdout);
                },
            });
        },
        getCurrentPullRequestStatus(input) {
            return cached({
                cwd: input.cwd,
                method: "getCurrentPullRequestStatus",
                args: {
                    headRef: input.headRef,
                    headRepositoryOwner: input.headRepositoryOwner,
                },
                readOptions: input,
                load: async () => {
                    const status = await resolveCurrentPullRequestView({
                        cwd: input.cwd,
                        headRef: input.headRef,
                        headRepositoryOwner: input.headRepositoryOwner,
                        run,
                    });
                    return addCurrentPullRequestGithubFacts({ cwd: input.cwd, status, run });
                },
            }).then((status) => {
                updatePollTargetAfterSuccess({
                    cwd: input.cwd,
                    headRef: input.headRef,
                    headRepositoryOwner: input.headRepositoryOwner,
                    status,
                    notify: input.reason === "self-heal-github",
                });
                return status;
            });
        },
        getPullRequestTimeline(input) {
            return cached({
                cwd: input.cwd,
                method: "getPullRequestTimeline",
                args: { prNumber: input.prNumber },
                readOptions: input,
                load: async () => {
                    try {
                        const stdout = await run([
                            "api",
                            "graphql",
                            "-f",
                            `query=${PULL_REQUEST_TIMELINE_QUERY}`,
                            "-F",
                            `owner=${input.repoOwner}`,
                            "-F",
                            `name=${input.repoName}`,
                            "-F",
                            `number=${input.prNumber}`,
                        ], { cwd: input.cwd });
                        return parsePullRequestTimeline(stdout, {
                            prNumber: input.prNumber,
                            repoOwner: input.repoOwner,
                            repoName: input.repoName,
                        });
                    }
                    catch (error) {
                        return {
                            prNumber: input.prNumber,
                            repoOwner: input.repoOwner,
                            repoName: input.repoName,
                            items: [],
                            truncated: false,
                            error: mapPullRequestTimelineError(error),
                        };
                    }
                },
            });
        },
        async searchIssuesAndPrs(input) {
            if (input.force && !input.reason) {
                throw new Error("GitHubService forced read requires a reason");
            }
            const kinds = input.kinds ?? ["github-issue", "github-pr"];
            const shouldFetchIssues = kinds.includes("github-issue");
            const shouldFetchPullRequests = kinds.includes("github-pr");
            const readOptions = input.force
                ? { force: true, reason: input.reason }
                : { force: false, reason: input.reason };
            const query = normalizeGitHubSearchQuery(input.query);
            const [issuesResult, prsResult] = await Promise.allSettled([
                shouldFetchIssues
                    ? this.listIssues({
                        cwd: input.cwd,
                        query,
                        limit: input.limit,
                        ...readOptions,
                    })
                    : Promise.resolve(null),
                shouldFetchPullRequests
                    ? this.listPullRequests({
                        cwd: input.cwd,
                        query,
                        limit: input.limit,
                        ...readOptions,
                    })
                    : Promise.resolve(null),
            ]);
            const items = [];
            const requestedResults = [
                shouldFetchIssues ? issuesResult : null,
                shouldFetchPullRequests ? prsResult : null,
            ].filter((result) => result !== null);
            if (requestedResults.length > 0 &&
                requestedResults.every((result) => result.status === "rejected" &&
                    (result.reason instanceof GitHubCliMissingError ||
                        result.reason instanceof GitHubAuthenticationError))) {
                return { items: [], githubFeaturesEnabled: false };
            }
            if (shouldFetchIssues && issuesResult.status === "fulfilled") {
                for (const item of issuesResult.value ?? []) {
                    items.push({
                        kind: "issue",
                        number: item.number,
                        title: item.title,
                        url: item.url,
                        state: item.state,
                        body: item.body,
                        labels: item.labels,
                        baseRefName: null,
                        headRefName: null,
                        updatedAt: item.updatedAt,
                    });
                }
            }
            if (shouldFetchPullRequests && prsResult.status === "fulfilled") {
                for (const item of prsResult.value ?? []) {
                    items.push({
                        kind: "pr",
                        number: item.number,
                        title: item.title,
                        url: item.url,
                        state: item.state,
                        body: item.body,
                        labels: item.labels,
                        baseRefName: item.baseRefName,
                        headRefName: item.headRefName,
                        updatedAt: item.updatedAt,
                    });
                }
            }
            items.sort((left, right) => {
                const leftTime = parseOptionalTime(left.updatedAt ?? null);
                const rightTime = parseOptionalTime(right.updatedAt ?? null);
                return rightTime - leftTime;
            });
            return { items, githubFeaturesEnabled: true };
        },
        async createPullRequest(input) {
            const args = ["api", "-X", "POST", `repos/${input.repo}/pulls`, "-f", `title=${input.title}`];
            args.push("-f", `head=${input.head}`);
            args.push("-f", `base=${input.base}`);
            if (input.body) {
                args.push("-f", `body=${input.body}`);
            }
            const stdout = await run(args, { cwd: input.cwd });
            const parsed = z
                .object({
                url: z.string(),
                number: z.number(),
            })
                .parse(JSON.parse(stdout || "{}"));
            return parsed;
        },
        async mergePullRequest(input) {
            assertDirectPullRequestMergeReady(input);
            await run(["pr", "merge", String(input.prNumber), `--${input.mergeMethod}`], {
                cwd: input.cwd,
                envOverlay: { GH_PROMPT_DISABLED: "1" },
            });
            return { success: true };
        },
        async enablePullRequestAutoMerge(input) {
            assertPullRequestAutoMergeEnableReady(input);
            await run(["pr", "merge", String(input.prNumber), "--auto", `--${input.mergeMethod}`], {
                cwd: input.cwd,
                envOverlay: { GH_PROMPT_DISABLED: "1" },
            });
            return { success: true };
        },
        async disablePullRequestAutoMerge(input) {
            assertPullRequestAutoMergeDisableReady(input);
            await run(["pr", "merge", String(input.prNumber), "--disable-auto"], {
                cwd: input.cwd,
                envOverlay: { GH_PROMPT_DISABLED: "1" },
            });
            return { success: true };
        },
        isAuthenticated(input) {
            return cached({
                cwd: input.cwd,
                method: "isAuthenticated",
                args: {},
                readOptions: input,
                load: async () => {
                    try {
                        await run(["auth", "status"], { cwd: input.cwd });
                        return true;
                    }
                    catch (error) {
                        if (isGitHubAuthenticationError(error)) {
                            throw error;
                        }
                        if (error instanceof GitHubCommandError && isAuthFailureText(error.stderr)) {
                            throw new GitHubAuthenticationError({ stderr: error.stderr });
                        }
                        throw error;
                    }
                },
            });
        },
        retainCurrentPullRequestStatusPoll(input) {
            const key = getPollTargetKey(input);
            let target = pollTargets.get(key);
            if (!target) {
                target = {
                    cwd: input.cwd,
                    headRef: input.headRef,
                    headRepositoryOwner: input.headRepositoryOwner,
                    retainCount: 0,
                    timer: null,
                    latestStatus: null,
                    consecutiveErrors: 0,
                    callbacks: new Set(),
                    errorCallbacks: new Set(),
                };
                pollTargets.set(key, target);
            }
            const isNewlyRetained = target.retainCount === 0;
            target.retainCount += 1;
            if (input.onStatus) {
                target.callbacks.add(input.onStatus);
            }
            if (input.onError) {
                target.errorCallbacks.add(input.onError);
            }
            if (isNewlyRetained) {
                scheduleImmediateGitHubPoll(target);
            }
            else {
                scheduleGitHubPoll(target);
            }
            let unsubscribed = false;
            return {
                unsubscribe: () => {
                    if (unsubscribed) {
                        return;
                    }
                    unsubscribed = true;
                    if (input.onStatus) {
                        target.callbacks.delete(input.onStatus);
                    }
                    if (input.onError) {
                        target.errorCallbacks.delete(input.onError);
                    }
                    target.retainCount -= 1;
                    if (target.retainCount > 0) {
                        return;
                    }
                    closeGitHubPollTarget(target);
                    pollTargets.delete(key);
                },
            };
        },
        invalidate(input) {
            // Local checkout mutations that can alter the current PR identity or PR status
            // must call this with the affected cwd before broadcasting fresh git state.
            for (const [key, entry] of cache.entries()) {
                if (entry.cwd === input.cwd) {
                    cache.delete(key);
                }
            }
            for (const [key, entry] of inFlight.entries()) {
                if (entry.cwd === input.cwd) {
                    inFlight.delete(key);
                }
            }
        },
        dispose() {
            for (const target of pollTargets.values()) {
                closeGitHubPollTarget(target);
            }
            pollTargets.clear();
        },
    };
    return api;
}
function assertDirectPullRequestMergeReady(input) {
    const github = input.status?.github;
    if (!github) {
        throw new Error("GitHub merge facts are unavailable for this pull request");
    }
    if (!DIRECT_PULL_REQUEST_MERGE_STATE_ALLOWLIST.has(github.mergeStateStatus ?? "")) {
        throw new Error("GitHub does not report this pull request as ready for direct merge");
    }
    if (github.isMergeQueueEnabled || github.isInMergeQueue) {
        throw new Error("Direct merge is not available because this repository uses a merge queue");
    }
    if (github.autoMergeRequest !== null) {
        throw new Error("Direct merge is not available because auto-merge is already enabled");
    }
    if (!isPullRequestMergeMethodAllowed(github.repository, input.mergeMethod)) {
        throw new Error(`Direct merge is not available because ${input.mergeMethod} is disabled`);
    }
}
export function assertPullRequestAutoMergeEnableReady(input) {
    const github = input.status?.github;
    if (!github) {
        throw new Error("GitHub auto-merge facts are unavailable for this pull request");
    }
    if (github.mergeStateStatus !== "BLOCKED") {
        throw new Error("GitHub does not report this pull request as blocked for auto-merge");
    }
    if (!github.viewerCanEnableAutoMerge) {
        throw new Error("GitHub does not allow this viewer to enable auto-merge");
    }
    if (!github.repository.autoMergeAllowed) {
        throw new Error("Auto-merge is disabled for this repository");
    }
    if (!isPullRequestMergeMethodAllowed(github.repository, input.mergeMethod)) {
        throw new Error(`Auto-merge is not available because ${input.mergeMethod} is disabled`);
    }
    if (github.autoMergeRequest !== null) {
        throw new Error("Auto-merge is already enabled for this pull request");
    }
    if (github.isMergeQueueEnabled || github.isInMergeQueue) {
        throw new Error("Auto-merge is not available because this repository uses a merge queue");
    }
    if (input.status?.mergeable === "CONFLICTING") {
        throw new Error("Auto-merge is not available because this pull request has conflicts");
    }
}
export function assertPullRequestAutoMergeDisableReady(input) {
    const github = input.status?.github;
    if (!github) {
        throw new Error("GitHub auto-merge facts are unavailable for this pull request");
    }
    if (github.autoMergeRequest === null) {
        throw new Error("Auto-merge is not enabled for this pull request");
    }
    if (!github.viewerCanDisableAutoMerge) {
        throw new Error("GitHub does not allow this viewer to disable auto-merge");
    }
    if (github.isMergeQueueEnabled || github.isInMergeQueue) {
        throw new Error("Auto-merge is not available because this repository uses a merge queue");
    }
}
export function isPullRequestMergeMethodAllowed(repository, method) {
    if (method === "squash") {
        return repository.squashMergeAllowed;
    }
    if (method === "merge") {
        return repository.mergeCommitAllowed;
    }
    return repository.rebaseMergeAllowed;
}
export function computeGithubNextInterval(status, consecutiveErrors) {
    const baseInterval = isGitHubStatusPending(status)
        ? GITHUB_POLL_FAST_INTERVAL_MS
        : GITHUB_POLL_SLOW_INTERVAL_MS;
    if (consecutiveErrors <= 1) {
        return baseInterval;
    }
    return Math.min(baseInterval * 2 ** (consecutiveErrors - 1), GITHUB_POLL_ERROR_BACKOFF_CAP_MS);
}
function isGitHubStatusPending(status) {
    if (!status) {
        return false;
    }
    if (status.checksStatus === "pending") {
        return true;
    }
    return status.checks.some((check) => check.status === "pending");
}
async function resolveGhPath() {
    return findExecutable("gh");
}
async function runGhCommand(args, options) {
    return execCommand("gh", args, {
        cwd: options.cwd,
        envOverlay: { ...GITHUB_ENV, ...options.envOverlay },
        maxBuffer: 10 * 1024 * 1024,
    });
}
const GITHUB_ISSUE_OR_PR_URL_PATTERN = /^https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:pull|issues)\/(\d+)(?:[/?#].*)?$/i;
function normalizeGitHubSearchQuery(query) {
    const trimmed = query.trim();
    const match = trimmed.match(GITHUB_ISSUE_OR_PR_URL_PATTERN);
    return match ? match[1] : query;
}
function buildCacheKey(params) {
    return `${params.cwd}:${params.method}:${stableStringify(params.args)}`;
}
function stableStringify(value) {
    return JSON.stringify(sortJsonValue(value));
}
function sortJsonValue(value) {
    if (Array.isArray(value)) {
        return value.map(sortJsonValue);
    }
    if (!value || typeof value !== "object") {
        return value;
    }
    const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
    const sorted = {};
    for (const [key, entryValue] of entries) {
        sorted[key] = sortJsonValue(entryValue);
    }
    return sorted;
}
function normalizeGitHubCommandError(error, context) {
    if (error instanceof GitHubAuthenticationError) {
        return error;
    }
    if (error instanceof GitHubCommandError) {
        if (isAuthFailureText(error.stderr)) {
            return new GitHubAuthenticationError({ stderr: error.stderr });
        }
        return error;
    }
    const failure = toCommandFailureLike(error);
    if (failure.code === "ENOENT") {
        return new GitHubCliMissingError();
    }
    const stderr = bufferOrStringToString(failure.stderr);
    const message = failure.message ?? "";
    if (isAuthFailureText(stderr) || isAuthFailureText(message)) {
        return new GitHubAuthenticationError({ stderr });
    }
    return new GitHubCommandError({
        args: context.args,
        cwd: context.cwd,
        exitCode: typeof failure.code === "number" ? failure.code : null,
        stderr: stderr || message,
    });
}
function toCommandFailureLike(error) {
    if (!error || typeof error !== "object") {
        return { message: String(error) };
    }
    const record = error;
    return {
        code: typeof record.code === "string" || typeof record.code === "number" || record.code === null
            ? record.code
            : undefined,
        stderr: typeof record.stderr === "string" || Buffer.isBuffer(record.stderr)
            ? record.stderr
            : undefined,
        stdout: typeof record.stdout === "string" || Buffer.isBuffer(record.stdout)
            ? record.stdout
            : undefined,
        message: typeof record.message === "string" ? record.message : undefined,
    };
}
function bufferOrStringToString(value) {
    if (Buffer.isBuffer(value)) {
        return value.toString("utf8");
    }
    return value ?? "";
}
function isGitHubAuthenticationError(error) {
    return error instanceof GitHubAuthenticationError;
}
function isAuthFailureText(text) {
    const normalized = text.toLowerCase();
    return (normalized.includes("gh auth login") ||
        normalized.includes("not logged into any github hosts") ||
        normalized.includes("authentication failed") ||
        normalized.includes("authentication required") ||
        normalized.includes("bad credentials") ||
        normalized.includes("http 401"));
}
function isNoPullRequestFoundError(error) {
    if (!(error instanceof GitHubCommandError)) {
        return false;
    }
    const text = error.stderr.toLowerCase();
    return text.includes("no pull requests found");
}
function isStatusCheckRollupPermissionError(error) {
    if (!(error instanceof GitHubCommandError)) {
        return false;
    }
    return error.stderr.toLowerCase().includes("statuscheckrollup");
}
async function resolveCurrentPullRequestView(options) {
    const viewCandidate = await tryCurrentPullRequestView(options);
    const viewMatch = viewCandidate
        ? pickPullRequestCandidate({
            candidates: [viewCandidate],
            headRef: options.headRef,
            headRepositoryOwner: options.headRepositoryOwner,
        })
        : null;
    if (viewMatch) {
        return viewMatch.status;
    }
    let listHeadRef = options.headRef;
    let listRepo;
    let headRepositoryOwner = options.headRepositoryOwner;
    if (!headRepositoryOwner) {
        const repo = await getGitHubRepoView(options);
        const forkOwner = repo?.owner?.login;
        const parentOwner = repo?.parent?.owner?.login;
        const parentName = repo?.parent?.name;
        if (!forkOwner || !parentOwner || !parentName) {
            return null;
        }
        listHeadRef = `${forkOwner}:${options.headRef}`;
        listRepo = `${parentOwner}/${parentName}`;
        headRepositoryOwner = forkOwner;
    }
    const candidates = await listCurrentPullRequestCandidates({
        cwd: options.cwd,
        headRef: listHeadRef,
        run: options.run,
        repo: listRepo,
    });
    const match = pickPullRequestCandidate({
        candidates,
        headRef: options.headRef,
        headRepositoryOwner,
    });
    return match?.status ?? null;
}
async function addCurrentPullRequestGithubFacts(options) {
    const { status } = options;
    if (!status?.repoOwner || !status.repoName || typeof status.number !== "number") {
        return status;
    }
    const facts = await loadPullRequestGithubFacts({
        cwd: options.cwd,
        owner: status.repoOwner,
        name: status.repoName,
        number: status.number,
        run: options.run,
    });
    if (!facts) {
        return status;
    }
    return {
        ...status,
        github: facts,
    };
}
async function loadPullRequestGithubFacts(options) {
    try {
        const stdout = await options.run([
            "api",
            "graphql",
            "-f",
            `query=${PULL_REQUEST_STATUS_FACTS_QUERY}`,
            "-F",
            `owner=${options.owner}`,
            "-F",
            `name=${options.name}`,
            "-F",
            `number=${options.number}`,
        ], { cwd: options.cwd });
        return parsePullRequestGithubFacts(stdout);
    }
    catch (error) {
        if (error instanceof GitHubCommandError ||
            error instanceof z.ZodError ||
            error instanceof SyntaxError) {
            return null;
        }
        throw error;
    }
}
async function tryCurrentPullRequestView(options) {
    try {
        const stdout = await runCurrentPullRequestStatusCommand({
            cwd: options.cwd,
            run: options.run,
            args: ["pr", "view"],
        });
        return parseCurrentPullRequestCandidate(stdout, options.headRef);
    }
    catch (error) {
        if (isNoPullRequestFoundError(error)) {
            return null;
        }
        throw error;
    }
}
async function listCurrentPullRequestCandidates(options) {
    const args = ["pr", "list"];
    if (options.repo) {
        args.push("--repo", options.repo);
    }
    args.push("--state", "all", "--head", options.headRef, "--limit", "10");
    try {
        const stdout = await runCurrentPullRequestStatusCommand({
            cwd: options.cwd,
            run: options.run,
            args,
        });
        return parseCurrentPullRequestCandidateList(stdout, options.headRef);
    }
    catch (error) {
        if (isNoPullRequestFoundError(error)) {
            return [];
        }
        throw error;
    }
}
async function runCurrentPullRequestStatusCommand(options) {
    try {
        return await options.run([...options.args, "--json", CURRENT_PR_STATUS_FIELDS], {
            cwd: options.cwd,
        });
    }
    catch (error) {
        if (!isStatusCheckRollupPermissionError(error)) {
            throw error;
        }
        return options.run([...options.args, "--json", CURRENT_PR_STATUS_BASE_FIELDS], {
            cwd: options.cwd,
        });
    }
}
async function getGitHubRepoView(options) {
    try {
        const stdout = await options.run(["repo", "view", "--json", "owner,name,parent"], {
            cwd: options.cwd,
        });
        return GitHubRepoViewSchema.parse(JSON.parse(stdout || "{}"));
    }
    catch {
        return null;
    }
}
function parseCurrentPullRequestCandidate(stdout, fallbackHeadRefName) {
    const item = CurrentPullRequestStatusSchema.parse(JSON.parse(stdout || "{}"));
    return toCurrentPullRequestCandidate(item, fallbackHeadRefName);
}
function parseCurrentPullRequestCandidateList(stdout, fallbackHeadRefName) {
    const items = z.array(CurrentPullRequestStatusSchema).parse(JSON.parse(stdout || "[]"));
    return items
        .map((item) => toCurrentPullRequestCandidate(item, fallbackHeadRefName))
        .filter((candidate) => candidate !== null);
}
function parsePullRequestGithubFacts(stdout) {
    const parsed = GitHubPullRequestFactsGraphqlSchema.parse(JSON.parse(stdout || "{}"));
    const repository = parsed.data.repository;
    const pullRequest = repository?.pullRequest;
    if (!repository || !pullRequest) {
        return null;
    }
    return {
        mergeStateStatus: pullRequest.mergeStateStatus ?? null,
        autoMergeRequest: toGitHubAutoMergeRequest(pullRequest.autoMergeRequest),
        viewerCanEnableAutoMerge: pullRequest.viewerCanEnableAutoMerge ?? false,
        viewerCanDisableAutoMerge: pullRequest.viewerCanDisableAutoMerge ?? false,
        viewerCanMergeAsAdmin: pullRequest.viewerCanMergeAsAdmin ?? false,
        viewerCanUpdateBranch: pullRequest.viewerCanUpdateBranch ?? false,
        repository: toGitHubRepositoryMergePolicy(repository),
        isMergeQueueEnabled: pullRequest.isMergeQueueEnabled ?? false,
        isInMergeQueue: pullRequest.isInMergeQueue ?? false,
    };
}
function toGitHubAutoMergeRequest(request) {
    if (!request) {
        return null;
    }
    return {
        enabledAt: request.enabledAt ?? null,
        mergeMethod: request.mergeMethod ?? null,
        enabledBy: request.enabledBy?.login ?? null,
    };
}
function toGitHubRepositoryMergePolicy(repository) {
    return {
        autoMergeAllowed: repository.autoMergeAllowed ?? false,
        mergeCommitAllowed: repository.mergeCommitAllowed ?? false,
        squashMergeAllowed: repository.squashMergeAllowed ?? false,
        rebaseMergeAllowed: repository.rebaseMergeAllowed ?? false,
        viewerDefaultMergeMethod: repository.viewerDefaultMergeMethod ?? null,
    };
}
function toCurrentPullRequestCandidate(item, fallbackHeadRefName) {
    const status = toCurrentPullRequestStatus(item, fallbackHeadRefName);
    if (!status) {
        return null;
    }
    const headRepositoryOwner = item.headRepositoryOwner?.login;
    return {
        status,
        ...(headRepositoryOwner ? { headRepositoryOwner } : {}),
    };
}
function isCandidateForHeadRef(candidate, headRef) {
    return candidate.status.headRefName === headRef && hasResolvedRepoIdentity(candidate.status);
}
function hasResolvedRepoIdentity(status) {
    return Boolean(status.repoOwner && status.repoName);
}
function pickPullRequestCandidate(options) {
    const matching = options.candidates.filter((candidate) => {
        if (!isCandidateForHeadRef(candidate, options.headRef)) {
            return false;
        }
        if (!options.headRepositoryOwner) {
            return true;
        }
        return candidate.headRepositoryOwner === options.headRepositoryOwner;
    });
    matching.sort(comparePullRequestCandidatePreference);
    return matching[0] ?? null;
}
function comparePullRequestCandidatePreference(left, right) {
    return getPullRequestStateRank(left.status) - getPullRequestStateRank(right.status);
}
function getPullRequestStateRank(status) {
    if (status.state === "open" || status.isDraft) {
        return 0;
    }
    if (status.state === "merged") {
        return 1;
    }
    return 2;
}
function parsePullRequestSummaries(stdout) {
    const parsed = z.array(GitHubPullRequestSummarySchema).parse(JSON.parse(stdout || "[]"));
    return parsed.map(toPullRequestSummary);
}
function parsePullRequestSummary(stdout) {
    return toPullRequestSummary(GitHubPullRequestSummarySchema.parse(JSON.parse(stdout || "{}")));
}
function parsePullRequestCheckoutTarget(stdout) {
    const parsed = PullRequestCheckoutTargetSchema.parse(JSON.parse(stdout || "{}"));
    const pullRequest = parsed.data.repository.pullRequest;
    if (!pullRequest) {
        throw new Error("Pull request not found");
    }
    return {
        number: pullRequest.number,
        baseRefName: pullRequest.baseRefName,
        headRefName: pullRequest.headRefName,
        headOwnerLogin: pullRequest.headRepositoryOwner?.login || null,
        headRepositorySshUrl: pullRequest.headRepository?.sshUrl || null,
        headRepositoryUrl: pullRequest.headRepository?.url || null,
        isCrossRepository: pullRequest.isCrossRepository,
    };
}
function toPullRequestSummary(item) {
    return {
        number: item.number,
        title: item.title,
        url: item.url,
        state: item.state,
        body: item.body,
        baseRefName: item.baseRefName,
        headRefName: item.headRefName,
        labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
        updatedAt: item.updatedAt,
    };
}
function parseIssueSummaries(stdout) {
    const parsed = z.array(GitHubIssueSummarySchema).parse(JSON.parse(stdout || "[]"));
    return parsed.map((item) => ({
        number: item.number,
        title: item.title,
        url: item.url,
        state: item.state,
        body: item.body,
        labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
        updatedAt: item.updatedAt,
    }));
}
function parsePullRequestTimeline(stdout, identity) {
    const parsed = PullRequestTimelineGraphqlSchema.parse(JSON.parse(stdout || "{}"));
    const pullRequest = parsed.data?.repository?.pullRequest;
    const items = pullRequest
        ? [
            ...pullRequest.reviews.nodes.flatMap(toPullRequestTimelineReviewItem),
            ...pullRequest.comments.nodes.map(toPullRequestTimelineCommentItem),
        ].sort(compareTimelineItems)
        : [];
    return {
        prNumber: pullRequest?.number ?? identity.prNumber,
        repoOwner: identity.repoOwner,
        repoName: identity.repoName,
        items,
        // S3 deliberately caps timeline fetches at the first 100 reviews and first 100 comments.
        truncated: Boolean(pullRequest?.reviews.pageInfo.hasNextPage || pullRequest?.comments.pageInfo.hasNextPage),
        error: pullRequest ? null : { kind: "not_found", message: "Pull request not found" },
    };
}
function toPullRequestTimelineReviewItem(review) {
    const reviewState = mapTimelineReviewState(review.state, review.body ?? "");
    if (!reviewState) {
        return [];
    }
    return [
        {
            kind: "review",
            id: review.id,
            author: review.author?.login ?? "unknown",
            authorUrl: review.author?.url ?? null,
            body: review.body ?? "",
            createdAt: parseOptionalTime(review.submittedAt ?? null),
            url: review.url,
            reviewState,
        },
    ];
}
function toPullRequestTimelineCommentItem(comment) {
    return {
        kind: "comment",
        id: comment.id,
        author: comment.author?.login ?? "unknown",
        authorUrl: comment.author?.url ?? null,
        body: comment.body ?? "",
        createdAt: parseOptionalTime(comment.createdAt ?? null),
        url: comment.url,
    };
}
function mapTimelineReviewState(state, body) {
    switch (state) {
        case "APPROVED":
            return "approved";
        case "CHANGES_REQUESTED":
            return "changes_requested";
        case "COMMENTED":
            return "commented";
        case "DISMISSED":
        case "PENDING":
            return body.trim().length > 0 ? "commented" : null;
        default:
            return body.trim().length > 0 ? "commented" : null;
    }
}
function compareTimelineItems(left, right) {
    if (left.createdAt !== right.createdAt) {
        return left.createdAt - right.createdAt;
    }
    return left.id.localeCompare(right.id);
}
function mapPullRequestTimelineError(error) {
    if (error instanceof GitHubCommandError) {
        return {
            kind: classifyPullRequestTimelineError(error.stderr),
            message: error.stderr || error.message,
        };
    }
    if (error instanceof GitHubAuthenticationError) {
        return {
            kind: "forbidden",
            message: error.stderr || error.message,
        };
    }
    return {
        kind: "unknown",
        message: error instanceof Error ? error.message : String(error),
    };
}
function classifyPullRequestTimelineError(stderr) {
    const normalized = stderr.toLowerCase();
    if (normalized.includes("could not resolve to a pullrequest") ||
        normalized.includes("pull request not found") ||
        normalized.includes("pullrequest not found")) {
        return "not_found";
    }
    if (normalized.includes("forbidden") ||
        normalized.includes("resource not accessible") ||
        normalized.includes("permission") ||
        normalized.includes("access denied") ||
        normalized.includes("requires authentication") ||
        normalized.includes("http 403")) {
        return "forbidden";
    }
    return "unknown";
}
function toCurrentPullRequestStatus(item, fallbackHeadRefName) {
    if (!item.url || !item.title) {
        return null;
    }
    const repoIdentity = parseGitHubPullRequestRepo(item.url);
    const mergedAt = typeof item.mergedAt === "string" && item.mergedAt.trim().length > 0 ? item.mergedAt : null;
    let state;
    if (mergedAt !== null) {
        state = "merged";
    }
    else if (item.state.trim().length > 0) {
        state = item.state.toLowerCase();
    }
    else {
        state = "";
    }
    const checks = parseStatusCheckRollup(item.statusCheckRollup);
    return {
        ...(typeof item.number === "number" ? { number: item.number } : {}),
        ...(repoIdentity ? { repoOwner: repoIdentity.owner, repoName: repoIdentity.name } : {}),
        url: item.url,
        title: item.title,
        state,
        baseRefName: item.baseRefName,
        headRefName: item.headRefName || fallbackHeadRefName,
        isMerged: mergedAt !== null,
        isDraft: item.isDraft ?? false,
        mergeable: item.mergeable,
        checks,
        checksStatus: computeChecksStatus(checks),
        reviewDecision: mapReviewDecision(item.reviewDecision),
    };
}
function parseGitHubPullRequestRepo(url) {
    try {
        const parsed = new URL(url);
        if (parsed.hostname !== "github.com") {
            return null;
        }
        const [owner, name, kind] = parsed.pathname.split("/").filter(Boolean);
        if (!owner || !name || kind !== "pull") {
            return null;
        }
        return { owner, name };
    }
    catch {
        return null;
    }
}
export function parseStatusCheckRollup(value) {
    const directContexts = PullRequestStatusCheckRollupArraySchema.safeParse(value);
    if (!directContexts.success) {
        const legacyContexts = LegacyPullRequestStatusCheckRollupSchema.safeParse(value);
        if (!legacyContexts.success) {
            return [];
        }
        return parseStatusCheckRollup(legacyContexts.data.contexts);
    }
    const dedupedChecks = new Map();
    for (const entry of directContexts.data) {
        const parsed = PullRequestStatusCheckRollupNodeSchema.safeParse(entry);
        if (!parsed.success) {
            continue;
        }
        const check = buildPullRequestCheck(parsed.data);
        if (!check) {
            continue;
        }
        const existing = dedupedChecks.get(check.name);
        if (!existing || check.recency > existing.recency) {
            dedupedChecks.set(check.name, check);
        }
    }
    return Array.from(dedupedChecks.values(), ({ recency: _recency, ...check }) => check);
}
function buildPullRequestCheck(context) {
    if (context.__typename === "CheckRun") {
        return {
            name: context.name,
            status: mapCheckRunStatus(context.status, context.conclusion),
            url: typeof context.detailsUrl === "string" ? context.detailsUrl : null,
            ...(typeof context.workflowName === "string" && context.workflowName.trim().length > 0
                ? { workflow: context.workflowName }
                : {}),
            ...formatCheckRunDuration(context),
            recency: getCheckRunRecency(context),
        };
    }
    if (context.__typename === "StatusContext") {
        return {
            name: context.context,
            status: mapStatusContextState(context.state),
            url: typeof context.targetUrl === "string" ? context.targetUrl : null,
            recency: getStatusContextRecency(context),
        };
    }
    return null;
}
function mapCheckRunStatus(status, conclusion) {
    if (status !== "COMPLETED") {
        return "pending";
    }
    switch (conclusion) {
        case "SUCCESS":
            return "success";
        case "FAILURE":
        case "TIMED_OUT":
        case "ACTION_REQUIRED":
            return "failure";
        case "CANCELLED":
            return "cancelled";
        case "SKIPPED":
        case "NEUTRAL":
            return "skipped";
        default:
            return "pending";
    }
}
function mapStatusContextState(state) {
    switch (state) {
        case "SUCCESS":
            return "success";
        case "FAILURE":
        case "ERROR":
            return "failure";
        case "EXPECTED":
        case "PENDING":
            return "pending";
        default:
            return "pending";
    }
}
function getCheckRunRecency(context) {
    const workflowRunId = context.checkSuite?.workflowRun?.databaseId;
    if (typeof workflowRunId === "number") {
        return workflowRunId;
    }
    return parseOptionalTime(context.completedAt ?? context.startedAt ?? null);
}
function formatCheckRunDuration(context) {
    const startedAt = parseOptionalTime(context.startedAt ?? null);
    const completedAt = parseOptionalTime(context.completedAt ?? null);
    if (startedAt <= 0 || completedAt <= 0 || completedAt < startedAt) {
        return {};
    }
    const durationSeconds = Math.floor((completedAt - startedAt) / 1000);
    return { duration: formatDurationSeconds(durationSeconds) };
}
function formatDurationSeconds(totalSeconds) {
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;
    const parts = [];
    if (hours > 0) {
        parts.push(`${hours}h`);
    }
    if (minutes > 0) {
        parts.push(`${minutes}m`);
    }
    if (seconds > 0 || parts.length === 0) {
        parts.push(`${seconds}s`);
    }
    return parts.join(" ");
}
function getStatusContextRecency(context) {
    return parseOptionalTime(context.createdAt ?? null);
}
function parseOptionalTime(timestamp) {
    if (!timestamp) {
        return 0;
    }
    const time = Date.parse(timestamp);
    return Number.isNaN(time) ? 0 : time;
}
function computeChecksStatus(checks) {
    if (checks.length === 0) {
        return "none";
    }
    if (checks.some((check) => check.status === "failure")) {
        return "failure";
    }
    if (checks.some((check) => check.status === "pending")) {
        return "pending";
    }
    return "success";
}
function mapReviewDecision(value) {
    const reviewDecision = PullRequestReviewDecisionSchema.parse(value);
    if (reviewDecision === "APPROVED") {
        return "approved";
    }
    if (reviewDecision === "CHANGES_REQUESTED") {
        return "changes_requested";
    }
    if (reviewDecision === "REVIEW_REQUIRED") {
        return "pending";
    }
    return null;
}
export async function resolveGitHubRepo(cwd) {
    try {
        const { stdout } = await runGitCommand(["config", "--get", "remote.origin.url"], {
            cwd,
            env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
        });
        const remote = await resolveGitHubRemote({ remoteUrl: stdout.trim() });
        return remote?.repo ?? null;
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=github-service.js.map