import { z } from "zod";
import { AI_PLAN_STREAMING_END_MARKER, } from "@superblocksteam/library-shared/types";
import { getErrorMeta, getLogger } from "../../../../util/logger.js";
import { AttachmentsStore } from "../../../attachments/index.js";
import { getActiveContext } from "../../../llm/context-v2/utils/index.js";
import { sendInteractiveMessageChannel, sendUserMessageChannel, } from "../../../state-machine/helpers/peer.js";
import { createToolFactory, PermissionLevel, ToolCategory } from "../types.js";
const MAX_ATTEMPTS = 5;
const MAX_PLAN_ATTACHMENTS = 5;
class NonRetryableError extends Error {
    constructor(message) {
        super(message);
        this.name = "NonRetryableError";
    }
}
const PlanComplexityLevels = [
    "Extra Small",
    "Small",
    "Medium",
    "Large",
    "Extra Large",
];
function getTimeEstimateForComplexity(complexity) {
    return PLAN_TIME_ESTIMATES[complexity];
}
function formatIntegrationForPlanPrompt(integration) {
    const { name, displayName, id, type } = integration;
    const labelForLLM = displayName || name;
    return `- ${labelForLLM}\n  tag: @${name}[id=${id}]\n  type: ${type}`;
}
/**
 * Exit Plan Mode - prompts the user to exit Plan mode when they have finished planning and are ready to code.
 */
