"use client";
import { useCallback } from "react";
import { IconClock, IconShield } from "@tabler/icons-react";
import { useToast } from "@/components/toast-provider";
import { useAppStore } from "@/components/state-provider";
import {
useCommentsStore,
isPRFeedbackComment,
type PRFeedbackComment,
} from "@/lib/state/slices/comments";
import type { TaskPR } from "@/lib/types/github";
import { isPRAwaitingReview, isPRWaitingOnBranchProtection } from "./pr-task-icon";
import { PRMergeabilityNotice, buildConflictResolutionMessage } from "./pr-mergeability-notice";
/**
* Wires the "Resolve conflicts" CTA to chat context, mirroring the PR detail
* panel (`useAddPRFeedbackAsContext` + `conflictQueued`). Returns a null handler
* when there is no active session so the button is suppressed rather than no-op.
*/
function useResolveConflicts(pr: TaskPR): {
onResolveConflicts: (() => void) | null;
conflictQueued: boolean;
} {
const sessionId = useAppStore((s) => s.tasks.activeSessionId);
const addComment = useCommentsStore((s) => s.addComment);
const { toast } = useToast();
const prNumber = pr.pr_number;
const headBranch = pr.head_branch;
const baseBranch = pr.base_branch;
// True once a conflict prompt for this PR is already queued — avoids piling
// up identical instructions if the user clicks "Resolve conflicts" again.
const conflictQueued = useCommentsStore((s) =>
s.pendingForChat.some((id) => {
const c = s.byId[id];
return (
!!c &&
isPRFeedbackComment(c) &&
c.feedbackType === "conflict" &&
c.sessionId === sessionId &&
c.prNumber === prNumber
);
}),
);
const handler = useCallback(() => {
if (!sessionId || conflictQueued) return;
const content = buildConflictResolutionMessage({ prNumber, headBranch, baseBranch });
const comment: PRFeedbackComment = {
id: `pr-feedback-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
sessionId,
text: content,
createdAt: new Date().toISOString(),
status: "pending",
source: "pr-feedback",
prNumber,
feedbackType: "conflict",
content,
};
addComment(comment);
toast({ description: "Added to chat context" });
}, [sessionId, conflictQueued, prNumber, headBranch, baseBranch, addComment, toast]);
return { onResolveConflicts: sessionId ? handler : null, conflictQueued };
}
/**
* Mergeability cue for the CI hover popover. Surfaces the conflict banner
* ("dirty") and the Blocked / Behind-base chips so an unmergeable PR no longer
* silently drops its merge button with no explanation. Renders nothing for
* clean / unstable / has_hooks and for transient unknown/empty states (we pass
* `mergeable: true` so the popover stays quiet instead of flapping "Not
* mergeable" while GitHub recomputes).
*/
export function PRMergeabilityRow({ pr }: { pr: TaskPR }) {
const { onResolveConflicts, conflictQueued } = useResolveConflicts(pr);
// "blocked" gets a richer note than the bare chip: it explains *why* the
// merge is gated. But when the block is only an outstanding requested review,
// stay silent — the review row above already conveys that and the
// pill/chip/badge deliberately read it as the calm "awaiting review" state,
// so a "Blocked by branch protection" note (or even the bare chip) here would
// contradict them.
if (pr.state === "open" && pr.mergeable_state === "blocked") {
if (isPRAwaitingReview(pr)) return null;
if (isPRWaitingOnBranchProtection(pr)) {
return
{reason}
{reason}