import type { IssueDocument } from "@paperclipai/shared";
import { FileText, Lightbulb, Maximize2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { SidebarDocumentLink } from "@/components/SidebarDocumentLink";
import { buildDocumentAnnotationHash } from "@/lib/document-annotation-hash";
import { useCoordinatorPresentation } from "@/components/coordinator/CoordinatorPresentation";
import { useTaskChatShimmer } from "./TaskChatShimmer";
import type { TaskChatProviderActivityItem } from "./task-chat-model";
const PLAN_PREVIEW_LINE_COUNT = 3;
export function planPreviewContent(markdown: string) {
const lines = markdown
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
const headingIndex = lines.findIndex((line) => /^#\s+/.test(line));
const title = headingIndex >= 0
? lines[headingIndex]!.replace(/^#\s+/, "").trim()
: "Plan";
const preview = lines
.filter((line, index) => index !== headingIndex && !/^(```|~~~|---+$|\*\*\*+$|___+$)/.test(line))
.map((line) => line
.replace(/^#{1,6}\s+/, "")
.replace(/^[-*+]\s+/, "")
.replace(/^\d+[.)]\s+/, "")
.replace(/^>\s*/, "")
.replace(/`([^`]+)`/g, "$1")
.replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/__([^_]+)__/g, "$1")
.replace(/\[([^\]]+)]\([^)]*\)/g, "$1")
.trim())
.filter(Boolean)
.slice(0, PLAN_PREVIEW_LINE_COUNT);
return { title, preview };
}
export type TaskChatPlanPreviewSource =
| {
kind: "saved";
document?: IssueDocument | null;
revision?: number | null;
fallbackTitle?: string | null;
}
| {
kind: "live";
activity: TaskChatProviderActivityItem;
};
export interface TaskChatPlanPreviewCardProps {
source: TaskChatPlanPreviewSource;
href?: string | null;
ariaLabel?: string;
testId?: string;
className?: string;
}
function livePlanContent(activity: TaskChatProviderActivityItem) {
const title = activity.title.trim() && activity.title.trim().toLowerCase() !== "plan"
? activity.title.trim()
: "Plan";
const preview = activity.steps
.map((step) => step.label.trim())
.filter(Boolean)
.slice(0, PLAN_PREVIEW_LINE_COUNT);
if (preview.length === 0 && activity.summary?.trim()) preview.push(activity.summary.trim());
return { title, preview };
}
function planRevision(source: TaskChatPlanPreviewSource): number | null {
if (source.kind === "saved") {
return source.revision ?? source.document?.latestRevisionNumber ?? null;
}
const detail = source.activity.details.find(({ label }) =>
label === "Revision" || label === "Document Revision",
);
if (!detail) return null;
const revision = Number.parseInt(detail.value, 10);
return Number.isFinite(revision) ? revision : null;
}
export function TaskChatPlanPreviewCard({
source,
href = source.kind === "saved" ? "#document-plan" : null,
ariaLabel,
testId = "task-chat-plan-preview",
className,
}: TaskChatPlanPreviewCardProps) {
const coordinator = useCoordinatorPresentation();
const shimmer = useTaskChatShimmer(source.kind === "live" ? source.activity.id : "saved-plan");
const revision = planRevision(source);
const content = source.kind === "saved" && source.document
? planPreviewContent(source.document.body)
: source.kind === "saved"
? { title: source.fallbackTitle?.trim() || "Plan", preview: [] as string[] }
: livePlanContent(source.activity);
const live = source.kind === "live";
if (coordinator) {
const document = source.kind === "saved" ? source.document : null;
const targetHref = href === "#document-plan" && document
? `/issues/${document.issueId}${buildDocumentAnnotationHash({ documentKey: document.key,
revisionId: document.latestRevisionId ?? undefined, threadId: null, commentId: null })}`
: href;
const compactClassName = cn("inline-flex max-w-full items-center gap-2 rounded-lg border border-border bg-muted/25 px-3 py-2.5 text-sm text-muted-foreground",
targetHref && "transition-colors hover:bg-muted/40 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", className);
const compactBody = <>
{live ? "Waiting for the first plan step…" : "Open the synchronized plan to review it."}
)}