export const exitPlanModeToolFactory = createToolFactory("exitPlanMode", ({ clark, services, }) => {
    return {
        description: EXIT_PLAN_MODE_DESCRIPTION,
        category: ToolCategory.PLAN,
        defaultPermission: PermissionLevel.ALLOW,
        readOnly: true,
        inputSchema: z.object({
            complexity: z
                .enum(PlanComplexityLevels)
                .describe("Your best estimate of the plan complexity"),
            title: z
                .string()
                .min(1, "Plan title is required")
                .max(160, "Plan title must be 160 characters or fewer")
                .describe("Title to display for the plan. Should be the name of the feature or fix, eg 'Executive orders dashboard', 'User login page', 'Fix data sync bug'"),
            integrationIds: z
                .array(z.string())
                .describe("List of integration IDs that may be referenced in the plan."),
            attachmentIds: z
                .array(z.number())
                .min(0)
                .max(MAX_PLAN_ATTACHMENTS)
                .describe(`List of up to ${MAX_PLAN_ATTACHMENTS} attachment IDs that are relevant to the plan. Call listAttachments first to see available attachments with their IDs, types, and labels.`),
            entityIds: z
                .array(z.string())
                .min(0)
                .describe(`List of targeted entity IDs (components) from the user's selection that are relevant to implementing this plan. Include entities that the plan will modify or depend on.`),
        }),
        execute: async ({ complexity, title, integrationIds, attachmentIds, entityIds }, { messages }) => {
            const { chatSessionStore } = services;
            const sendUserMessage = sendUserMessageChannel(clark, chatSessionStore);
            const sendInteractiveMessage = sendInteractiveMessageChannel(clark, chatSessionStore);
            const availableIntegrations = await services.integrationStore.availableIntegrations();
            const sanitizedTitle = title.trim() || "Plan";
            const allowedIntegrationIds = new Set(integrationIds ?? []);
            const filteredIntegrations = availableIntegrations
                .filter((integration) => allowedIntegrationIds.has(integration.id))
                .map((integration) => ({
                id: integration.id,
                name: integration.name,
                displayName: integration.displayName,
                type: integration.type,
                metadata: {},
            }));
            const unavailableRequestedIds = [...allowedIntegrationIds].filter((id) => !availableIntegrations.some((integration) => integration.id === id));
            if (unavailableRequestedIds.length > 0) {
                getLogger().warn(`exitPlanMode: Requested integration IDs not available: ${unavailableRequestedIds.join(", ")}`);
            }
            // Filter tagged entities for plan execution
            const taggedEntities = clark.context.taggedEntities ?? [];
            let filteredEntities = [];
            if (entityIds && entityIds.length > 0) {
                const entityIdSet = new Set(entityIds);
                filteredEntities = taggedEntities.filter((entity) => entityIdSet.has(entity.id));
                getLogger().info(`Plan entities: ${taggedEntities.length} tagged, ${filteredEntities.length} selected for execution`);
            }
            const integrationGuidance = filteredIntegrations.length > 0
                ? `
Only reference the integrations listed below when they are necessary. Use the exact tag shown for each integration within relevant plan steps (e.g. "Use @Integration[id=123] to ..."). Do not reference any other integrations.

${filteredIntegrations
                    .map((integration) => formatIntegrationForPlanPrompt(integration))
                    .join("\n\n")}`
                : "";
            const baseInstructions = PLAN_GENERATION_PROMPT;
            const planInstructions = [
                baseInstructions.trim(),
                `Plan title: ${sanitizedTitle}`,
                `Use integration.displayName instead of integration.name in titles and natural text`,
                "NEVER include a different title in your response. Begin directly with the plan content. Start with an Overview heading if applicable.",
                integrationGuidance.trim(),
                `The task complexity has been assessed to be '${complexity}`,
            ]
                .filter((section) => section.length > 0)
                .join("\n\n")
                .trim();
            // Append plan generation prompt
            const planPrompt = {
                role: "user",
                content: planInstructions.trim(),
            };
            getLogger().info(`Starting plan generation stream. complexity: ${complexity}`);
            const timeEstimate = getTimeEstimateForComplexity(complexity);
            let attempt = 0;
            let fullPlanText = "";
            while (attempt < MAX_ATTEMPTS) {
                const attemptNumber = attempt + 1;
                let attemptPlanText = "";
                let hasSentInitialPlanMessageThisAttempt = false;
                if (clark.context.abortController?.signal?.aborted) {
                    throw new Error("Plan generation aborted. User canceled, ask how they want to proceed.");
                }
                try {
                    const result = await clark.streamText(services, {
                        user: planPrompt,
                        context: { messages },
                        enableFallback: false,
                        // the llm will re-call the tool if it fails, so we don't need to retry here
                        retry: {
                            maxAttempts: 0,
                        },
                        onError: (error) => {
                            clark.logger.error("[exitPlanMode] LLM stream generation failed", getErrorMeta(error));
                        },
                    });
                    const chunkTypeCounts = {};
                    let streamError = null;
                    let finishReason = null;
                    let reasoningLength = 0;
                    for await (const chunk of result.fullStream) {
                        chunkTypeCounts[chunk.type] =
                            (chunkTypeCounts[chunk.type] ?? 0) + 1;
                        if (chunk.type === "error") {
                            streamError = chunk.error;
                            getLogger().error(`[exitPlanMode] Stream error`, getErrorMeta(chunk.error));
                        }
                        if (chunk.type === "reasoning-delta") {
                            reasoningLength += chunk.text.length;
                        }
                        if (chunk.type === "finish") {
                            finishReason = chunk.finishReason;
                            getLogger().info(`[exitPlanMode] Stream finished with reason: ${chunk.finishReason}`, chunk.totalUsage);
                        }
                        if (chunk.type !== "text-delta") {
                            continue;
                        }
                        const text = chunk.text;
                        if (!hasSentInitialPlanMessageThisAttempt) {
                            // Skip leading whitespace-only chunks so the initial plan
                            // message starts on real content. Once the plan has begun,
                            // whitespace chunks (e.g. "\n\n" between sections) are
                            // meaningful for markdown formatting and must be forwarded.
                            if (text.trim().length === 0) {
                                continue;
                            }
                            const result = await sendInteractiveMessage({
                                role: "assistant",
                                type: "plan",
                                content: text,
                                group: "clark",
                                title: sanitizedTitle,
                                timeEstimate,
                                integrations: filteredIntegrations,
                                entities: filteredEntities.length > 0 ? filteredEntities : undefined,
                                streaming: true,
                                id: crypto.randomUUID(),
                            });
                            if (!result.success) {
                                throw new NonRetryableError("Plan generation failed as there is already an active message that requires user input.");
                            }
                            hasSentInitialPlanMessageThisAttempt = true;
                        }
                        else {
                            await sendUserMessage({
                                role: "assistant",
                                type: "plan-delta",
                                content: text,
                                group: "clark",
                            });
                        }
                        attemptPlanText += text;
                    }
                    if (!attemptPlanText.trim()) {
                        getLogger().info(`[exitPlanMode] No text content produced`, {
                            chunkTypeCounts,
                            finishReason,
                            reasoningLength,
                        });
                        if (streamError) {
                            throw streamError;
                        }
                        throw new Error(`Plan generation produced no content. ` +
                            `chunks: ${JSON.stringify(chunkTypeCounts)}, ` +
                            `finishReason: ${finishReason}, ` +
                            `reasoningLength: ${reasoningLength}`);
                    }
                    fullPlanText = attemptPlanText;
                    // Load all attachments from store by ID (images, PDFs, text files - all unified)
                    let planAttachments = [];
                    if (attachmentIds && attachmentIds.length > 0) {
                        const context = getActiveContext(clark, services);
                        if (!context) {
                            getLogger().error("[exitPlanMode] Context not loaded");
                        }
                        else {
                            const store = new AttachmentsStore();
                            const storedAttachments = store.getAttachmentsByIds(context, attachmentIds.slice(0, MAX_PLAN_ATTACHMENTS));
                            planAttachments =
                                AttachmentsStore.toAttachments(storedAttachments);
                            getLogger().info(`Plan attachments: ${attachmentIds.length} requested, ${planAttachments.length} found in store (max ${MAX_PLAN_ATTACHMENTS})`, planAttachments.map((a) => ({
                                type: a.type,
                                fileName: a.fileName,
                            })));
                        }
                    }
                    await sendUserMessage({
                        role: "assistant",
                        type: "plan-delta",
                        content: AI_PLAN_STREAMING_END_MARKER,
                        streaming: false,
                        group: "clark",
                        attachments: planAttachments.length > 0 ? planAttachments : undefined,
                    });
                    // Map complexity to plan_size for billing
                    const planSizeMap = {
                        "Extra Small": "small",
                        Small: "small",
                        Medium: "medium",
                        Large: "large",
                        "Extra Large": "extra large",
                    };
                    clark.updateContext((context) => ({
                        ...context,
                        planContext: {
                            enableTesting: context.planContext?.enableTesting ?? false,
                            plan: attemptPlanText,
                            integrations: filteredIntegrations,
                            entities: filteredEntities.length > 0 ? filteredEntities : undefined,
                            approved: false,
                            title: sanitizedTitle,
                            attachments: planAttachments.length > 0 ? planAttachments : undefined,
                        },
                        planSize: planSizeMap[complexity],
                    }));
                    getLogger().info(`Plan generation completed successfully on attempt ${attemptNumber}. Plan length: ${attemptPlanText.length}`);
                    return {
                        plan: fullPlanText,
                        message: "Plan presented to user. Awaiting mode confirmation.",
                        title: sanitizedTitle,
                        complexity,
                    };
                }
                catch (error) {
                    if (hasSentInitialPlanMessageThisAttempt) {
                        await sendUserMessage({
                            role: "assistant",
                            type: "plan-delta",
                            content: AI_PLAN_STREAMING_END_MARKER,
                            streaming: false,
                            group: "clark",
                        });
                    }
                    attempt++;
                    getLogger().error(`Attempt ${attempt} failed to generate plan: ${error instanceof Error ? error.message : String(error)}. ${attempt < MAX_ATTEMPTS ? "Retrying..." : "Max attempts reached"}`, getErrorMeta(error));
                    if (clark.context.abortController?.signal?.aborted) {
                        throw error;
                    }
                    if (error instanceof NonRetryableError) {
                        throw error;
                    }
                    if (attempt >= MAX_ATTEMPTS) {
                        throw new Error(`Failed to generate a plan. Please call the tool again.`);
                    }
                }
            }
        },
        getSummary: (input, output) => {
            const title = output?.title ?? input.title ?? "plan";
            const complexity = output?.complexity ?? input.complexity ?? "unknown";
            return {
                input: `title: "${title}", complexity: ${complexity}`,
                output: "Plan presented to user",
            };
        },
    };
});
const PLAN_TIME_ESTIMATES = {
    "Extra Small": "30 seconds",
    Small: "1 minute",
    Medium: "2 minutes",
    Large: "3 minutes",
    "Extra Large": "5+ minutes",
};
const PLAN_GENERATION_PROMPT = `
Based on our conversation and the information gathered, create a specific, actionable plan.

IMPORTANT: You MUST output a plan. Even for trivial tasks, provide a brief plan describing what will be done.

The plan title has been generated separately. DO NOT output a title - begin directly with the plan content.

There are five levels of complexity:
1. Extra Small: For trivial changes like updating a color, text, or single property.
2. Small: For small tweaks and bug fixes, such as changing a filter or fixing a component bug.
3. Medium: For moderate features or enhancements, such as adding a new data-driven component to a page.
4. Large: For significant features or multi-part tasks, such as creating or overhauling a page and associated components and APIs.
5. Extra Large: For major projects or overhauls, such as building a new application section with multiple pages, components, and backend services.

The character limits for each complexity level are defined below. Shorter is always better.

Use bullets.

Focus on WHAT to build, not HOW to build it. Focus mainly on functional requirements and only very high-level implementation details.
Assume the user is non-technical and avoid technical jargon. They only care about what the final outcome will be in terms of functionality and design.

Format your response in markdown with clear sections.

Here are some examples of tasks and plans at each complexity level.

Extra Small - (MAX 150 Characters)
<task>
Make the header text red
</task>

<plan>
Change the header text color to red.
</plan>

Small - (MAX 500 Characters)
<task>
Move the search bar into the header
</task>

<plan>
Move the search input into the header beside the filter dropdown to fix misalignment and improve visual consistency.

Requirements
- Vertically center and maintain balanced padding
- Stay responsive across screen sizes and window resize
- Avoid any visual or layout regressions across the app
</plan>

Medium - (MAX 700 Characters)
<task>
Add a filter that gets the dropdown with values from @demoOrders in postgres
</task>

<plan>
Add a dynamic Order Status filter that retrieves live values from @demoOrders in Postgres. The filter should let users easily view orders by status, updating results instantly without reloading the page.

Requirements
- Display unique ORDER_STATUS values from @demoOrders
- Include a default "All" option showing all orders
- Selecting a status filters the table results immediately
- Automatically update if new statuses are added to the dataset
</plan>

Large - (MAX 900 Characters)
<task>
Add a page with data from @demoOrders to display orders in a searchable table.
</task>

<plan>
Build an interactive Orders page connected to @demoOrders in Postgres. It should display live order data in a searchable, paginated table where users can type in the search bar to instantly filter results without pressing Enter or reloading the page.

Layout & Data
- Show a paginated table with columns Order ID, Customer Name, Status, Amount, and Date
- Load data directly from @demoOrders, defaulting to all orders on initial load

Search Behavior
- Filter by Order ID, Customer Name, or Status, updating results dynamically on each keystroke with debounce
- Provide loading feedback while searching and display a clear "No results found" message when no matches exist
</plan>

Extra Large - (MAX 1100 Characters)
<task>
Build a dashboard with data from @demoOrders. Should have a searchable, paginated table with all orders. Clicking an order should bring you to an orders detail page where full order details are displayed.
</task>

<plan>
Build a two-page Orders experience backed by @demoOrders in Postgres. The first page lists all orders in a searchable, paginated table. The second displays full order details when a row is clicked, enabling smooth navigation between list and detail views.

Orders List & Search
- Show a paginated table with columns Order ID, Customer Name, Status, Amount, and Date
- Add a search bar that filters by Order ID, Customer Name, or Status with instant updates
- Trigger on keystroke with debounce and show loading feedback
- Default view shows all orders

Order Detail Page
- Clicking a row opens a new page showing Order ID, Customer Info, Items, Status, Amounts, and Dates
- Include a back button that returns to the list while preserving search, pagination, and selection

Navigation & Behavior
- Use Order ID for navigation (/order/1234)
- Preserve state and scroll position between pages
- Match dashboard styling and layout
</plan>
`;
const EXIT_PLAN_MODE_DESCRIPTION = `
Use this tool to delegate to a planning expert who will generate a plan and present it to the user.
This tool presents a plan with a "Build It" button that allows the user to switch to Build mode.
NEVER output a plan directly as text - ALWAYS use this tool to present plans properly.

IMPORTANT: Only use this tool AFTER you have gathered enough information to create a specific plan.

ATTACHMENTS:
- IMPORTANT: If the user has uploaded attachments (images, PDFs, CSV, JSON, etc.) at some point in the conversation, call listAttachments first to see available attachment IDs with their types, labels and insights
- Pass relevant attachmentIds to include attachments in the plan (max ${MAX_PLAN_ATTACHMENTS})
- Only include attachments that contain information essential for implementation (UI mockups, data schemas, requirements, etc.)

ENTITIES:
- If there are focused_entities in the context (components selected by the user), extract the "id" field from each entity
- Pass these IDs to the entityIds parameter - they will be displayed in the plan and preserved for build mode
- Include the relevant focused entity IDs unless they are clearly irrelevant to the plan

Before presenting a plan:
- If the user's request is ambiguous or has multiple interpretations, use askMultiChoice to clarify
- If there are unknowns about requirements, preferences, or approach, use askMultiChoice first
- If design decisions need to be made, ask the user for their preferences first
- Gather enough context to create a concrete, specific plan
- IMPORTANT: Check metadata for relevant integrations to validate feasibility and inform the plan
- IMPORTANT: Validate that required data is available via integrations using the executeRequestToIntegration tool if needed
  - eg. if the user wants to display data in a certain way, ensure the integration can support that
  - eg. if the user wants to join data from two sources, ensure that there is a common key available that has overlap
  - eg. if metadata is insufficient to validate feasibility, use executeRequestToIntegration to fetch sample data for validation
  - at minimum, if plan can be validated via metadata alone, just make a test call to confirm availability

When to use this tool:
- You are in Plan mode
- You have a clear understanding of the user's requirements
- You have resolved any ambiguities through askMultiChoice
- You have asked at least 3 clarifying questions
- You have checked relevant integration metadata to inform the plan
- You have validated feasibility via executeRequestToIntegration

When NOT to use:
- When there are still unknowns or ambiguities - use askMultiChoice instead
- When you haven't asked at least 3 clarifying questions
- For research tasks (gathering information, searching files, understanding codebase)
- When answering questions without a coding plan
- When you're already in Build mode
- When you haven't checked relevant integration metadata
- When you have not validated feasibility via metadata and possibly executeRequestToIntegration
- When the task is purely a research or informational request with no code changes needed

You must provide an estimate of the plan complexity when calling this tool.
There are 5 levels of complexity:
1. Extra Small: For really small tweaks and bug fixes, like changing a component property or fixing a typo.
2. Small: For small tweaks and bug fixes, such as changing a filter or fixing a component bug.
3. Medium: For moderate features or enhancements, such as adding a new data-driven component to a page.
4. Large: For significant features or multi-part tasks, such as creating or overhauling a page and associated components and APIs.
5. Extra Large: For major projects or overhauls, such as building a new application section with multiple pages, components, and backend services.

After calling this tool, the user will either:
- Click "Build It" to switch to Build mode and execute the plan
- Provide feedback to refine the plan further

Remember: Always use this tool to present plans - never output plans directly as text.
IF THE TOOL CALL FAILS, DO NOT ATTEMPT TO GENERATE THE PLAN YOURSELF. Call the tool again.
`;
//# sourceMappingURL=exit-plan-mode.js.map