"use client";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useCallback, useRef, useEffect, useLayoutEffect, useMemo } from "react";
import { Sparkles, Check, X, SendHorizonal, Wand2, RotateCcw, Maximize2, History, Cpu, RotateCw, StopCircle, Bug, } from "lucide-react";
import { Button } from "../../components/ui/button";
import { Badge } from "../../components/ui/badge";
import { Textarea } from "../../components/ui/textarea";
import { Popover, PopoverTrigger, PopoverContent } from "../../components/ui/popover";
import { cn } from "../../lib/utils";
import { sanitizeSvg } from "../../lib/sanitize";
import { useEditContext } from "../client/editContext";
import { startAgent, closeAgent } from "../services/agentService";
import { getAiTextEditPrompts, getEditorSettings } from "../services/systemService";
import { loadAiProfiles } from "../services/aiService";
import { localStorageService } from "../services/localStorageService";
// Default Profile ID for the Inline Text Editor profile
const DEFAULT_INLINE_TEXT_EDITOR_PROFILE_ID = "d7e8f9a0-b1c2-4d3e-a5f6-789012345678";
const AI_TEXT_EDIT_MODELS_STORAGE_KEY = "aiTextEdit.selectedModelIds";
/** Normalize profile ID to a stable key (lowercase, no braces) so storage works regardless of API casing. */
function normalizeProfileIdForStorage(profileId) {
    return profileId.replace(/^\{|\}$/g, "").toLowerCase();
}
function getStoredModelIds(profileId) {
    try {
        const parsed = localStorageService.getItem(AI_TEXT_EDIT_MODELS_STORAGE_KEY);
        if (!parsed)
            return null;
        const normalizedKey = normalizeProfileIdForStorage(profileId);
        // Prefer exact normalized key, then find any key that normalizes to the same
        let ids = parsed?.[normalizedKey] ?? null;
        if (!ids && parsed && typeof parsed === "object") {
            for (const key of Object.keys(parsed)) {
                if (normalizeProfileIdForStorage(key) === normalizedKey) {
                    ids = parsed[key] ?? null;
                    break;
                }
            }
        }
        return Array.isArray(ids) && ids.length > 0 ? ids : null;
    }
    catch {
        return null;
    }
}
function setStoredModelIds(profileId, modelIds) {
    try {
        const parsed = localStorageService.getItem(AI_TEXT_EDIT_MODELS_STORAGE_KEY) || {};
        const normalizedKey = normalizeProfileIdForStorage(profileId);
        // Merge: keep other profiles' keys as-is, but normalize keys we care about so we don't accumulate duplicates
        const data = {};
        const seen = new Set();
        for (const key of Object.keys(parsed)) {
            const n = normalizeProfileIdForStorage(key);
            if (seen.has(n))
                continue;
            seen.add(n);
            data[n] = parsed[key] ?? [];
        }
        data[normalizedKey] = modelIds;
        localStorageService.setItem(AI_TEXT_EDIT_MODELS_STORAGE_KEY, data);
    }
    catch {
        // ignore
    }
}
const AI_TEXT_EDIT_RESPONSE_FORMAT_INSTRUCTION = `Return ONLY valid JSON (no markdown, no code fences, no extra text).
Use exactly one of these shapes:
{"replacementText":"<the modified text>"}
{"error":"<short explanation of why you cannot complete this request>"}`;
function tryExtractFirstJsonObject(input) {
    const start = input.indexOf("{");
    if (start < 0)
        return null;
    let depth = 0;
    let inString = false;
    let escaped = false;
    for (let i = start; i < input.length; i++) {
        const ch = input[i];
        if (inString) {
            if (escaped) {
                escaped = false;
            }
            else if (ch === "\\") {
                escaped = true;
            }
            else if (ch === "\"") {
                inString = false;
            }
            continue;
        }
        if (ch === "\"") {
            inString = true;
            continue;
        }
        if (ch === "{")
            depth++;
        if (ch === "}") {
            depth--;
            if (depth === 0) {
                return input.slice(start, i + 1);
            }
        }
    }
    return null;
}
function cleanAgentResponse(raw) {
    return raw
        .trim()
        .replace(/^```[\s\S]*?\n/, "")
        .replace(/\n```$/, "")
        .trim();
}
/**
 * Extract just the human-readable text from a (possibly incomplete) streaming
 * agent response. Strips the markdown fence and pulls the in-progress value of
 * the `replacementText` JSON field so the user sees only the generated text,
 * not the surrounding JSON envelope.
 */
function extractStreamingPreview(raw) {
    const cleaned = cleanAgentResponse(raw);
    if (!cleaned)
        return "";
    const keyMatch = cleaned.match(/"replacementText"\s*:\s*"/);
    if (!keyMatch || keyMatch.index === undefined) {
        // No replacementText field yet — show nothing rather than partial JSON.
        return "";
    }
    const start = keyMatch.index + keyMatch[0].length;
    let i = start;
    let escaped = false;
    while (i < cleaned.length) {
        const ch = cleaned[i];
        if (escaped) {
            escaped = false;
            i++;
            continue;
        }
        if (ch === "\\") {
            escaped = true;
            i++;
            continue;
        }
        if (ch === '"')
            break;
        i++;
    }
    let body = cleaned.slice(start, i);
    // Drop a trailing lone backslash that would make JSON.parse fail mid-stream.
    if (escaped)
        body = body.slice(0, -1);
    try {
        return JSON.parse('"' + body + '"');
    }
    catch {
        return body;
    }
}
function parseAiTextEditResponse(raw) {
    const cleaned = cleanAgentResponse(raw);
    if (!cleaned) {
        return { error: "AI returned an empty response." };
    }
    try {
        let parsed = JSON.parse(cleaned);
        // Some models return JSON wrapped as a JSON string; parse one extra level.
        if (typeof parsed === "string") {
            const reparsed = JSON.parse(parsed);
            parsed = reparsed;
        }
        const parsedObj = parsed;
        if (!parsedObj || typeof parsedObj !== "object") {
            return { error: "AI returned an invalid response format." };
        }
        if (typeof parsedObj.error === "string" && parsedObj.error.trim().length > 0) {
            return { error: parsedObj.error.trim() };
        }
        if (typeof parsedObj.replacementText === "string") {
            return { replacementText: parsedObj.replacementText };
        }
        return { error: "AI response is missing `replacementText` or `error`." };
    }
    catch {
        const extractedJson = tryExtractFirstJsonObject(cleaned);
        if (extractedJson) {
            try {
                const recovered = JSON.parse(extractedJson);
                if (recovered && typeof recovered === "object") {
                    if (typeof recovered.error === "string" && recovered.error.trim()) {
                        return { error: recovered.error.trim() };
                    }
                    if (typeof recovered.replacementText === "string") {
                        return { replacementText: recovered.replacementText };
                    }
                }
            }
            catch {
                // ignore and fall through to regular error handling
            }
        }
        if (cleaned && !cleaned.includes("{")) {
            return { error: cleaned.slice(0, 300) };
        }
        return { error: "AI returned invalid JSON. Please try again." };
    }
}
function extractAgentErrorMessage(message) {
    const payload = message?.payload ?? {};
    const payloadData = payload?.data ?? {};
    const candidates = [
        payload?.error,
        payload?.Error,
        payload?.message,
        payload?.Message,
        payload?.statusMessage,
        payload?.StatusMessage,
        payloadData?.error,
        payloadData?.Error,
        payloadData?.message,
        payloadData?.Message,
        payloadData?.statusMessage,
        payloadData?.StatusMessage,
        message?.data?.error,
    ];
    for (const candidate of candidates) {
        if (typeof candidate === "string" && candidate.trim()) {
            return candidate.trim();
        }
    }
    return null;
}
export function InlineAiDialog({ selectedText = "", contextBefore, contextAfter, onAccept, onClose, position, className, }) {
    const editContext = useEditContext();
    const [state, setState] = useState("input");
    const [instruction, setInstruction] = useState("");
    const [result, setResult] = useState("");
    const [error, setError] = useState(null);
    const [conversationHistory, setConversationHistory] = useState([]);
    const [quickActions, setQuickActions] = useState([]);
    const [profileId, setProfileId] = useState(DEFAULT_INLINE_TEXT_EDITOR_PROFILE_ID);
    const [activeProfile, setActiveProfile] = useState(undefined);
    const [selectedModelIds, setSelectedModelIds] = useState([]);
    const [modelRuns, setModelRuns] = useState({});
    const inputRef = useRef(null);
    const dialogRef = useRef(null);
    const [isLarge, setIsLarge] = useState(false);
    const [clampedPosition, setClampedPosition] = useState(position);
    // Agent state for the current multi-model run
    const activeAgentIdsRef = useRef(new Set());
    const unsubscribeRef = useRef(null);
    const modelRunsRef = useRef({});
    const latestRawContentRef = useRef({});
    const lastParsedRawRef = useRef({});
    // Model popover state
    const [modelPopoverOpen, setModelPopoverOpen] = useState(false);
    // ALT+letter quick select state
    const [altSearchBuffer, setAltSearchBuffer] = useState("");
    const [isAltPressed, setIsAltPressed] = useState(false);
    const altSearchTimeoutRef = useRef(null);
    // Available models from the profile
    const availableModels = activeProfile?.models || [];
    const hasMultipleModels = availableModels.length > 1;
    // Build the selected model list (fallback to first model if somehow stale)
    const selectedModels = useMemo(() => {
        if (!availableModels.length)
            return [];
        const byId = new Map(availableModels.map((m) => [m.id, m]));
        const selected = selectedModelIds.map((id) => byId.get(id)).filter(Boolean);
        return selected.length ? selected : availableModels.slice(0, 1);
    }, [selectedModelIds, availableModels]);
    const selectedModelSummary = useMemo(() => {
        if (!selectedModels.length)
            return null;
        if (selectedModels.length === 1)
            return selectedModels[0]?.name || null;
        return `${selectedModels.length} models selected`;
    }, [selectedModels]);
    const successfulRuns = useMemo(() => Object.values(modelRuns).filter((run) => run.status === "done" && typeof run.replacementText === "string" && run.replacementText.length > 0), [modelRuns]);
    const hasMultipleResults = successfulRuns.length > 1;
    // Automatically switch to larger mode when showing more than one response
    useEffect(() => {
        if (state === "preview" && hasMultipleResults) {
            setIsLarge(true);
        }
    }, [state, hasMultipleResults]);
    useEffect(() => {
        const loadSettings = async () => {
            try {
                const [promptsResult, settingsResult] = await Promise.all([
                    getAiTextEditPrompts(),
                    getEditorSettings()
                ]);
                if (promptsResult.type === "success" && promptsResult.data && promptsResult.data.length > 0) {
                    setQuickActions(promptsResult.data);
                }
                if (settingsResult.type === "success" && settingsResult.data?.aiTextEditProfileId) {
                    setProfileId(settingsResult.data.aiTextEditProfileId);
                }
            }
            catch (err) {
                console.error("[InlineAiDialog] Error loading settings:", err);
            }
        };
        loadSettings();
    }, []);
    // Load the profile when profileId changes to get available models
    useEffect(() => {
        const loadProfile = async () => {
            if (!profileId)
                return;
            try {
                const profiles = await loadAiProfiles();
                const normalizedProfileId = normalizeProfileIdForStorage(profileId);
                const profile = profiles.find((p) => normalizeProfileIdForStorage(p.id) === normalizedProfileId);
                if (profile) {
                    setActiveProfile(profile);
                    const availableModelIds = new Set((profile.models ?? []).map((m) => m.id));
                    const defaultModelId = profile.defaultModelId || profile.models?.[0]?.id;
                    const stored = getStoredModelIds(profileId);
                    const validStored = stored?.filter((id) => availableModelIds.has(id));
                    if (validStored?.length) {
                        setSelectedModelIds(validStored);
                    }
                    else if (defaultModelId) {
                        setSelectedModelIds([defaultModelId]);
                    }
                }
            }
            catch (err) {
                console.error("[InlineAiDialog] Error loading profile:", err);
            }
        };
        loadProfile();
    }, [profileId]);
    useEffect(() => {
        modelRunsRef.current = modelRuns;
    }, [modelRuns]);
    // Persist model selection to localStorage when it or profileId changes
    useEffect(() => {
        if (profileId && selectedModelIds.length > 0) {
            setStoredModelIds(profileId, selectedModelIds);
        }
    }, [profileId, selectedModelIds]);
    // Focus input on mount
    useEffect(() => {
        if (inputRef.current) {
            inputRef.current.focus();
        }
    }, []);
    const evaluateRunCompletion = useCallback((runs) => {
        const values = Object.values(runs);
        if (!values.length)
            return;
        const allTerminal = values.every((run) => run.status === "done" || run.status === "error");
        if (!allTerminal)
            return;
        const doneRuns = values.filter((run) => run.status === "done" && run.replacementText?.length);
        if (doneRuns.length === 0) {
            setError("All selected models failed. See each model’s card for details. You can retry or change your instruction.");
            setResult("");
            // Stay in preview so the user sees per-model error cards and can Retry/Discard
            return;
        }
        if (doneRuns.length === 1) {
            setResult(doneRuns[0]?.replacementText ?? "");
        }
        else {
            setResult("");
        }
        setError(null);
        // State is already "preview" – we show results side-by-side as they stream in
    }, []);
    const updateModelRun = useCallback((agentId, updater) => {
        setModelRuns((prev) => {
            const current = prev[agentId];
            if (!current)
                return prev;
            const updated = updater(current);
            const next = { ...prev, [agentId]: updated };
            modelRunsRef.current = next;
            evaluateRunCompletion(next);
            return next;
        });
    }, [evaluateRunCompletion]);
    const subscribeAgent = useCallback((agentId) => {
        const socket = globalThis.editorSocket;
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            console.warn("[InlineAiDialog] WebSocket not available");
            return;
        }
        socket.send(JSON.stringify({
            type: "agent:subscribe",
            agentId,
        }));
    }, []);
    const unsubscribeAllAgents = useCallback(() => {
        const socket = globalThis.editorSocket;
        if (!socket || socket.readyState !== WebSocket.OPEN)
            return;
        for (const agentId of activeAgentIdsRef.current) {
            socket.send(JSON.stringify({
                type: "agent:unsubscribe",
                agentId,
            }));
        }
    }, []);
    const closeAllActiveAgents = useCallback(() => {
        for (const agentId of activeAgentIdsRef.current) {
            closeAgent(agentId).catch(() => { });
        }
        activeAgentIdsRef.current.clear();
        latestRawContentRef.current = {};
        lastParsedRawRef.current = {};
    }, []);
    const handleStopModelRun = useCallback((agentId) => {
        activeAgentIdsRef.current.delete(agentId);
        delete latestRawContentRef.current[agentId];
        delete lastParsedRawRef.current[agentId];
        closeAgent(agentId).catch(() => { });
        updateModelRun(agentId, (current) => ({
            ...current,
            status: "error",
            error: "Stopped by user",
        }));
    }, [updateModelRun]);
    const handleOpenAgentInSidePanel = useCallback((agentId) => {
        editContext?.openSidebar?.("agents-panel");
        window.dispatchEvent(new CustomEvent("editor:openAgent", { detail: { agentId } }));
    }, [editContext]);
    // Cleanup on unmount
    useEffect(() => {
        return () => {
            unsubscribeAllAgents();
            if (unsubscribeRef.current) {
                unsubscribeRef.current();
            }
            closeAllActiveAgents();
        };
    }, [unsubscribeAllAgents, closeAllActiveAgents]);
    const ensureSocketListener = useCallback(() => {
        if (!editContext?.addSocketMessageListener || unsubscribeRef.current) {
            return;
        }
        const handleMessage = (message) => {
            const messageAgentId = message.agentId || message.payload?.agentId;
            if (!messageAgentId || !activeAgentIdsRef.current.has(messageAgentId)) {
                return;
            }
            const msgType = message.type;
            if (msgType === "agent:run:delta") {
                const deltaData = message.payload?.data;
                const content = deltaData?.deltaContent || "";
                const isIncremental = deltaData?.isIncremental !== false;
                if (!content)
                    return;
                const previousRaw = latestRawContentRef.current[messageAgentId] || "";
                const nextRaw = isIncremental ? previousRaw + content : content;
                latestRawContentRef.current[messageAgentId] = nextRaw;
                const existingRun = modelRunsRef.current[messageAgentId];
                if (existingRun) {
                    modelRunsRef.current = {
                        ...modelRunsRef.current,
                        [messageAgentId]: {
                            ...existingRun,
                            status: "streaming",
                            rawContent: nextRaw,
                        },
                    };
                }
                updateModelRun(messageAgentId, (current) => {
                    const nextRawContent = isIncremental ? current.rawContent + content : content;
                    return {
                        ...current,
                        status: "streaming",
                        rawContent: nextRawContent,
                    };
                });
            }
            else if (msgType === "agent:run:status" || msgType === "agent:status:update") {
                const statusData = message.payload?.data || message.payload || {};
                const normalizedStatus = (statusData.state || statusData.status || "").toString().toLowerCase();
                if (normalizedStatus === "error") {
                    const errorMsg = extractAgentErrorMessage(message) || "AI could not complete this request.";
                    updateModelRun(messageAgentId, (current) => {
                        if (current.status === "done")
                            return current; // don't overwrite success
                        return { ...current, status: "error", error: errorMsg };
                    });
                }
            }
            else if (msgType === "agent:run:error" || msgType === "agent:error") {
                const errorMsg = extractAgentErrorMessage(message) || "AI could not complete this request.";
                updateModelRun(messageAgentId, (current) => {
                    if (current.status === "done")
                        return current; // don't overwrite success
                    return { ...current, status: "error", error: errorMsg };
                });
            }
            else if (msgType === "agent:message:complete" || msgType === "agent:run:complete") {
                const lifecycleError = extractAgentErrorMessage(message);
                if (lifecycleError) {
                    updateModelRun(messageAgentId, (current) => ({
                        ...current,
                        status: "error",
                        error: lifecycleError,
                    }));
                    return;
                }
                const current = modelRunsRef.current[messageAgentId];
                const rawContentToParse = latestRawContentRef.current[messageAgentId] ?? current?.rawContent ?? "";
                if (lastParsedRawRef.current[messageAgentId] === rawContentToParse) {
                    return;
                }
                lastParsedRawRef.current[messageAgentId] = rawContentToParse;
                const parsed = parseAiTextEditResponse(rawContentToParse);
                if (parsed.error) {
                    updateModelRun(messageAgentId, (run) => ({
                        ...run,
                        status: "error",
                        error: parsed.error,
                    }));
                }
                else {
                    updateModelRun(messageAgentId, (run) => ({
                        ...run,
                        status: "done",
                        replacementText: parsed.replacementText ?? "",
                        error: undefined,
                    }));
                }
            }
        };
        unsubscribeRef.current = editContext.addSocketMessageListener(handleMessage);
    }, [editContext, updateModelRun]);
    const executeAiModification = useCallback(async (customInstruction) => {
        if (!editContext)
            return;
        if (!selectedModels.length) {
            setError("Select at least one model.");
            return;
        }
        setError(null);
        setResult("");
        setModelRuns({});
        modelRunsRef.current = {};
        latestRawContentRef.current = {};
        lastParsedRawRef.current = {};
        unsubscribeAllAgents();
        closeAllActiveAgents();
        try {
            // Build the message with context around the selection if available
            const hasContext = contextBefore || contextAfter;
            const contextInfo = hasContext
                ? `CONTEXT (text surrounding the selection):
Before: "${contextBefore || ""}"
After: "${contextAfter || ""}"

`
                : "";
            let message;
            if (selectedText) {
                message = `${contextInfo}I need you to modify ONLY the following selected portion of the text:

SELECTED TEXT TO MODIFY:
"""
${selectedText}
"""

${customInstruction}

Return ONLY the modified version of the selected text, not the entire field.
${AI_TEXT_EDIT_RESPONSE_FORMAT_INSTRUCTION}`;
            }
            else {
                message = `${contextInfo}I need you to generate text at the cursor position.

${customInstruction}

Return ONLY the text to insert at the cursor position, not the entire field.
${AI_TEXT_EDIT_RESPONSE_FORMAT_INSTRUCTION}`;
            }
            setConversationHistory((prev) => [...prev, customInstruction]);
            let agentContext = undefined;
            const contextMode = activeProfile?.editorContextMode;
            const shouldIncludeContext = contextMode === "onCreate" || contextMode === "live";
            if (shouldIncludeContext) {
                const item = editContext.currentItemDescriptor;
                if (item) {
                    agentContext = {
                        items: [
                            {
                                id: item.id,
                                language: item.language,
                                version: item.version,
                                name: editContext.item?.name,
                                path: editContext.item?.path,
                            },
                        ],
                        // Include selected components if any
                        components: editContext.selection?.length
                            ? editContext.selection.map((componentId) => ({
                                componentId,
                                pageItem: {
                                    id: item.id,
                                    language: item.language,
                                    version: item.version,
                                    name: editContext.item?.name,
                                },
                            }))
                            : undefined,
                        // Set the current item as the active context
                        currentItemId: item.id,
                    };
                }
            }
            ensureSocketListener();
            const initialRuns = {};
            for (const model of selectedModels) {
                const agentId = crypto.randomUUID();
                activeAgentIdsRef.current.add(agentId);
                latestRawContentRef.current[agentId] = "";
                delete lastParsedRawRef.current[agentId];
                initialRuns[agentId] = {
                    agentId,
                    modelId: model.id,
                    modelName: model.name,
                    status: "pending",
                    rawContent: "",
                };
            }
            setModelRuns(initialRuns);
            modelRunsRef.current = initialRuns;
            setState("preview"); // Show results immediately side-by-side as they stream in
            await Promise.all(Object.values(initialRuns).map(async (run) => {
                subscribeAgent(run.agentId);
                try {
                    await startAgent({
                        agentId: run.agentId,
                        message,
                        sessionId: editContext.sessionId,
                        profileId: profileId,
                        mode: "autonomous",
                        model: run.modelId,
                        context: agentContext,
                    });
                }
                catch (runErr) {
                    const errorMsg = runErr instanceof Error ? runErr.message : "Failed to start model run";
                    updateModelRun(run.agentId, (current) => ({
                        ...current,
                        status: "error",
                        error: errorMsg,
                    }));
                }
            }));
        }
        catch (err) {
            console.error("AI modification error:", err);
            setError(err instanceof Error ? err.message : "An error occurred");
            setState("input");
        }
    }, [
        editContext,
        selectedText,
        contextBefore,
        contextAfter,
        profileId,
        activeProfile,
        selectedModels,
        closeAllActiveAgents,
        unsubscribeAllAgents,
        ensureSocketListener,
        subscribeAgent,
        updateModelRun,
    ]);
    const handleQuickAction = useCallback((action) => {
        executeAiModification(action.instruction);
    }, [executeAiModification]);
    // ALT+letter quick select for quick actions
    useEffect(() => {
        // Only active in input state
        if (state !== "input")
            return;
        const handleKeyDown = (e) => {
            // Track ALT key state
            if (e.key === "Alt") {
                setIsAltPressed(true);
                e.preventDefault();
                return;
            }
            // Handle letter typing while ALT is pressed
            if (isAltPressed && e.key.length === 1 && /[a-zA-Z]/.test(e.key)) {
                e.preventDefault();
                // Clear any existing timeout
                if (altSearchTimeoutRef.current) {
                    clearTimeout(altSearchTimeoutRef.current);
                }
                const newBuffer = altSearchBuffer + e.key.toLowerCase();
                setAltSearchBuffer(newBuffer);
                // Find matching actions (case-insensitive prefix match on label)
                const matches = quickActions.filter((action) => action.label.toLowerCase().startsWith(newBuffer));
                if (matches.length === 1 && matches[0]) {
                    // Execute the action immediately if it's a unique match
                    handleQuickAction(matches[0]);
                    setAltSearchBuffer("");
                    setIsAltPressed(false);
                }
                else if (matches.length > 1) {
                    // Multiple matches - check if any match is EXACTLY the buffer
                    const exactMatch = matches.find(m => m.label.toLowerCase() === newBuffer);
                    if (exactMatch) {
                        handleQuickAction(exactMatch);
                        setAltSearchBuffer("");
                        setIsAltPressed(false);
                    }
                    else {
                        // Wait for more input
                        altSearchTimeoutRef.current = setTimeout(() => {
                            setAltSearchBuffer("");
                        }, 1500);
                    }
                }
                else {
                    // No matches - auto-clear buffer after 1.5s of no typing
                    altSearchTimeoutRef.current = setTimeout(() => {
                        setAltSearchBuffer("");
                    }, 1500);
                }
            }
        };
        const handleKeyUp = (e) => {
            if (e.key === "Alt") {
                // Clear buffer when ALT is released
                setIsAltPressed(false);
                setAltSearchBuffer("");
                if (altSearchTimeoutRef.current) {
                    clearTimeout(altSearchTimeoutRef.current);
                }
            }
        };
        window.addEventListener("keydown", handleKeyDown);
        window.addEventListener("keyup", handleKeyUp);
        return () => {
            window.removeEventListener("keydown", handleKeyDown);
            window.removeEventListener("keyup", handleKeyUp);
            if (altSearchTimeoutRef.current) {
                clearTimeout(altSearchTimeoutRef.current);
            }
        };
    }, [state, isAltPressed, altSearchBuffer, quickActions, handleQuickAction]);
    const handleUseRunResult = useCallback((replacementText) => {
        if (!replacementText)
            return;
        onAccept(replacementText);
    }, [onAccept]);
    const toggleModelSelection = useCallback((modelId) => {
        setSelectedModelIds((prev) => {
            if (prev.includes(modelId)) {
                if (prev.length === 1) {
                    return prev;
                }
                return prev.filter((id) => id !== modelId);
            }
            return [...prev, modelId];
        });
    }, []);
    const handleTryAgain = useCallback(() => {
        // Go back to input state to refine
        setState("input");
        setResult("");
    }, []);
    const handleRetry = useCallback(() => {
        const lastInstruction = conversationHistory[conversationHistory.length - 1];
        if (lastInstruction) {
            executeAiModification(lastInstruction);
        }
    }, [conversationHistory, executeAiModification]);
    const handleStartOver = useCallback(() => {
        unsubscribeAllAgents();
        closeAllActiveAgents();
        setModelRuns({});
        modelRunsRef.current = {};
        latestRawContentRef.current = {};
        lastParsedRawRef.current = {};
        setState("input");
        setResult("");
        setConversationHistory([]);
        setError(null);
    }, [unsubscribeAllAgents, closeAllActiveAgents]);
    const handleClose = useCallback(() => {
        unsubscribeAllAgents();
        closeAllActiveAgents();
        onClose();
    }, [unsubscribeAllAgents, closeAllActiveAgents, onClose]);
    const toggleLarge = useCallback(() => {
        setIsLarge((prev) => !prev);
    }, []);
    const renderTextWithContext = (centerText, isResult = false) => {
        const hasContext = contextBefore || contextAfter;
        // No context available - just show the selected text
        if (!hasContext) {
            return (_jsx("span", { className: cn("font-medium px-0.5 rounded", isResult ? "bg-green-100 text-green-900" : "bg-violet-100 text-violet-900 italic"), children: centerText }));
        }
        // Show context with ellipsis indicators
        return (_jsxs("div", { className: "leading-relaxed text-xs whitespace-pre-wrap", children: [contextBefore && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-400", children: "..." }), _jsx("span", { className: "text-gray-400", children: contextBefore })] })), _jsx("span", { className: cn("font-medium mx-0.5 px-0.5 rounded", isResult ? "bg-green-100 text-green-900" : "bg-violet-100 text-violet-900 italic"), children: centerText }), contextAfter && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-400", children: contextAfter }), _jsx("span", { className: "text-gray-400", children: "..." })] }))] }));
    };
    const handleKeyDown = useCallback((e) => {
        if (e.key === "Escape") {
            e.preventDefault();
            if (state === "preview") {
                handleTryAgain();
            }
            else {
                handleClose();
            }
        }
    }, [state, handleTryAgain, handleClose]);
    const handleTextareaKeyDown = useCallback((e) => {
        if (e.key === "Enter" && !e.shiftKey) {
            e.preventDefault();
            if (instruction.trim()) {
                executeAiModification(instruction.trim());
                setInstruction("");
            }
        }
    }, [instruction, executeAiModification]);
    // Clamp the dialog position so it never renders outside the viewport.
    // This matters because `position` is based on selection coordinates which can be near the viewport edge.
    useLayoutEffect(() => {
        if (!position) {
            if (clampedPosition !== undefined)
                setClampedPosition(undefined);
            return;
        }
        const margin = 12;
        const vw = window.innerWidth || 0;
        const vh = window.innerHeight || 0;
        // Use ResizeObserver to track the actual size of the dialog as it changes
        const updatePosition = () => {
            const el = dialogRef.current;
            if (!el)
                return;
            const width = el.offsetWidth || 0;
            const height = el.offsetHeight || 0;
            if (!width || !height || !vw || !vh) {
                const next = { x: position.x, y: position.y };
                if (clampedPosition?.x !== next.x || clampedPosition?.y !== next.y)
                    setClampedPosition(next);
                return;
            }
            const x = Math.min(Math.max(margin, position.x), Math.max(margin, vw - width - margin));
            const y = Math.min(Math.max(margin, position.y), Math.max(margin, vh - height - margin));
            if (clampedPosition?.x !== x || clampedPosition?.y !== y) {
                setClampedPosition({ x, y });
            }
        };
        const resizeObserver = new ResizeObserver(() => {
            updatePosition();
        });
        if (dialogRef.current) {
            resizeObserver.observe(dialogRef.current);
        }
        // Initial update
        updatePosition();
        return () => {
            resizeObserver.disconnect();
        };
        // Re-clamp when the content changes (height changes) or when the requested position changes.
    }, [position?.x, position?.y, state, result, selectedText, isLarge, modelRuns, hasMultipleResults]);
    return (_jsxs("div", { ref: dialogRef, className: cn("bg-white/95 backdrop-blur-sm rounded-xl shadow-[0_10px_40px_rgba(0,0,0,0.1)] border border-gray-100 max-h-[80vh] flex flex-col overflow-hidden agent-inline-dialog transition-all duration-300 ease-in-out", isLarge ? "w-[800px]" : "w-[420px]", "animate-in fade-in-0 zoom-in-95 duration-200 ease-out", className), style: clampedPosition
            ? {
                position: "fixed",
                left: clampedPosition.x,
                top: clampedPosition.y,
                zIndex: 95,
            }
            : undefined, onKeyDown: handleKeyDown, children: [_jsxs("div", { className: "flex-shrink-0 flex items-center gap-2.5 px-4 py-3 bg-gradient-to-r from-violet-500/5 to-purple-500/5 border-b border-gray-100", children: [_jsx("div", { className: "flex items-center justify-center w-6 h-6 rounded-lg bg-violet-600 shadow-sm shadow-violet-200", children: _jsx(Sparkles, { className: "w-3.5 h-3.5 text-white" }) }), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "text-sm font-semibold text-gray-800 leading-tight", children: "AI Text Edit" }), conversationHistory.length > 0 && (_jsxs("span", { className: "text-[10px] text-gray-400 font-medium", children: [conversationHistory.length, " ", conversationHistory.length === 1 ? "turn" : "turns", " in history"] }))] }), _jsxs("div", { className: "ml-auto flex items-center gap-1", children: [_jsx("button", { onClick: toggleLarge, className: cn("p-1.5 hover:bg-gray-100 rounded-lg transition-all duration-200 group", isLarge && "bg-violet-50 text-violet-600 hover:bg-violet-100"), title: isLarge ? "Make smaller" : "Make larger", children: _jsx(Maximize2, { className: cn("w-4 h-4 text-gray-400 group-hover:text-gray-600", isLarge && "text-violet-600") }) }), _jsx("button", { onClick: handleClose, className: "p-1.5 hover:bg-gray-100 rounded-lg transition-all duration-200 group", title: "Close", children: _jsx(X, { className: "w-4 h-4 text-gray-400 group-hover:text-gray-600" }) })] })] }), selectedText && (_jsxs("div", { className: "flex-shrink-0 px-4 py-3 bg-gray-50/50 border-b border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2", children: [_jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-200" }), "Selected Content"] }), _jsxs("div", { className: "relative max-h-48 overflow-y-auto whitespace-pre-wrap transition-all duration-300", children: [_jsx("div", { className: "absolute left-0 top-0 bottom-0 w-0.5 bg-violet-200 rounded-full" }), _jsx("div", { className: "pl-3", children: renderTextWithContext(selectedText) })] })] })), _jsxs("div", { className: "p-4 min-h-0 flex-1 overflow-y-auto", children: [state === "input" && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [isAltPressed && (_jsxs("div", { className: "flex items-center gap-2 text-xs text-violet-600 bg-violet-50 px-2.5 py-1.5 rounded-lg animate-in fade-in duration-150", children: [_jsx("kbd", { className: "px-1.5 py-0.5 bg-violet-100 rounded text-[10px] font-mono font-bold", children: "ALT" }), _jsx("span", { className: "font-medium", children: altSearchBuffer ? (_jsxs(_Fragment, { children: ["Type to select: ", _jsx("span", { className: "font-bold", children: altSearchBuffer })] })) : ("Type first letters of an action...") })] })), _jsx("div", { className: "flex flex-wrap gap-2", children: quickActions.map((action) => {
                                            const hasSvgIcon = action.icon?.trim() && (action.icon.trim().startsWith("<svg") || action.icon.trim().startsWith("<?xml"));
                                            const labelLower = action.label.toLowerCase();
                                            const isMatch = isAltPressed && altSearchBuffer && labelLower.startsWith(altSearchBuffer);
                                            const isPartialMatch = isAltPressed && altSearchBuffer && labelLower.includes(altSearchBuffer) && !isMatch;
                                            return (_jsxs("button", { onClick: () => handleQuickAction(action), className: cn("flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-lg transition-all duration-200", "bg-white border border-gray-200 text-gray-600", "hover:border-violet-300 hover:text-violet-700 hover:bg-violet-50", "active:scale-95 shadow-sm hover:shadow-md", isMatch && "border-violet-400 bg-violet-100 text-violet-700 ring-2 ring-violet-300", isPartialMatch && "border-violet-200 bg-violet-50/50", isAltPressed && !isMatch && !isPartialMatch && "opacity-50"), children: [hasSvgIcon && (_jsx("div", { className: "w-3.5 h-3.5 flex items-center justify-center [&>svg]:w-full [&>svg]:h-full shrink-0", dangerouslySetInnerHTML: {
                                                            __html: sanitizeSvg(action.icon.trim()),
                                                        } })), isAltPressed && altSearchBuffer ? (_jsxs("span", { children: [_jsx("span", { className: cn(isMatch && "font-bold underline underline-offset-2"), children: action.label.slice(0, altSearchBuffer.length) }), action.label.slice(altSearchBuffer.length)] })) : (action.label)] }, action.id));
                                        }) })] }), _jsxs("form", { onSubmit: (e) => {
                                    e.preventDefault();
                                    if (instruction.trim()) {
                                        executeAiModification(instruction.trim());
                                        setInstruction("");
                                    }
                                }, className: "relative group", children: [_jsx(Textarea, { ref: inputRef, value: instruction, onChange: (e) => setInstruction(e.target.value), onKeyDown: handleTextareaKeyDown, placeholder: conversationHistory.length > 0
                                            ? "How can I improve this further?"
                                            : "Describe the change you want...", className: cn("w-full py-3 pl-3 pr-14 text-xs rounded-2xl border border-gray-200", "focus:border-violet-400 focus:ring-4 focus:ring-violet-500/10", "outline-none transition-all duration-200 bg-gray-50/30 resize-none", isLarge ? "min-h-[200px]" : "min-h-[100px]") }), _jsxs("div", { className: "absolute right-2 bottom-2 flex items-center gap-1", children: [hasMultipleModels && (_jsxs(Popover, { open: modelPopoverOpen, onOpenChange: setModelPopoverOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn("p-1.5 rounded-lg transition-all duration-200", "text-gray-400 hover:text-violet-600 hover:bg-violet-50", modelPopoverOpen && "text-violet-600 bg-violet-50"), title: selectedModelSummary ? `Models: ${selectedModelSummary}` : "Select models", children: _jsx(Cpu, { className: "w-3.5 h-3.5" }) }) }), _jsxs(PopoverContent, { className: "w-56 p-1.5", align: "end", sideOffset: 8, children: [_jsxs("div", { className: "flex items-center justify-between px-2 py-1", children: [_jsx("span", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-wider", children: "Models" }), _jsxs("span", { className: "text-[10px] text-violet-600 font-medium", children: [selectedModels.length, " selected"] })] }), _jsx("div", { className: "space-y-0.5", children: availableModels.map((model) => (_jsxs("button", { type: "button", onClick: () => toggleModelSelection(model.id), className: cn("w-full text-left px-2 py-1.5 text-xs rounded-md transition-colors flex items-center gap-2", selectedModelIds.includes(model.id)
                                                                        ? "bg-violet-100 text-violet-700 font-medium"
                                                                        : "text-gray-600 hover:bg-gray-100"), children: [_jsx("span", { className: cn("w-3.5 h-3.5 rounded border flex items-center justify-center", selectedModelIds.includes(model.id)
                                                                                ? "border-violet-600 bg-violet-600 text-white"
                                                                                : "border-gray-300 bg-white text-transparent"), children: _jsx(Check, { className: "w-3 h-3" }) }), model.name] }, model.id))) })] })] })), _jsx("button", { type: "submit", disabled: !instruction.trim() || selectedModels.length === 0, className: cn("p-2.5 rounded-xl transition-all duration-300", instruction.trim() && selectedModels.length > 0
                                                    ? "bg-violet-600 text-white shadow-lg shadow-violet-200 hover:bg-violet-700 hover:-translate-y-0.5"
                                                    : "bg-gray-100 text-gray-400 cursor-not-allowed"), title: "Send (Enter)", children: _jsx(SendHorizonal, { className: "w-4 h-4" }) })] })] }), conversationHistory.length > 0 && (_jsxs("div", { className: "flex items-center justify-between pt-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-gray-400 font-medium", children: [_jsx(History, { className: "w-3 h-3" }), "Conversation active"] }), _jsxs("button", { onClick: handleStartOver, className: "text-[10px] font-bold text-violet-600 hover:text-violet-700 uppercase tracking-tight flex items-center gap-1 transition-colors", children: [_jsx(RotateCcw, { className: "w-3 h-3" }), "Clear History"] })] })), error && (_jsxs("div", { className: "text-xs text-red-600 bg-red-50 px-3 py-2 rounded-lg border border-red-100 flex items-start gap-2 animate-in fade-in slide-in-from-top-1 duration-200", children: [_jsx(X, { className: "w-3.5 h-3.5 mt-0.5 shrink-0" }), _jsx("span", { children: error })] }))] })), state === "preview" && (_jsxs("div", { className: "space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [Object.keys(modelRuns).length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] font-bold text-gray-500 uppercase tracking-wider", children: [_jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-violet-400" }), Object.values(modelRuns).some((r) => r.status === "pending" || r.status === "streaming")
                                                ? "Responses (live)"
                                                : "Model responses"] }), _jsx("div", { className: "flex gap-3 overflow-x-auto pb-1", children: Object.values(modelRuns).map((run) => (_jsxs("div", { className: cn("min-w-[280px] flex-1 rounded-xl p-3 flex flex-col gap-2 border", run.status === "done" && "border-green-100 bg-green-50/40", run.status === "error" && "border-red-100 bg-red-50/30", (run.status === "pending" || run.status === "streaming") && "border-violet-100 bg-violet-50/30"), children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("div", { className: "text-xs font-semibold text-gray-700 truncate", children: run.modelName }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [_jsx("button", { type: "button", onClick: () => handleOpenAgentInSidePanel(run.agentId), className: "p-1 rounded text-gray-400 hover:text-violet-600 hover:bg-violet-50 transition-colors", title: "Open agent in side panel", children: _jsx(Bug, { className: "w-3 h-3" }) }), (run.status === "pending" || run.status === "streaming") && (_jsx("button", { type: "button", onClick: () => handleStopModelRun(run.agentId), className: "p-1.5 rounded-lg text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors", title: "Stop this model", children: _jsx(StopCircle, { className: "w-4 h-4" }) })), _jsxs(Badge, { variant: "outline", className: cn("text-[10px]", run.status === "done" && "border-green-300 text-green-700", run.status === "error" && "border-red-300 text-red-700", (run.status === "pending" || run.status === "streaming") && "border-violet-300 text-violet-700"), children: [run.status === "pending" && "Queued", run.status === "streaming" && "Streaming…", run.status === "done" && "Ready", run.status === "error" && "Error"] })] })] }), _jsxs("div", { className: cn("text-xs whitespace-pre-wrap rounded-lg p-2 overflow-y-auto border min-h-[80px]", run.status === "done" && "bg-white/80 border-green-100", run.status === "error" && "bg-white/80 border-red-100 text-red-700", (run.status === "pending" || run.status === "streaming") && "bg-white/60 border-violet-100"), style: { maxHeight: isLarge ? "420px" : "200px" }, children: [run.status === "pending" && (_jsx("span", { className: "text-gray-400 italic", children: "Starting\u2026" })), run.status === "streaming" && (() => {
                                                            const preview = extractStreamingPreview(run.rawContent ?? "");
                                                            return preview ? (renderTextWithContext(preview, false)) : (_jsx("span", { className: "text-gray-400 italic", children: "Waiting for response\u2026" }));
                                                        })(), run.status === "done" && run.replacementText != null && (renderTextWithContext(run.replacementText, true)), run.status === "error" && (_jsx("div", { className: "text-red-700", children: run.error || "Something went wrong." }))] }), run.status === "done" && run.replacementText != null && (_jsxs(Button, { size: "sm", onClick: () => handleUseRunResult(run.replacementText), className: "mt-2 bg-violet-600 hover:bg-violet-700 text-white rounded-lg font-semibold w-full", children: [_jsx(Check, { className: "w-4 h-4 mr-1.5" }), "Use this"] }))] }, run.agentId))) })] })), _jsxs("form", { onSubmit: (e) => {
                                    e.preventDefault();
                                    if (instruction.trim()) {
                                        executeAiModification(instruction.trim());
                                        setInstruction("");
                                    }
                                }, className: "relative group", children: [_jsx(Textarea, { ref: inputRef, value: instruction, onChange: (e) => setInstruction(e.target.value), onKeyDown: handleTextareaKeyDown, placeholder: "Not quite right? Ask for a change...", className: cn("w-full min-h-[80px] py-3 pl-3 pr-14 text-xs rounded-2xl border border-gray-200", "focus:border-violet-400 focus:ring-4 focus:ring-violet-500/10", "outline-none transition-all duration-200 bg-gray-50/30 resize-none") }), _jsxs("div", { className: "absolute right-2 bottom-2 flex items-center gap-1", children: [hasMultipleModels && (_jsxs(Popover, { open: modelPopoverOpen, onOpenChange: setModelPopoverOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn("p-1.5 rounded-lg transition-all duration-200", "text-gray-400 hover:text-violet-600 hover:bg-violet-50", modelPopoverOpen && "text-violet-600 bg-violet-50"), title: selectedModelSummary ? `Models: ${selectedModelSummary}` : "Select models", children: _jsx(Cpu, { className: "w-3.5 h-3.5" }) }) }), _jsxs(PopoverContent, { className: "w-56 p-1.5", align: "end", sideOffset: 8, children: [_jsxs("div", { className: "flex items-center justify-between px-2 py-1", children: [_jsx("span", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-wider", children: "Models" }), _jsxs("span", { className: "text-[10px] text-violet-600 font-medium", children: [selectedModels.length, " selected"] })] }), _jsx("div", { className: "space-y-0.5", children: availableModels.map((model) => (_jsxs("button", { type: "button", onClick: () => toggleModelSelection(model.id), className: cn("w-full text-left px-2 py-1.5 text-xs rounded-md transition-colors flex items-center gap-2", selectedModelIds.includes(model.id)
                                                                        ? "bg-violet-100 text-violet-700 font-medium"
                                                                        : "text-gray-600 hover:bg-gray-100"), children: [_jsx("span", { className: cn("w-3.5 h-3.5 rounded border flex items-center justify-center", selectedModelIds.includes(model.id)
                                                                                ? "border-violet-600 bg-violet-600 text-white"
                                                                                : "border-gray-300 bg-white text-transparent"), children: _jsx(Check, { className: "w-3 h-3" }) }), model.name] }, model.id))) })] })] })), _jsx("button", { type: "submit", disabled: !instruction.trim() || selectedModels.length === 0, className: cn("p-2.5 rounded-xl transition-all duration-300", instruction.trim() && selectedModels.length > 0
                                                    ? "bg-violet-600 text-white shadow-lg shadow-violet-200 hover:bg-violet-700 hover:-translate-y-0.5"
                                                    : "bg-gray-100 text-gray-400 cursor-not-allowed"), title: "Refine (Enter)", children: _jsx(Wand2, { className: "w-4 h-4" }) })] })] }), _jsxs("div", { className: "flex gap-2.5 pt-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: handleStartOver, className: "flex-1 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-xl font-medium", children: [_jsx(RotateCcw, { className: "w-4 h-4 mr-2" }), "Discard"] }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: handleRetry, disabled: conversationHistory.length === 0, className: "flex-1 text-violet-600 hover:text-violet-700 hover:bg-violet-50 rounded-xl font-medium disabled:opacity-50 disabled:pointer-events-none", title: "Run the same request again", children: [_jsx(RotateCw, { className: "w-4 h-4 mr-2" }), "Retry"] })] })] }))] })] }));
}
export default InlineAiDialog;
//# sourceMappingURL=InlineAiDialog.js.map