/**
 * WP Agentic Writer - Gutenberg Sidebar
 *
 * @package WP_Agentic_Writer
 */

(function (wp) {
  const { registerPlugin } = wp.plugins;
  const { PluginSidebarMoreMenuItem } = wp.editPost;
  const { PluginSidebar } = wp.editPost;
  const { Panel, TextareaControl, TextControl, CheckboxControl, Button } =
    wp.components;
  const { dispatch, select } = wp.data;
  const { RawHTML } = wp.element;

  // Debug logger - only logs when SCRIPT_DEBUG is enabled
  const isDebug =
    typeof wpAgenticWriter !== "undefined" && wpAgenticWriter.debug;
  const wpawLog = {
    log: (...args) => {
      if (isDebug) console.log("[WPAW]", ...args);
    },
    error: (...args) => console.error("[WPAW]", ...args), // Always log errors
    info: (...args) => {
      if (isDebug) console.info("[WPAW]", ...args);
    },
    warn: (...args) => {
      if (isDebug) console.warn("[WPAW]", ...args);
    },
  };
  const pluginIcon = <img
  src={wpAgenticWriter.pluginUrl + "/assets/img/icon.svg"}
  alt={"WP Agentic Writer"}
  style={{ width: "20px", height: "20px" }}
 />;

  // Sidebar Component.
  const AgenticWriterSidebar = ({ postId }) => {
    // Get settings from wpAgenticWriter global.
    const settings =
      typeof wpAgenticWriter !== "undefined" ? wpAgenticWriter.settings : {};
    const formatAiErrorMessage = (
      error,
      fallback = "The AI request failed.",
    ) => {
      const rawMessage =
        typeof error === "string" ? error : error?.message || fallback;
      const cleanMessage = String(rawMessage || fallback)
        .replace(/^API error:\s*/i, "")
        .trim();
      const lowerMessage = cleanMessage.toLowerCase();

      // Returns structured object { title, detail, actionUrl, actionLabel }
      const structured = (title, detail, actionUrl, actionLabel) => {
        return {
          title,
          detail,
          actionUrl: actionUrl || "",
          actionLabel: actionLabel || "",
        };
      };

      if (
        lowerMessage.includes("no allowed providers are available") ||
        (lowerMessage.includes("allowed providers") &&
          lowerMessage.includes("selected model"))
      ) {
        const routedProvider =
          settings?.openrouter_provider_slug &&
          settings.openrouter_provider_slug !== "auto"
            ? ` Pinned: ${settings.openrouter_provider_slug}.`
            : "";
        return structured(
          "Model unavailable from current provider",
          `The pinned provider routing doesn't support this model.${routedProvider} Change provider routing or select a compatible model.`,
          settings?.settings_url || "",
          "Open Settings",
        );
      }

      if (cleanMessage.includes("429") || lowerMessage.includes("rate limit")) {
        return structured(
          "Rate limit exceeded",
          "The AI provider is throttling requests. Wait a moment and try again.",
        );
      }

      if (
        cleanMessage.includes("cURL error 28") ||
        lowerMessage.includes("operation timed out") ||
        lowerMessage.includes("timed out after")
      ) {
        return structured(
          "Request timed out",
          "The model took too long to respond. Try a faster model, reduce content length, or check your provider routing.",
          settings?.settings_url || "",
          "Open Settings",
        );
      }

      if (
        cleanMessage.startsWith("HTTP 401") ||
        lowerMessage.includes("unauthorized")
      ) {
        return structured(
          "API key rejected",
          "The provider rejected your API key. Check your key in settings.",
          settings?.settings_url || "",
          "Open Settings",
        );
      }

      if (
        cleanMessage.startsWith("HTTP 402") ||
        lowerMessage.includes("insufficient credits")
      ) {
        return structured(
          "Insufficient credits",
          "Your provider account has no remaining credits or quota.",
        );
      }

      if (
        lowerMessage.includes("api key is not configured") ||
        lowerMessage.includes("no_api_key")
      ) {
        return structured(
          "API key not configured",
          "Add your OpenRouter API key in plugin settings to start using AI features.",
          settings?.settings_url || "",
          "Configure API Key",
        );
      }

      return structured(cleanMessage || fallback, "");
    };

    // Tab state (top tabs instead of bottom)
    const [activeTab, setActiveTab] = React.useState("chat");

    // Chat state
    const [messages, setMessages] = React.useState([]);
    const [input, setInput] = React.useState("");
    const [isLoading, setIsLoading] = React.useState(false);
    const [currentSessionId, setCurrentSessionId] = React.useState("");
    const [availableSessions, setAvailableSessions] = React.useState([]);
    const [isSessionActionLoading, setIsSessionActionLoading] =
      React.useState(false);
    const [agentMode, setAgentMode] = React.useState("chat");

    // Session lock state (multi-tab safety)
    const tabIdRef = React.useRef(
      Math.random().toString(36).substring(2, 10) + Date.now().toString(36),
    );
    const [sessionLock, setSessionLock] = React.useState({
      locked: false, // true = we hold the lock (or no lock needed)
      lockedByOther: false, // true = another tab holds the lock
      holderTabId: "",
    });
    const lockHeartbeatRef = React.useRef(null);

    // Config state
    const defaultPostConfig = React.useMemo(
      () => ({
        article_length: "medium",
        language: "auto",
        tone: "",
        audience: "",
        experience_level: "general",
        include_images: true,
        web_search: Boolean(settings.web_search_enabled),
        default_mode: "chat",
        // SEO fields
        seo_focus_keyword: "",
        focus_keyword: "",
        seo_secondary_keywords: "",
        seo_meta_description: "",
        seo_enabled: true,
      }),
      [settings.web_search_enabled],
    );
    const [postConfig, setPostConfig] = React.useState(defaultPostConfig);
    const [isConfigLoading, setIsConfigLoading] = React.useState(false);
    const [isConfigSaving, setIsConfigSaving] = React.useState(false);
    const [configError, setConfigError] = React.useState("");
    const configHydratedRef = React.useRef(false);
    const lastSavedConfigRef = React.useRef("");
    const configSaveTimeoutRef = React.useRef(null);

    // Cost state
    const [cost, setCost] = React.useState({
      session: 0,
      today: 0,
      monthlyUsed: 0,
    });
    const [monthlyBudget, setMonthlyBudget] = React.useState(
      settings.monthly_budget || 600,
    );

    // Provider info state for transparency display
    const [providerInfo, setProviderInfo] = React.useState(null);

    // Helper to extract and apply provider metadata from any AI response
    const applyProviderMetadata = (data) => {
      if (!data) return;
      if (data.session_id) {
        setCurrentSessionId(data.session_id);
      }

      // Support both nested provider_metadata and top-level provider fields
      const meta = data.provider_metadata || data;
      const provider = meta.provider || meta.selected_provider || meta.provider;

      if (provider) {
        setProviderInfo({
          provider: provider,
          model: meta.model,
          fallbackUsed: meta.fallback_used || meta.fallbackUsed,
          warnings: meta.warnings || [],
        });
      }
    };

    const [isEditorLocked, setIsEditorLocked] = React.useState(false);
    const [isRefinementLocked, setIsRefinementLocked] = React.useState(false);
    const [refiningBlockIds, setRefiningBlockIds] = React.useState([]);
    const refinementDecoratedIdsRef = React.useRef([]);
    const lockedEditableNodesRef = React.useRef([]);
    const lockedBlockIdsRef = React.useRef([]);
    const REFINEMENT_ALL_CONFIRM_THRESHOLD = 25;
    const [refineAllConfirm, setRefineAllConfirm] = React.useState({
      isOpen: false,
      blockCount: 0,
      dontAskAgain: false,
    });
    const refineAllConfirmResolverRef = React.useRef(null);
    const skipRefineAllConfirmRef = React.useRef(false);

    // SEO audit state
    const [seoAudit, setSeoAudit] = React.useState(null);
    const [isSeoAuditing, setIsSeoAuditing] = React.useState(false);
    const [isGeneratingMeta, setIsGeneratingMeta] = React.useState(false);
    const [activeSeoFixKey, setActiveSeoFixKey] = React.useState("");

    // Clarification state.
    const [inClarification, setInClarification] = React.useState(false);
    const [questions, setQuestions] = React.useState([]);
    const [currentQuestionIndex, setCurrentQuestionIndex] = React.useState(0);
    const [answers, setAnswers] = React.useState([]);
    const [detectedLanguage, setDetectedLanguage] = React.useState("auto");
    const [clarificationMode, setClarificationMode] =
      React.useState("generation");
    const [pendingRefinement, setPendingRefinement] = React.useState(null);
    const [pendingEditPlan, setPendingEditPlan] = React.useState(null);
    const [pendingDiffBlockIds, setPendingDiffBlockIds] = React.useState([]);
    const lastGenerationRequestRef = React.useRef(null);
    const currentPlanRef = React.useRef(null);
    const lastExecuteRequestRef = React.useRef(null);
    const sectionInsertIndexRef = React.useRef({});
    const activeSectionIdRef = React.useRef(null);
    const sectionBlocksRef = React.useRef({});
    const blockSectionRef = React.useRef({});
    const markdownRendererRef = React.useRef(null);
    const lastRefineRequestRef = React.useRef(null);
    const lastChatRequestRef = React.useRef(null);
    const stopExecutionRef = React.useRef(false);
    const activeAbortControllerRef = React.useRef(null);
    const activeReaderRef = React.useRef(null);
    const activeOperationRef = React.useRef({
      type: "idle",
      status: "idle",
      label: "",
    });
    const [executionStopped, setExecutionStopped] = React.useState(false);
    const [activeOperation, setActiveOperation] = React.useState({
      type: "idle",
      status: "idle",
      label: "",
    });
    const [writingState, setWritingState] = React.useState({
      status: "idle",
      current_section_index: 0,
      sections_written: [],
      last_updated: null,
      plan_id: "",
      resume_token: "",
    });
    const [isWritingStateLoading, setIsWritingStateLoading] =
      React.useState(false);
    const [workspaceSnapshot, setWorkspaceSnapshot] = React.useState({
      title: "",
      blockCount: 0,
      selectedBlockLabel: "None selected",
      selectedBlockPreview: "",
    });
    const [isWorkspaceCollapsed, setIsWorkspaceCollapsed] = React.useState(
      () => {
        try {
          return (
            window.localStorage.getItem("wpaw_agent_workspace_collapsed") ===
            "1"
          );
        } catch (error) {
          return false;
        }
      },
    );
    const toggleAgentWorkspace = () => {
      setIsWorkspaceCollapsed((prev) => {
        const next = !prev;
        try {
          window.localStorage.setItem(
            "wpaw_agent_workspace_collapsed",
            next ? "1" : "0",
          );
        } catch (error) {
          // Ignore storage failures; the in-session toggle still works.
        }
        return next;
      });
    };

    // Mention autocomplete state
    const [showMentionAutocomplete, setShowMentionAutocomplete] =
      React.useState(false);
    const [mentionQuery, setMentionQuery] = React.useState("");
    const [mentionOptions, setMentionOptions] = React.useState([]);
    const [mentionCursorIndex, setMentionCursorIndex] = React.useState(0);
    const [showSlashAutocomplete, setShowSlashAutocomplete] =
      React.useState(false);
    const [slashQuery, setSlashQuery] = React.useState("");
    const [slashOptions, setSlashOptions] = React.useState([]);
    const [slashCursorIndex, setSlashCursorIndex] = React.useState(0);
    const [isTextareaExpanded, setIsTextareaExpanded] = React.useState(false);
    const inputRef = React.useRef(null);
    const streamTargetRef = React.useRef(null);

    // Focus keyword state
    const [focusKeywordSuggestions, setFocusKeywordSuggestions] =
      React.useState([]);
    const [selectedFocusKeyword, setSelectedFocusKeyword] = React.useState("");
    const [showCustomKeywordInput, setShowCustomKeywordInput] =
      React.useState(false);
    const [customKeywordInput, setCustomKeywordInput] = React.useState("");
    const messagesSaveTimeoutRef = React.useRef(null);
    const lastPersistedMessagesRef = React.useRef("");
    const isHydratingSessionRef = React.useRef(false);

    // Welcome screen state
    const [showWelcome, setShowWelcome] = React.useState(true);
    const [welcomeKeywordInput, setWelcomeKeywordInput] = React.useState("");
    const [welcomeStartMode, setWelcomeStartMode] = React.useState("chat"); // 'chat' or 'planning'

    // Undo stack for AI operations
    const [aiUndoStack, setAiUndoStack] = React.useState([]);
    const MAX_UNDO_STACK = 10;

    // MEMANTO memory restore state
    const [memantoRestore, setMemantoRestore] = React.useState({
      restored: false,
      summary: "",
      memories: [],
      preferences: [],
      systemMessage: "",
    });
    const memantoRestoreFetchedRef = React.useRef(false);
    React.useEffect(() => {
      if (agentMode === "writing" && !isLoading) {
        setAgentMode("chat");
      }
    }, [agentMode, isLoading]);

    React.useEffect(() => {
      if (!postId) {
        return;
      }

      setIsConfigLoading(true);
      fetch(`${wpAgenticWriter.apiUrl}/post-config/${postId}`, {
        headers: {
          "X-WP-Nonce": wpAgenticWriter.nonce,
        },
      })
        .then((response) =>
          response.ok ? response.json() : Promise.reject(response),
        )
        .then((data) => {
          const merged = { ...defaultPostConfig, ...data };
          merged.default_mode = "chat";
          setPostConfig(merged);
          lastSavedConfigRef.current = JSON.stringify(merged);
          configHydratedRef.current = true;
        })
        .catch(() => {
          configHydratedRef.current = true;
        })
        .finally(() => {
          setIsConfigLoading(false);
        });
    }, [postId, defaultPostConfig]);

    const savePostConfig = React.useCallback(
      async (config) => {
        if (!postId) {
          return;
        }

        setIsConfigSaving(true);
        setConfigError("");
        try {
          const response = await fetch(
            `${wpAgenticWriter.apiUrl}/post-config/${postId}`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify({ postConfig: config }),
            },
          );

          if (!response.ok) {
            const error = await response.json();
            throw new Error(
              error.message || "Failed to save post configuration",
            );
          }

          const data = await response.json();
          lastSavedConfigRef.current = JSON.stringify(data);
          // Don't update state if data matches current - prevents focus loss
          setPostConfig((prev) => {
            const newConfig = { ...prev, ...data };
            if (JSON.stringify(prev) === JSON.stringify(newConfig)) {
              return prev; // Return same reference to prevent re-render
            }
            return newConfig;
          });
        } catch (error) {
          setConfigError(error.message || "Failed to save post configuration");
        } finally {
          setIsConfigSaving(false);
        }
      },
      [postId],
    );

    React.useEffect(() => {
      if (!configHydratedRef.current || isConfigLoading) {
        return;
      }

      const serialized = JSON.stringify(postConfig);
      if (serialized === lastSavedConfigRef.current) {
        return;
      }

      if (configSaveTimeoutRef.current) {
        clearTimeout(configSaveTimeoutRef.current);
      }

      configSaveTimeoutRef.current = setTimeout(() => {
        savePostConfig(postConfig);
      }, 600);

      return () => {
        if (configSaveTimeoutRef.current) {
          clearTimeout(configSaveTimeoutRef.current);
        }
      };
    }, [postConfig, isConfigLoading, savePostConfig]);

    React.useEffect(() => {
      if (!settings.cost_tracking_enabled || !postId) {
        return;
      }

      fetch(`${wpAgenticWriter.apiUrl}/cost-tracking/${postId}`, {
        headers: {
          "X-WP-Nonce": wpAgenticWriter.nonce,
        },
      })
        .then((response) => response.json())
        .then((data) => {
          if (data && typeof data.session === "number") {
            setCost({
              session: data.session,
              today: data.today?.total?.cost || 0,
              monthlyUsed: data.monthly?.used || 0,
            });
          }
          if (data?.monthly?.budget) {
            setMonthlyBudget(data.monthly.budget);
          }
        })
        .catch(() => {});
    }, [postId]);

    const normalizeWritingState = (state = {}) => ({
      status: state.status || "idle",
      current_section_index: Number(state.current_section_index || 0),
      sections_written: Array.isArray(state.sections_written)
        ? state.sections_written
        : [],
      last_updated: state.last_updated || null,
      plan_id: state.plan_id || "",
      resume_token: state.resume_token || "",
    });

    const saveWritingState = React.useCallback(
      async (statePatch) => {
        if (!postId) {
          return;
        }

        const nextState = normalizeWritingState(statePatch);
        try {
          const response = await fetch(
            `${wpAgenticWriter.apiUrl}/writing-state/${postId}`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify(nextState),
            },
          );
          if (!response.ok) {
            throw new Error("Failed to save writing state");
          }
        } catch (error) {
          wpawLog.warn("Writing state save failed:", error);
        }
      },
      [postId],
    );

    const persistWritingStatePatch = React.useCallback(
      (patch) => {
        setWritingState((prev) => {
          const next = normalizeWritingState({ ...prev, ...patch });
          saveWritingState(next);
          return next;
        });
      },
      [saveWritingState],
    );

    React.useEffect(() => {
      if (!postId) {
        return;
      }

      let cancelled = false;
      setIsWritingStateLoading(true);
      fetch(`${wpAgenticWriter.apiUrl}/writing-state/${postId}`, {
        headers: {
          "X-WP-Nonce": wpAgenticWriter.nonce,
        },
      })
        .then((response) =>
          response.ok ? response.json() : Promise.reject(response),
        )
        .then((data) => {
          if (!cancelled) {
            setWritingState(normalizeWritingState(data));
          }
        })
        .catch((error) => {
          wpawLog.warn("Writing state load failed:", error);
        })
        .finally(() => {
          if (!cancelled) {
            setIsWritingStateLoading(false);
          }
        });

      return () => {
        cancelled = true;
      };
    }, [postId]);

    // Chat messages container ref for auto-scroll
    const messagesEndRef = React.useRef(null);
    const messagesContainerRef = React.useRef(null);

    // Auto-scroll to bottom when messages change
    React.useEffect(() => {
      if (messagesEndRef.current) {
        messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
      }
    }, [messages]);

    const progressRegex =
      /^(I'll|Writing|Now|Creating|Adding|Let me|I'll write|Saya|Saya akan|Sedang menulis|Sedang membuat|Menulis tentang|Membuat tentang|Thinking|Analyzing|Reviewing|Refining|Checking|Updating|Planning|Searching|Querying|Generated|Drafting|Reading|Context|Processing)/i;
    const activeTimelineStatuses = new Set([
      "active",
      "starting",
      "refining",
      "checking",
      "waiting",
      "planning",
      "plan_complete",
      "writing",
      "writing_section",
    ]);
    const writingTimelineStatuses = new Set(["writing", "writing_section"]);
    const findLastActiveTimelineIndex = (items) => {
      for (let i = items.length - 1; i >= 0; i--) {
        if (
          items[i].type === "timeline" &&
          activeTimelineStatuses.has(items[i].status)
        ) {
          return i;
        }
      }

      return -1;
    };
    const deactivateActiveTimelineEntries = (items) => {
      return items.map((item) => {
        if (
          item.type === "timeline" &&
          activeTimelineStatuses.has(item.status)
        ) {
          return {
            ...item,
            status: "inactive",
          };
        }

        return item;
      });
    };
    const updateOrCreateTimelineEntry = (message) => {
      setMessages((prev) => {
        const newMessages = [...prev];
        const timelineIndex = findLastActiveTimelineIndex(newMessages);

        if (timelineIndex === -1) {
          newMessages.push({
            role: "system",
            type: "timeline",
            status: "active",
            message: message,
            timestamp: new Date(),
          });
        } else {
          newMessages[timelineIndex] = {
            ...newMessages[timelineIndex],
            message: message,
          };
        }

        return newMessages;
      });
    };
    const addActivityTimeline = (status, message, options = {}) => {
      const { deactivate = true, extra = {} } = options;
      setMessages((prev) => [
        ...(deactivate ? deactivateActiveTimelineEntries(prev) : prev),
        {
          role: "system",
          type: "timeline",
          status,
          message,
          timestamp: new Date(),
          ...extra,
        },
      ]);
    };
    const setActiveOperationState = (nextOperation) => {
      const normalized = {
        type: nextOperation?.type || "idle",
        status: nextOperation?.status || "idle",
        label: nextOperation?.label || "",
      };
      activeOperationRef.current = normalized;
      setActiveOperation(normalized);
    };
    const beginAgentOperation = (type, label) => {
      stopExecutionRef.current = false;
      const controller = new AbortController();
      activeAbortControllerRef.current = controller;
      activeReaderRef.current = null;
      setExecutionStopped(false);
      setActiveOperationState({ type, status: "running", label });
      return controller;
    };
    const finishAgentOperation = (type = "") => {
      const current = activeOperationRef.current || {};
      if (type && current.type && current.type !== type) {
        return;
      }
      activeAbortControllerRef.current = null;
      activeReaderRef.current = null;
      setActiveOperationState({ type: "idle", status: "idle", label: "" });
    };
    const markActiveOperationStopping = () => {
      const current = activeOperationRef.current || {};
      setActiveOperationState({
        type: current.type || "unknown",
        status: "stopping",
        label: current.label || "operation",
      });
      setMessages((prev) => {
        const newMessages = [...prev];
        const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
        if (lastTimelineIndex !== -1) {
          newMessages[lastTimelineIndex] = {
            ...newMessages[lastTimelineIndex],
            status: "stopping",
            message: `Stopping ${current.label || "operation"}...`,
            timestamp: new Date(),
          };
        }
        return newMessages;
      });
    };
    const isAbortError = (error) =>
      error?.name === "AbortError" ||
      /aborted|abort/i.test(String(error?.message || ""));
    const registerActiveReader = (reader) => {
      activeReaderRef.current = reader;
      return reader;
    };
    const requestRefineAllConfirmation = React.useCallback((blockCount) => {
      if (skipRefineAllConfirmRef.current) {
        return Promise.resolve(true);
      }

      return new Promise((resolve) => {
        refineAllConfirmResolverRef.current = resolve;
        setRefineAllConfirm({
          isOpen: true,
          blockCount: Number(blockCount) || 0,
          dontAskAgain: false,
        });
      });
    }, []);
    const resolveRefineAllConfirmation = React.useCallback((approved) => {
      const resolver = refineAllConfirmResolverRef.current;
      refineAllConfirmResolverRef.current = null;
      setRefineAllConfirm((prev) => ({ ...prev, isOpen: false }));
      if (resolver) {
        resolver(Boolean(approved));
      }
    }, []);

    // Undo helper functions
    const captureEditorSnapshot = (label = "AI Operation") => {
      const allBlocks = select("core/block-editor").getBlocks();
      const serializedBlocks = allBlocks
        .map((block) => wp.blocks.serialize(block))
        .join("\n");
      return {
        label,
        timestamp: new Date(),
        blocks: serializedBlocks,
      };
    };

    const pushUndoSnapshot = (label = "AI Operation") => {
      const snapshot = captureEditorSnapshot(label);
      setAiUndoStack((prev) => {
        const newStack = [...prev, snapshot];
        if (newStack.length > MAX_UNDO_STACK) {
          return newStack.slice(-MAX_UNDO_STACK);
        }
        return newStack;
      });
    };

    const undoLastAiOperation = () => {
      if (aiUndoStack.length === 0) {
        return;
      }

      const lastSnapshot = aiUndoStack[aiUndoStack.length - 1];
      const { resetBlocks } = dispatch("core/block-editor");

      try {
        const parsedBlocks = wp.blocks.parse(lastSnapshot.blocks);
        resetBlocks(parsedBlocks);

        setAiUndoStack((prev) => prev.slice(0, -1));

        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "timeline",
            status: "complete",
            message: `Undid: ${lastSnapshot.label}`,
            timestamp: new Date(),
          },
        ]);
      } catch (error) {
        wpawLog.error("Failed to undo AI operation:", error);
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Failed to undo operation: " + error.message,
          },
        ]);
      }
    };

    React.useEffect(() => {
      const lastTimelineIndex = findLastActiveTimelineIndex(messages);
      const lastTimeline =
        lastTimelineIndex !== -1 ? messages[lastTimelineIndex] : null;
      const isWritingActive = Boolean(
        isLoading &&
        lastTimeline &&
        writingTimelineStatuses.has(lastTimeline.status),
      );

      if (isWritingActive && !isEditorLocked) {
        dispatch("core/editor").lockPostSaving("wpaw-writing");
        document.body.classList.add("wpaw-editor-locked");
        setIsEditorLocked(true);
      } else if (!isWritingActive && isEditorLocked) {
        dispatch("core/editor").unlockPostSaving("wpaw-writing");
        document.body.classList.remove("wpaw-editor-locked");
        setIsEditorLocked(false);
      }
    }, [messages, isLoading, isEditorLocked]);
    React.useEffect(() => {
      if (isRefinementLocked) {
        dispatch("core/editor").lockPostSaving("wpaw-refining");
        document.body.classList.add("wpaw-refining-locked");
      } else {
        dispatch("core/editor").unlockPostSaving("wpaw-refining");
        document.body.classList.remove("wpaw-refining-locked");
      }
    }, [isRefinementLocked]);
    React.useEffect(() => {
      const blockEditorDispatch = dispatch("core/block-editor");
      if (
        !blockEditorDispatch ||
        typeof blockEditorDispatch.setBlockEditingMode !== "function"
      ) {
        return undefined;
      }

      if (isRefinementLocked) {
        const allBlocks = select("core/block-editor").getBlocks();
        const ids = [];
        const collectIds = (blocks) => {
          blocks.forEach((block) => {
            if (!block?.clientId) {
              return;
            }
            ids.push(block.clientId);
            if (
              Array.isArray(block.innerBlocks) &&
              block.innerBlocks.length > 0
            ) {
              collectIds(block.innerBlocks);
            }
          });
        };
        collectIds(allBlocks);
        lockedBlockIdsRef.current = ids;
        ids.forEach((id) =>
          blockEditorDispatch.setBlockEditingMode(id, "disabled"),
        );
      } else if (lockedBlockIdsRef.current.length > 0) {
        lockedBlockIdsRef.current.forEach((id) =>
          blockEditorDispatch.setBlockEditingMode(id, "default"),
        );
        lockedBlockIdsRef.current = [];
      }

      return () => {
        if (lockedBlockIdsRef.current.length > 0) {
          lockedBlockIdsRef.current.forEach((id) =>
            blockEditorDispatch.setBlockEditingMode(id, "default"),
          );
          lockedBlockIdsRef.current = [];
        }
      };
    }, [isRefinementLocked, messages]);

    React.useEffect(() => {
      const prevIds = refinementDecoratedIdsRef.current || [];
      prevIds.forEach((id) => {
        const node = document.querySelector(`[data-block="${id}"]`);
        if (node) {
          node.classList.remove("wpaw-block-refining");
        }
      });

      if (isRefinementLocked && Array.isArray(refiningBlockIds)) {
        refiningBlockIds.forEach((id) => {
          const node = document.querySelector(`[data-block="${id}"]`);
          if (node) {
            node.classList.add("wpaw-block-refining");
          }
        });
        refinementDecoratedIdsRef.current = [...refiningBlockIds];
      } else {
        refinementDecoratedIdsRef.current = [];
      }

      return () => {
        const cleanupIds = refinementDecoratedIdsRef.current || [];
        cleanupIds.forEach((id) => {
          const node = document.querySelector(`[data-block="${id}"]`);
          if (node) {
            node.classList.remove("wpaw-block-refining");
          }
        });
      };
    }, [isRefinementLocked, refiningBlockIds, messages]);

    React.useEffect(() => {
      if (!isRefinementLocked) {
        return undefined;
      }

      const shouldBlockEditorInput = (eventTarget) => {
        if (!eventTarget || !(eventTarget instanceof Element)) {
          return false;
        }
        if (
          eventTarget.closest(
            ".wpaw-sidebar, .wpaw-command-area, .wpaw-messages",
          )
        ) {
          return false;
        }
        return Boolean(
          eventTarget.closest(
            ".interface-interface-skeleton__content, .editor-styles-wrapper, .block-editor-writing-flow",
          ),
        );
      };

      const keydownHandler = (event) => {
        if (!shouldBlockEditorInput(event.target)) {
          return;
        }
        if (event.metaKey || event.ctrlKey || event.altKey) {
          return;
        }
        const blockedKeys = new Set(["Enter", "Backspace", "Delete", "Tab"]);
        if (
          (typeof event.key === "string" && event.key.length === 1) ||
          blockedKeys.has(event.key)
        ) {
          event.preventDefault();
          event.stopPropagation();
        }
      };

      const blockMutationEvent = (event) => {
        if (!shouldBlockEditorInput(event.target)) {
          return;
        }
        event.preventDefault();
        event.stopPropagation();
      };

      document.addEventListener("keydown", keydownHandler, true);
      document.addEventListener("paste", blockMutationEvent, true);
      document.addEventListener("drop", blockMutationEvent, true);
      document.addEventListener("cut", blockMutationEvent, true);

      return () => {
        document.removeEventListener("keydown", keydownHandler, true);
        document.removeEventListener("paste", blockMutationEvent, true);
        document.removeEventListener("drop", blockMutationEvent, true);
        document.removeEventListener("cut", blockMutationEvent, true);
      };
    }, [isRefinementLocked]);
    React.useEffect(() => {
      if (isRefinementLocked) {
        const editableNodes = Array.from(
          document.querySelectorAll(
            '.editor-styles-wrapper [contenteditable="true"]',
          ),
        );
        lockedEditableNodesRef.current = editableNodes.map((node) => ({
          node,
          prev: node.getAttribute("contenteditable"),
        }));
        lockedEditableNodesRef.current.forEach(({ node }) => {
          node.setAttribute("contenteditable", "false");
        });
      } else {
        (lockedEditableNodesRef.current || []).forEach(({ node, prev }) => {
          if (!node) return;
          if (prev === null) {
            node.removeAttribute("contenteditable");
          } else {
            node.setAttribute("contenteditable", prev);
          }
        });
        lockedEditableNodesRef.current = [];
      }

      return () => {
        (lockedEditableNodesRef.current || []).forEach(({ node, prev }) => {
          if (!node) return;
          if (prev === null) {
            node.removeAttribute("contenteditable");
          } else {
            node.setAttribute("contenteditable", prev);
          }
        });
        lockedEditableNodesRef.current = [];
      };
    }, [isRefinementLocked, messages]);
    const toTextValue = (value) => {
      if (value === null || value === undefined) {
        return "";
      }
      if (typeof value === "string" || typeof value === "number") {
        return String(value);
      }
      return "";
    };
    const updatePostConfig = (key, value) => {
      setPostConfig((prev) => ({ ...prev, [key]: value }));
    };
    const buildPostConfigFromAnswers = (answerMap = {}) => {
      const merged = { ...postConfig };
      if (answerMap.config_language) {
        let languageValue = answerMap.config_language;
        if (
          languageValue === "__custom__" &&
          answerMap.config_language_custom
        ) {
          languageValue = answerMap.config_language_custom.toLowerCase().trim();
        }
        if (languageValue && languageValue !== "__skipped__") {
          merged.language = languageValue;
        }
      }

      if (answerMap.config_all) {
        try {
          const configData = JSON.parse(answerMap.config_all);
          if (configData.web_search !== undefined) {
            merged.web_search = configData.web_search;
          }
          if (configData.seo !== undefined) {
            merged.seo_enabled = configData.seo;
          }
          if (configData.focus_keyword) {
            merged.focus_keyword = configData.focus_keyword;
            merged.seo_focus_keyword = configData.focus_keyword;
          }
          if (configData.secondary_keywords) {
            merged.seo_secondary_keywords = configData.secondary_keywords;
          }
        } catch (error) {
          wpawLog.error("Failed to merge config answers:", error);
        }
      }

      return merged;
    };

    // Focus keyword handlers
    const handleFocusKeywordChange = (keyword) => {
      setSelectedFocusKeyword(keyword);
      updatePostConfig("focus_keyword", keyword);
      updatePostConfig("seo_focus_keyword", keyword);
      setShowCustomKeywordInput(false);
      setCustomKeywordInput("");
    };

    const handleKeywordSelect = (e) => {
      const value = e.target.value;
      if (value === "__custom__") {
        setShowCustomKeywordInput(true);
      } else {
        handleFocusKeywordChange(value);
      }
    };

    // Extract ALL focus keyword suggestions from AI response (returns array)
    const extractFocusKeywordSuggestions = (aiResponse) => {
      if (!aiResponse || typeof aiResponse !== "string") return [];

      const suggestions = [];

      // Method 1: Bullet list after "Fokus Keyword Suggestion:" or "Focus Keyword Suggestion:"
      // Matches: - "Keyword Here" or * "Keyword Here" or - Keyword Here
      const bulletListMatch = aiResponse.match(
        /(?:fokus|focus)\s+keyword\s+suggestion[s]?\s*:\s*([\s\S]*?)(?=\n\n|Pilih|$)/i,
      );
      if (bulletListMatch) {
        const listContent = bulletListMatch[1];
        // Extract items from bullet list (- or *)
        const bulletItems = listContent.match(/[-*]\s*["']?([^"'\n]+)["']?/g);
        if (bulletItems) {
          bulletItems.forEach((item) => {
            const cleaned = item
              .replace(/^[-*]\s*["']?/, "")
              .replace(/["']?$/, "")
              .trim();
            if (cleaned.length > 2 && cleaned.length < 60) {
              suggestions.push(cleaned);
            }
          });
        }
      }

      // Method 2: Single line "Focus Keyword Suggestion: keyword"
      if (suggestions.length === 0) {
        const singleMatch = aiResponse.match(
          /(?:fokus|focus)\s+keyword\s+suggestion[s]?\s*:\s*["']?([^"'\n]+)["']?/i,
        );
        if (
          singleMatch &&
          !singleMatch[1].includes("-") &&
          !singleMatch[1].includes("*")
        ) {
          const kw = singleMatch[1].trim();
          if (kw.length > 2 && kw.length < 60) {
            suggestions.push(kw);
          }
        }
      }

      return suggestions;
    };

    // Legacy single extraction (for backward compatibility)
    const extractFocusKeywordSuggestion = (aiResponse) => {
      const suggestions = extractFocusKeywordSuggestions(aiResponse);
      return suggestions.length > 0 ? suggestions[0] : null;
    };

    const addFocusKeywordSuggestion = (suggestion) => {
      if (!suggestion) return;
      setFocusKeywordSuggestions((prev) => {
        if (prev.includes(suggestion)) return prev;
        const updated = [...prev, suggestion];
        return updated.slice(-5); // Keep max 5 suggestions
      });
      // Don't auto-select - let user choose
    };

    // Add multiple suggestions at once
    const addFocusKeywordSuggestions = (suggestions) => {
      if (!suggestions || !Array.isArray(suggestions)) return;
      suggestions.forEach((s) => addFocusKeywordSuggestion(s));
    };

    // Load focus keyword from postConfig on mount
    React.useEffect(() => {
      if (postConfig.focus_keyword && !selectedFocusKeyword) {
        setSelectedFocusKeyword(postConfig.focus_keyword);
      } else if (postConfig.seo_focus_keyword && !selectedFocusKeyword) {
        setSelectedFocusKeyword(postConfig.seo_focus_keyword);
      }
    }, [postConfig.focus_keyword, postConfig.seo_focus_keyword]);

    // Check if should show welcome screen (no messages yet)
    React.useEffect(() => {
      if (messages.length > 0 || currentPlanRef.current) {
        setShowWelcome(false);
      }
    }, [messages.length]);

    // Welcome screen start handler
    const handleWelcomeStart = () => {
      // Set focus keyword if provided (but don't add to AI suggestions - it's user input)
      if (welcomeKeywordInput.trim()) {
        const keyword = welcomeKeywordInput.trim();
        handleFocusKeywordChange(keyword);
        // NOT adding to suggestions - user input is NOT AI suggestion
      }
      // Set mode and hide welcome
      setAgentMode(welcomeStartMode);
      setShowWelcome(false);
      // Focus the input
      setTimeout(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, 100);
    };

    // Run SEO Audit
    const runSeoAudit = async () => {
      if (isSeoAuditing || !postId) return;
      const operationController = beginAgentOperation("seo_audit", "SEO audit");
      setIsSeoAuditing(true);
      try {
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/seo-audit/${postId}`,
          {
            headers: {
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            signal: operationController.signal,
          },
        );
        const data = await response.json();
        if (!response.ok) {
          throw new Error(data.message || "Failed to run SEO audit");
        }
        setSeoAudit(data);
        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "complete",
              message: "SEO audit complete.",
              completedAt: new Date(),
            };
          }
          return newMessages;
        });
      } catch (error) {
        if (isAbortError(error)) {
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "stopped",
                message: "SEO audit stopped.",
              };
            }
            return newMessages;
          });
          return;
        }
        wpawLog.error("SEO Audit error:", error);
        setMessages((prev) => [
          ...prev,
          {
            role: "assistant",
            content: `SEO Audit error: ${error.message}`,
            type: "error",
          },
        ]);
        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "error",
              message: "SEO audit failed.",
            };
          }
          return newMessages;
        });
      } finally {
        setIsSeoAuditing(false);
        finishAgentOperation("seo_audit");
      }
    };

    const buildSeoAuditFixInstruction = (check) => {
      const focusKeyword =
        selectedFocusKeyword ||
        postConfig.focus_keyword ||
        postConfig.seo_focus_keyword ||
        "";
      const languageHint =
        postConfig.language && postConfig.language !== "auto"
          ? `Use ${postConfig.language}.`
          : "Use the same language as the article.";
      const issueName = check?.name || "SEO audit issue";
      const issueMessage = check?.message || "";
      const focusHint = focusKeyword
        ? `Focus keyword: "${focusKeyword}".`
        : "If a focus keyword is missing, ask the user to set one first.";
      const lowerName = String(issueName).toLowerCase();

      if (lowerName.includes("keyword in intro")) {
        return `Fix this SEO audit issue in the article introduction: ${issueMessage}. ${focusHint} Add the focus keyword naturally in the first paragraph without sounding forced. ${languageHint}`;
      }

      if (lowerName.includes("keyword density")) {
        return `Fix this SEO audit issue across the article: ${issueMessage}. ${focusHint} Improve keyword usage naturally, avoid stuffing, and keep the writing human and useful. ${languageHint}`;
      }

      if (lowerName.includes("ai-ish")) {
        return `Fix this audit issue across the article: ${issueMessage}. Make the writing more natural, specific, and human. Reduce generic AI-style phrasing while preserving meaning, structure, and facts. ${languageHint}`;
      }

      if (lowerName.includes("content length")) {
        return `Fix this SEO audit issue: ${issueMessage}. Expand the article with useful, non-fluffy details, examples, and reader guidance. ${focusHint} ${languageHint}`;
      }

      if (lowerName.includes("subheadings")) {
        return `Fix this readability issue: ${issueMessage}. Improve the article structure with useful H2/H3 subheadings while preserving the article's intent. ${focusHint} ${languageHint}`;
      }

      return `Fix this SEO audit issue: ${issueName}: ${issueMessage}. ${focusHint} Keep the result natural, useful, and aligned with the article intent. ${languageHint}`;
    };
    const getSeoFixKey = (check) =>
      `${check?.name || ""}:${check?.message || ""}`;
    const getSeoAuditPatternCount = (check) => {
      const auditCount = Number(seoAudit?.ai_ish_pattern_count || 0);
      if (auditCount > 0) {
        return auditCount;
      }
      const match = String(check?.message || "").match(/(\d+)\s+pattern/i);
      return match ? Number(match[1]) : 0;
    };
    const formatCountLabel = (count, singular, plural = `${singular}s`) => {
      const numeric = Number(count || 0);
      return `${numeric} ${numeric === 1 ? singular : plural}`;
    };
    const formatAuditPatternLabel = (auditContext) => {
      const patternCount = Number(auditContext?.patternCount || 0);
      return patternCount > 0
        ? formatCountLabel(patternCount, "pattern occurrence")
        : "audit pattern occurrences";
    };
    const buildAuditRefinementContext = (
      check,
      targetBlocks,
      refineableBlocks,
    ) => {
      const issueName = check?.name || "SEO audit issue";
      return {
        source: "seo_audit",
        issueName,
        auditMessage: check?.message || "",
        patternCount: getSeoAuditPatternCount(check),
        candidateBlockCount: Array.isArray(targetBlocks)
          ? targetBlocks.length
          : 0,
        refineableBlockCount: Array.isArray(refineableBlocks)
          ? refineableBlocks.length
          : 0,
      };
    };

    const handleSeoAuditFix = async (check) => {
      if (isLoading || isSeoAuditing || !check) {
        return;
      }

      const issueName = String(check.name || "").toLowerCase();
      const focusKeyword =
        selectedFocusKeyword ||
        postConfig.focus_keyword ||
        postConfig.seo_focus_keyword ||
        "";
      const fixKey = getSeoFixKey(check);
      setActiveSeoFixKey(fixKey);

      if (issueName.includes("focus keyword") && !focusKeyword) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Set a focus keyword first, then run the audit again.",
          },
        ]);
        setActiveTab("config");
        setActiveSeoFixKey("");
        return;
      }

      setActiveTab("chat");
      setShowWelcome(false);

      try {
        if (issueName.includes("meta description")) {
          setMessages((prev) => [
            ...prev,
            { role: "user", content: `Fix SEO audit: ${check.message}` },
          ]);
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "system",
              type: "timeline",
              status: "refining",
              message: "Generating SEO meta description...",
              timestamp: new Date(),
            },
          ]);
          await generateMetaDescription();
          return;
        }

        if (issueName.includes("keyword in title")) {
          const titleInstruction = focusKeyword
            ? `include the focus keyword "${focusKeyword}" naturally in the title, keep it compelling, and match the article language`
            : "make the title more SEO-friendly and aligned with the article";
          await handleTitleRefinement(`@title ${titleInstruction}`, ["@title"]);
          return;
        }

        const refineableBlocks = getRefineableBlocks();
        if (!refineableBlocks.length) {
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content:
                "No article blocks found to fix yet. Generate or insert content first, then run the audit again.",
            },
          ]);
          return;
        }

        const instruction = buildSeoAuditFixInstruction(check);
        const targetBlocks = issueName.includes("ai-ish")
          ? selectLikelyAiSlopBlocks(instruction, refineableBlocks)
          : refineableBlocks;
        if (issueName.includes("ai-ish") && targetBlocks.length === 0) {
          const patternCount = getSeoAuditPatternCount(check);
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content:
                patternCount > 0
                  ? `Audit found ${formatCountLabel(patternCount, "pattern occurrence")}, but I could not safely map those occurrences to editor blocks. I did not send the whole article to refinement.`
                  : "I rechecked the editor blocks and did not find AI-ish pattern matches, so I did not send the whole article to refinement.",
            },
          ]);
          return;
        }

        await handleChatRefinement(
          instruction,
          targetBlocks.map((block) => block.clientId),
          {
            useDiffPlan: false,
            auditContext: issueName.includes("ai-ish")
              ? buildAuditRefinementContext(
                  check,
                  targetBlocks,
                  refineableBlocks,
                )
              : null,
          },
        );
      } finally {
        setActiveSeoFixKey("");
      }
    };

    const generateMetaDescription = async () => {
      if (isGeneratingMeta) return;
      const operationController = beginAgentOperation(
        "meta",
        "meta description",
      );
      setIsGeneratingMeta(true);
      try {
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/generate-meta`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              postId: postId,
              sessionId: currentSessionId,
              focusKeyword: postConfig.seo_focus_keyword,
              chatHistory: messages.filter((m) => m.role !== "system"),
            }),
            signal: operationController.signal,
          },
        );

        if (!response.ok) {
          const data = await response.json();
          throw new Error(
            data.message || "Failed to generate meta description",
          );
        }

        const data = await response.json();
        applyProviderMetadata(data);
        if (data.meta_description) {
          updatePostConfig("seo_meta_description", data.meta_description);
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content: `✅ Meta description generated successfully`,
              type: "success",
            },
          ]);
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "complete",
                message: "Meta description generated.",
                completedAt: new Date(),
              };
            }
            return newMessages;
          });
        } else {
          throw new Error("No meta description returned from API");
        }
      } catch (error) {
        if (isAbortError(error)) {
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "stopped",
                message: "Meta description generation stopped.",
              };
            }
            return newMessages;
          });
          return;
        }
        wpawLog.error("Error generating meta description:", error);
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            content: `❌ Failed to generate meta description: ${error.message}`,
            type: "error",
          },
        ]);
        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "error",
              message: "Meta description failed.",
            };
          }
          return newMessages;
        });
      } finally {
        setIsGeneratingMeta(false);
        finishAgentOperation("meta");
      }
    };

    const extractBlockPreview = (block) => {
      const direct = toTextValue(
        block.attributes?.content ||
          block.attributes?.value ||
          block.attributes?.caption ||
          block.attributes?.title ||
          "",
      );

      if (direct) {
        return direct;
      }

      if (wp.blocks && typeof wp.blocks.getBlockContent === "function") {
        const html = wp.blocks.getBlockContent(block);
        if (html) {
          const temp = document.createElement("div");
          temp.innerHTML = html;
          return toTextValue(temp.textContent);
        }
      }

      return "";
    };
    const getBlockPreviewById = (clientId) => {
      if (!clientId) {
        return "";
      }
      const allBlocks = select("core/block-editor").getBlocks();
      const block = allBlocks.find((entry) => entry.clientId === clientId);
      if (!block) {
        return "";
      }
      return extractBlockPreview(block);
    };

    const buildWorkspaceSnapshot = React.useCallback(() => {
      const editor = select("core/editor");
      const blockEditor = select("core/block-editor");
      const allBlocks = blockEditor?.getBlocks ? blockEditor.getBlocks() : [];
      const selectedBlockId = blockEditor?.getSelectedBlockClientId
        ? blockEditor.getSelectedBlockClientId()
        : "";
      const selectedBlock =
        selectedBlockId && blockEditor?.getBlock
          ? blockEditor.getBlock(selectedBlockId)
          : null;
      const postTitle = editor?.getEditedPostAttribute
        ? editor.getEditedPostAttribute("title") || ""
        : "";
      const selectedType = selectedBlock?.name
        ? selectedBlock.name.replace("core/", "")
        : "";
      const selectedPreview = selectedBlock
        ? extractBlockPreview(selectedBlock)
        : "";

      return {
        title: postTitle || "Untitled draft",
        blockCount: allBlocks.filter((block) => {
          const preview = extractBlockPreview(block);
          return (
            preview ||
            (Array.isArray(block.innerBlocks) && block.innerBlocks.length > 0)
          );
        }).length,
        selectedBlockLabel: selectedBlock
          ? `${selectedType || "block"} ${selectedBlockId.slice(0, 6)}`
          : "None selected",
        selectedBlockPreview: selectedPreview
          ? selectedPreview.slice(0, 90)
          : "",
      };
    }, []);

    React.useEffect(() => {
      const updateSnapshot = () => {
        const next = buildWorkspaceSnapshot();
        setWorkspaceSnapshot((prev) => {
          if (JSON.stringify(prev) === JSON.stringify(next)) {
            return prev;
          }
          return next;
        });
      };

      updateSnapshot();
      const unsubscribe = wp.data?.subscribe
        ? wp.data.subscribe(updateSnapshot)
        : null;
      return () => {
        if (typeof unsubscribe === "function") {
          unsubscribe();
        }
      };
    }, [buildWorkspaceSnapshot]);

    // Auto-scroll to bottom when new messages arrive
    React.useEffect(() => {
      if (messagesContainerRef.current) {
        const container = messagesContainerRef.current;
        container.scrollTop = container.scrollHeight;
      }
    }, [messages, isLoading]);

    React.useEffect(() => {
      const handleBeforeUnload = (event) => {
        if (!isLoading) {
          return;
        }
        event.preventDefault();
        event.returnValue = "";
        return "";
      };
      window.addEventListener("beforeunload", handleBeforeUnload);
      return () =>
        window.removeEventListener("beforeunload", handleBeforeUnload);
    }, [isLoading]);

    React.useEffect(() => {
      loadSectionBlocks();
    }, [postId]);

    React.useEffect(() => {
      if (!postId) {
        return;
      }
      try {
        const savedSession = window.localStorage.getItem(
          `wpawSessionId_${postId}`,
        );
        if (savedSession) {
          setCurrentSessionId(savedSession);
        }
      } catch (error) {
        // Ignore storage read errors.
      }
    }, [postId]);

    React.useEffect(() => {
      if (!postId || !currentSessionId) {
        return;
      }
      try {
        window.localStorage.setItem(
          `wpawSessionId_${postId}`,
          currentSessionId,
        );
      } catch (error) {
        // Ignore storage write errors.
      }
    }, [postId, currentSessionId]);

    const sanitizeMessagesForStorage = React.useCallback((items) => {
      if (!Array.isArray(items)) {
        return [];
      }

      const MAX_MESSAGES = 300;
      const clipped = items.slice(-MAX_MESSAGES);

      return clipped.map((msg) => {
        const out = {};
        out.role = typeof msg?.role === "string" ? msg.role : "assistant";

        if (typeof msg?.content === "string") {
          out.content = msg.content;
        }
        if (typeof msg?.type === "string") {
          out.type = msg.type;
        }
        if (typeof msg?.status === "string") {
          out.status = msg.status;
        }
        if (msg?.timestamp) {
          out.timestamp = msg.timestamp;
        }
        if (Array.isArray(msg?.sections)) {
          out.sections = msg.sections;
        }
        if (msg?.meta && typeof msg.meta === "object") {
          out.meta = msg.meta;
        }
        if (msg?.plan && typeof msg.plan === "object") {
          out.plan = msg.plan;
        }

        return out;
      });
    }, []);

    const hydrateSessionStateFromMessages = React.useCallback(
      (sessionMessages) => {
        if (!Array.isArray(sessionMessages) || sessionMessages.length === 0) {
          currentPlanRef.current = null;
          setAgentMode("chat");
          return;
        }

        let latestPlan = null;
        for (let i = sessionMessages.length - 1; i >= 0; i -= 1) {
          if (sessionMessages[i]?.type === "plan" && sessionMessages[i]?.plan) {
            latestPlan = ensurePlanTasks(sessionMessages[i].plan);
            break;
          }
        }

        currentPlanRef.current = latestPlan;
        if (latestPlan) {
          setAgentMode("planning");
        } else {
          setAgentMode("chat");
        }
        setShowWelcome(false);
      },
      [],
    );

    const persistSessionMessages = React.useCallback(
      async (
        forceSessionId = null,
        skipSanitization = false,
        targetMessages = null,
      ) => {
        const sessionId = forceSessionId || currentSessionId;
        if (!sessionId) {
          return;
        }

        const msgsToSave =
          targetMessages ||
          (messagesRef && messagesRef.current ? messagesRef.current : messages);

        // Safety: never overwrite the DB with an empty array. This prevents
        // race conditions during session switching from wiping message history.
        if (!Array.isArray(msgsToSave) || msgsToSave.length === 0) {
          return;
        }

        const sanitized = skipSanitization
          ? msgsToSave
          : sanitizeMessagesForStorage(msgsToSave);
        const serialized = JSON.stringify(sanitized);

        if (serialized === lastPersistedMessagesRef.current) {
          return;
        }

        try {
          const response = await fetch(
            `${wpAgenticWriter.apiUrl}/conversations/${sessionId}/messages`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify({ messages: sanitized }),
            },
          );
          if (!response.ok) {
            throw new Error("Failed to persist session messages");
          }
          lastPersistedMessagesRef.current = serialized;
        } catch (error) {
          // Non-fatal: keep editor responsive, but do not mark this state persisted.
          window.console?.warn?.(
            "WP Agentic Writer: failed to persist session messages.",
            error,
          );
        }
      },
      [sanitizeMessagesForStorage, currentSessionId],
    );

    // Stable ref for the latest messages array to prevent stale closures in unload events
    const messagesRef = React.useRef(messages);
    React.useEffect(() => {
      messagesRef.current = messages;
    }, [messages]);

    // Flush pending message persistence on page unload to prevent data loss
    React.useEffect(() => {
      const flushOnUnload = () => {
        if (messagesSaveTimeoutRef.current) {
          clearTimeout(messagesSaveTimeoutRef.current);
        }
        if (!currentSessionId || isHydratingSessionRef.current) {
          return;
        }

        // Use messagesRef.current (or fallback to messages) to prevent stale closures
        const currentMessages =
          messagesRef && messagesRef.current ? messagesRef.current : messages;

        // Never flush empty messages — prevents wiping DB on session switch / race
        if (!Array.isArray(currentMessages) || currentMessages.length === 0) {
          return;
        }

        const sanitized = sanitizeMessagesForStorage(currentMessages);
        const serialized = JSON.stringify(sanitized);
        if (serialized === lastPersistedMessagesRef.current) {
          return;
        }
        // Synchronous XHR as last resort during unload (sendBeacon can't set headers)
        try {
          const xhr = new XMLHttpRequest();
          xhr.open(
            "POST",
            `${wpAgenticWriter.apiUrl}/conversations/${currentSessionId}/messages`,
            false,
          ); // sync
          xhr.setRequestHeader("Content-Type", "application/json");
          xhr.setRequestHeader("X-WP-Nonce", wpAgenticWriter.nonce);
          xhr.send(JSON.stringify({ messages: sanitized }));
        } catch (error) {
          // Ignore unload errors
        }
      };

      window.addEventListener("beforeunload", flushOnUnload);
      return () => window.removeEventListener("beforeunload", flushOnUnload);
    }, [currentSessionId, sanitizeMessagesForStorage]);

    // ── Session edit-lock heartbeat ──────────────────────────────────────
    const acquireSessionLock = React.useCallback(async (sessionId) => {
      if (!sessionId) return false;
      try {
        const res = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/${sessionId}/lock`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ tab_id: tabIdRef.current }),
          },
        );
        if (!res.ok) return false;
        const data = await res.json();
        if (data.locked) {
          setSessionLock({
            locked: true,
            lockedByOther: false,
            holderTabId: "",
          });
          return true;
        }
        // Another tab holds the lock
        setSessionLock({
          locked: false,
          lockedByOther: true,
          holderTabId: data.holder?.tab_id || "",
        });
        return false;
      } catch (e) {
        // Network error — assume unlocked to avoid blocking the user
        return true;
      }
    }, []);

    const releaseSessionLock = React.useCallback(async (sessionId) => {
      if (!sessionId) return;
      try {
        // Use sendBeacon-style sync for reliability on unload
        await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/${sessionId}/lock`,
          {
            method: "DELETE",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ tab_id: tabIdRef.current }),
            keepalive: true,
          },
        );
      } catch (e) {
        // Best-effort
      }
    }, []);

    const startLockHeartbeat = React.useCallback(
      (sessionId) => {
        // Clear any existing heartbeat
        if (lockHeartbeatRef.current) {
          clearInterval(lockHeartbeatRef.current);
        }
        // Heartbeat: refresh lock every 30 seconds
        lockHeartbeatRef.current = setInterval(() => {
          acquireSessionLock(sessionId);
        }, 30000);
      },
      [acquireSessionLock],
    );

    const stopLockHeartbeat = React.useCallback(() => {
      if (lockHeartbeatRef.current) {
        clearInterval(lockHeartbeatRef.current);
        lockHeartbeatRef.current = null;
      }
    }, []);

    // Release lock + stop heartbeat on unload
    React.useEffect(() => {
      const releaseLockOnUnload = () => {
        if (!currentSessionId) return;
        stopLockHeartbeat();
        // Sync XHR for unload reliability
        try {
          const xhr = new XMLHttpRequest();
          xhr.open(
            "DELETE",
            `${wpAgenticWriter.apiUrl}/conversations/${currentSessionId}/lock`,
            false,
          );
          xhr.setRequestHeader("Content-Type", "application/json");
          xhr.setRequestHeader("X-WP-Nonce", wpAgenticWriter.nonce);
          xhr.send(JSON.stringify({ tab_id: tabIdRef.current }));
        } catch (e) {
          // Best-effort
        }
      };
      window.addEventListener("beforeunload", releaseLockOnUnload);
      return () =>
        window.removeEventListener("beforeunload", releaseLockOnUnload);
    }, [currentSessionId, stopLockHeartbeat]);

    // Force-take-over: acquire the lock regardless of who holds it
    const takeOverSession = React.useCallback(async () => {
      if (!currentSessionId) return;
      // Force-acquire by telling the server to ignore the expiry window
      try {
        const res = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/${currentSessionId}/lock`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ tab_id: tabIdRef.current, force: true }),
          },
        );
        if (res.ok) {
          const data = await res.json();
          if (data.locked) {
            setSessionLock({
              locked: true,
              lockedByOther: false,
              holderTabId: "",
            });
            startLockHeartbeat(currentSessionId);
          }
        }
      } catch (e) {
        // Best-effort
      }
    }, [currentSessionId, startLockHeartbeat]);

    // Auto-save debounced messages to database
    React.useEffect(() => {
      if (!currentSessionId) {
        return;
      }
      if (isHydratingSessionRef.current) {
        return;
      }

      // Don't auto-save if another tab holds the edit lock
      if (sessionLock.lockedByOther) {
        return;
      }

      // Never auto-persist an empty messages array — this protects against
      // race conditions where setMessages([]) fires during session switches
      // and the debounce timer would overwrite the DB with nothing.
      const currentMsgs =
        messagesRef && messagesRef.current ? messagesRef.current : messages;
      if (!Array.isArray(currentMsgs) || currentMsgs.length === 0) {
        return;
      }

      if (messagesSaveTimeoutRef.current) {
        clearTimeout(messagesSaveTimeoutRef.current);
      }

      messagesSaveTimeoutRef.current = setTimeout(() => {
        // Re-check at fire time: skip if hydrating or messages are now empty
        if (isHydratingSessionRef.current) {
          return;
        }
        const latestMsgs =
          messagesRef && messagesRef.current ? messagesRef.current : [];
        if (latestMsgs.length === 0) {
          return;
        }
        persistSessionMessages(currentSessionId, false, latestMsgs);
      }, 3000);

      return () => {
        if (messagesSaveTimeoutRef.current) {
          clearTimeout(messagesSaveTimeoutRef.current);
        }
      };
    }, [currentSessionId, messages, persistSessionMessages]);

    React.useEffect(() => {
      const loadChatHistory = async () => {
        // Skip if we already have a session loaded (e.g., from openSessionById)
        if (messages.length > 0 || isHydratingSessionRef.current) {
          return;
        }

        try {
          const headers = {
            "X-WP-Nonce": wpAgenticWriter.nonce,
          };
          let historyMessages = [];
          let resolvedSessionId = "";

          // Primary source: merged sessions list (post sessions + unassigned sessions).
          const sessions = await loadPostSessions();
          if (!postId) {
            return;
          }
          if (sessions.length > 0) {
            if (sessions.length > 0) {
              let selected = sessions[0];
              const preferred = (() => {
                try {
                  return (
                    window.localStorage.getItem(`wpawSessionId_${postId}`) || ""
                  );
                } catch (error) {
                  return "";
                }
              })();
              if (preferred) {
                const match = sessions.find((s) => s?.session_id === preferred);
                if (match) {
                  selected = match;
                }
              }
              resolvedSessionId = selected?.session_id || "";
              if (
                Array.isArray(selected?.messages) &&
                selected.messages.length > 0
              ) {
                historyMessages = selected.messages;
              }
            }
          }

          // If we found a session but its messages weren't inlined in the list
          // response (e.g. from the user-sessions endpoint which only returns
          // message_count), fetch the full session to get actual messages.
          if (resolvedSessionId && historyMessages.length === 0) {
            try {
              const fullSession = await fetch(
                `${wpAgenticWriter.apiUrl}/conversations/${resolvedSessionId}`,
                { method: "GET", headers },
              );
              if (fullSession.ok) {
                const fullData = await fullSession.json();
                if (
                  fullData &&
                  Array.isArray(fullData.messages) &&
                  fullData.messages.length > 0
                ) {
                  historyMessages = fullData.messages;
                }
              }
            } catch (e) {
              // Non-fatal: fall through to legacy endpoints
            }
          }

          // Canonical single-session endpoint fallback.
          if (postId && !resolvedSessionId) {
            const primary = await fetch(
              `${wpAgenticWriter.apiUrl}/conversation/${postId}`,
              {
                method: "GET",
                headers,
              },
            );
            if (primary.ok) {
              const data = await primary.json();
              if (data?.session_id) {
                resolvedSessionId = data.session_id;
              }
              if (
                data &&
                Array.isArray(data.messages) &&
                data.messages.length > 0
              ) {
                historyMessages = data.messages;
              }
            }
          }

          // Legacy endpoint fallback - only if no session found at all.
          if (postId && historyMessages.length === 0 && !resolvedSessionId) {
            const legacy = await fetch(
              `${wpAgenticWriter.apiUrl}/chat-history/${postId}`,
              {
                method: "GET",
                headers,
              },
            );
            if (legacy.ok) {
              const legacyData = await legacy.json();
              if (
                legacyData &&
                Array.isArray(legacyData.messages) &&
                legacyData.messages.length > 0
              ) {
                historyMessages = legacyData.messages;
              }
            }
          }

          if (historyMessages.length > 0) {
            isHydratingSessionRef.current = true;
            lastPersistedMessagesRef.current = JSON.stringify(
              sanitizeMessagesForStorage(historyMessages),
            );
            hydrateSessionStateFromMessages(historyMessages);
            setMessages(historyMessages);
            setTimeout(() => {
              isHydratingSessionRef.current = false;
            }, 0);
          }
          if (resolvedSessionId) {
            setCurrentSessionId(resolvedSessionId);
            // Acquire edit lock for initial session on mount
            acquireSessionLock(resolvedSessionId).then((acquired) => {
              if (acquired) startLockHeartbeat(resolvedSessionId);
            });
          }
        } catch (error) {
          // Ignore history load failures.
        }
      };
      loadChatHistory();
      // Only run on mount / postId change — NOT on currentSessionId change
      // Session switches are handled by openSessionById directly
    }, [postId]);

    // MEMANTO: Restore session from memory when post editor loads.
    React.useEffect(() => {
      if (!postId || memantoRestoreFetchedRef.current) {
        return;
      }
      memantoRestoreFetchedRef.current = true;

      fetch(`${wpAgenticWriter.apiUrl}/memanto/restore?post_id=${postId}`, {
        headers: { "X-WP-Nonce": wpAgenticWriter.nonce },
      })
        .then((r) => (r.ok ? r.json() : Promise.reject(r)))
        .then((data) => {
          if (data?.restored) {
            wpawLog.info("MEMANTO: Session restored", data);
            setMemantoRestore({
              restored: true,
              summary: data.summary || "",
              memories: data.memories || [],
              preferences: data.preferences || [],
              systemMessage: data.system_message || "",
            });
          }
        })
        .catch(() => {
          // Graceful degradation — ignore restore failures.
        });
    }, [postId]);

    // MEMANTO: Apply user preferences to new post config (carry-over).
    React.useEffect(() => {
      if (
        !postId ||
        !memantoRestore.restored ||
        !memantoRestore.preferences?.length ||
        !configHydratedRef.current
      ) {
        return;
      }
      // Only apply preferences that aren't already set by the post config.
      setPostConfig((prev) => {
        let changed = false;
        const updated = { ...prev };
        for (const pref of memantoRestore.preferences) {
          // Parse "User preference: tone=X, audience=Y, length=Z, language=W"
          const content = pref.content || "";
          const toneMatch = content.match(/tone\s*=\s*([^,\n]+)/i);
          const audienceMatch = content.match(/audience\s*=\s*([^,\n]+)/i);
          const lengthMatch = content.match(
            /(?:article_)?length\s*=\s*([^,\n]+)/i,
          );
          const langMatch = content.match(/language\s*=\s*([^,\n]+)/i);
          if (
            toneMatch &&
            !prev.tone &&
            "default" !== toneMatch[1].trim().toLowerCase()
          ) {
            updated.tone = toneMatch[1].trim();
            changed = true;
          }
          if (
            audienceMatch &&
            !prev.audience &&
            "general" !== audienceMatch[1].trim().toLowerCase()
          ) {
            updated.audience = audienceMatch[1].trim();
            changed = true;
          }
          if (
            lengthMatch &&
            prev.article_length === "medium" &&
            "medium" !== lengthMatch[1].trim().toLowerCase()
          ) {
            updated.article_length = lengthMatch[1].trim();
            changed = true;
          }
          if (
            langMatch &&
            prev.language === "auto" &&
            "auto" !== langMatch[1].trim().toLowerCase()
          ) {
            updated.language = langMatch[1].trim();
            changed = true;
          }
        }
        return changed ? updated : prev;
      });
    }, [postId, memantoRestore.restored, isConfigLoading]);

    const loadPostSessions = async () => {
      const headers = {
        "X-WP-Nonce": wpAgenticWriter.nonce,
      };
      let postSessions = [];
      let unassignedSessions = [];
      const fetchUserSessionsByStatus = async (status) => {
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations?status=${status}&limit=50`,
          {
            method: "GET",
            headers,
          },
        );
        if (!response.ok) {
          return [];
        }
        const data = await response.json();
        return Array.isArray(data?.sessions) ? data.sessions : [];
      };
      const getContinuableUnassignedSessions = (sessions) =>
        sessions.filter((s) => {
          const pid = Number(s?.post_id || 0);
          return pid === 0;
        });
      if (postId) {
        const postRes = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/post/${postId}`,
          {
            method: "GET",
            headers,
          },
        );
        if (postRes.ok) {
          const postData = await postRes.json();
          postSessions = Array.isArray(postData?.sessions)
            ? postData.sessions
            : [];
        }

        // Fallback: if this post has no linked sessions, surface truly unassigned
        // sessions so users can continue unfinished work from other tabs or prior
        // page loads without attaching history from another saved post.
        if (postSessions.length === 0) {
          const [activeSessions, completedSessions] = await Promise.all([
            fetchUserSessionsByStatus("active"),
            fetchUserSessionsByStatus("completed"),
          ]);
          // Completed is a legacy conversation status; still treat it as continuable.
          unassignedSessions = getContinuableUnassignedSessions([
            ...activeSessions,
            ...completedSessions,
          ]);
        }
      } else {
        // New post flow: list prior sessions for manual navigation. The
        // loadChatHistory guard below prevents auto-continuing any of them.
        const [activeSessions, completedSessions] = await Promise.all([
          fetchUserSessionsByStatus("active"),
          fetchUserSessionsByStatus("completed"),
        ]);
        // Completed is a legacy conversation status; still treat it as continuable.
        unassignedSessions = [
          ...activeSessions,
          ...completedSessions,
        ];
      }

      const merged = [...postSessions, ...unassignedSessions];
      const deduped = [];
      const seen = new Set();
      merged.forEach((session) => {
        const sid = session?.session_id || "";
        if (!sid || seen.has(sid)) {
          return;
        }
        const storedMessageCount = Number(
          session?.message_count ??
            (Array.isArray(session?.messages) ? session.messages.length : 0),
        );
        if (storedMessageCount <= 0) {
          return;
        }
        seen.add(sid);
        deduped.push(session);
      });

      setAvailableSessions(deduped);
      return deduped;
    };
    const openSessionById = async (sessionId) => {
      if (!sessionId) {
        return;
      }
      const headers = {
        "X-WP-Nonce": wpAgenticWriter.nonce,
      };
      setIsSessionActionLoading(true);
      try {
        // Release lock on previous session before switching
        if (currentSessionId && currentSessionId !== sessionId) {
          stopLockHeartbeat();
          releaseSessionLock(currentSessionId);
        }

        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/${sessionId}`,
          {
            method: "GET",
            headers,
          },
        );
        if (!response.ok) {
          throw new Error("Failed to load session");
        }
        const data = await response.json();
        // Cancel any pending message-save debounce from the previous session
        // to prevent stale timers from overwriting the new session's data.
        if (messagesSaveTimeoutRef.current) {
          clearTimeout(messagesSaveTimeoutRef.current);
          messagesSaveTimeoutRef.current = null;
        }
        isHydratingSessionRef.current = true;
        setCurrentSessionId(sessionId);
        const sessionMessages = Array.isArray(data?.messages)
          ? data.messages
          : [];

        // If session has no messages, try fetching from the conversations/post endpoint
        // as a recovery mechanism (messages may be stored under post relationship)
        if (
          sessionMessages.length === 0 &&
          data?.post_id &&
          Number(data.post_id) > 0
        ) {
          wpawLog.warn(
            "Session has 0 messages, attempting post-based recovery:",
            sessionId,
          );
          try {
            const postSessionRes = await fetch(
              `${wpAgenticWriter.apiUrl}/conversation/${data.post_id}`,
              {
                method: "GET",
                headers,
              },
            );
            if (postSessionRes.ok) {
              const postSessionData = await postSessionRes.json();
              if (
                Array.isArray(postSessionData?.messages) &&
                postSessionData.messages.length > 0
              ) {
                sessionMessages.push(...postSessionData.messages);
              }
            }
          } catch (e) {
            // Non-fatal recovery attempt
          }
        }

        lastPersistedMessagesRef.current = JSON.stringify(
          sanitizeMessagesForStorage(sessionMessages),
        );
        hydrateSessionStateFromMessages(sessionMessages);
        setMessages(sessionMessages);
        setShowWelcome(false);

        // Auto-link unassigned session to current post for continuity
        const sessionPostId = Number(data?.post_id || 0);
        if (postId && postId > 0 && sessionPostId === 0) {
          fetch(
            `${wpAgenticWriter.apiUrl}/conversations/${sessionId}/link-post`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify({ postId: postId }),
            },
          ).catch(() => {}); // Non-blocking
        }

        // Acquire edit lock for this session (multi-tab safety)
        const lockAcquired = await acquireSessionLock(sessionId);
        if (lockAcquired) {
          startLockHeartbeat(sessionId);
        }

        setTimeout(() => {
          isHydratingSessionRef.current = false;
        }, 0);
      } catch (error) {
        isHydratingSessionRef.current = false;
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: Failed to load selected session.",
          },
        ]);
      } finally {
        setIsSessionActionLoading(false);
      }
    };

    const resolveStreamTarget = (content) => {
      if (progressRegex.test(content)) {
        return "timeline";
      }

      if (content.length >= 6 || /[\s.!?]/.test(content)) {
        return "assistant";
      }

      return null;
    };
    const normalizeMentionToken = (token) => {
      if (!token) {
        return "";
      }

      return token
        .replace(/[\u2010-\u2015\u2212]/g, "-")
        .replace(/[.,;:!?)]*$/g, "")
        .toLowerCase();
    };
    const extractMentionsFromText = (text) => {
      const tokens = [];
      const mentionRegex = /@([^\s]+)/g;
      let match;

      while ((match = mentionRegex.exec(text))) {
        const normalized = normalizeMentionToken(match[1]);
        if (normalized) {
          tokens.push("@" + normalized);
        }
      }

      return tokens;
    };
    const stripMentionsFromText = (text) => {
      if (!text) {
        return "";
      }

      return text
        .replace(/@[\w-]+/g, "")
        .replace(/\s{2,}/g, " ")
        .trim();
    };
    const hasTitleMention = (mentionTokens) => {
      return (
        Array.isArray(mentionTokens) &&
        mentionTokens.some(
          (token) =>
            normalizeMentionToken(String(token).replace("@", "")) === "title",
        )
      );
    };
    const handleTitleRefinement = async (
      rawMessage,
      mentionTokens,
      options = {},
    ) => {
      const { skipUserMessage = false } = options;
      const instruction = stripMentionsFromText(rawMessage || "");

      if (!instruction) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "Please add title instruction after @title. Example: @title tulis ulang, gunakan focus keyword di awal.",
          },
        ]);
        return false;
      }

      if (!skipUserMessage) {
        setMessages((prev) => [...prev, { role: "user", content: rawMessage }]);
      }

      const operationController = beginAgentOperation(
        "title",
        "title refinement",
      );
      setIsLoading(true);
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "refining",
          message: "Refining title...",
          timestamp: new Date(),
        },
      ]);

      try {
        const response = await fetch(`${wpAgenticWriter.apiUrl}/refine-title`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-WP-Nonce": wpAgenticWriter.nonce,
          },
          body: JSON.stringify({
            postId: postId,
            sessionId: currentSessionId,
            instruction: instruction,
          }),
          signal: operationController.signal,
        });

        const data = await response.json();
        if (!response.ok) {
          throw new Error(data?.message || "Failed to refine title");
        }

        if (data?.title) {
          dispatch("core/editor").editPost({ title: data.title });
        }

        if (data?.cost) {
          setCost({ ...cost, session: cost.session + Number(data.cost || 0) });
        }
        applyProviderMetadata(data);
        setMessages((prev) => {
          const next = [...prev];
          const timelineIndex = findLastActiveTimelineIndex(next);
          if (timelineIndex !== -1) {
            next[timelineIndex] = {
              ...next[timelineIndex],
              status: "complete",
              message: "Title refined successfully.",
              completedAt: new Date(),
            };
          }
          next.push({
            role: "assistant",
            content: `Updated title: ${data.title || ""}`,
          });
          return next;
        });
        return true;
      } catch (error) {
        if (timeout) {
          clearTimeout(timeout);
        }
        if (isAbortError(error)) {
          setMessages((prev) => {
            const next = [...prev];
            const timelineIndex = findLastActiveTimelineIndex(next);
            if (timelineIndex !== -1) {
              next[timelineIndex] = {
                ...next[timelineIndex],
                status: "stopped",
                message: "Title refinement stopped.",
              };
            }
            return next;
          });
          return false;
        }
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: " + (error.message || "Failed to refine title"),
          },
        ]);
        return false;
      } finally {
        setIsLoading(false);
        finishAgentOperation("title");
      }
    };
    const parseInsertCommand = (text) => {
      const commands = [
        { mode: "add_below", regex: /^\s*(?:\/)?add below\b[:\-]?\s*/i },
        { mode: "add_above", regex: /^\s*(?:\/)?add above\b[:\-]?\s*/i },
        {
          mode: "append_code",
          regex: /^\s*(?:\/)?append code block\b[:\-]?\s*/i,
        },
        { mode: "append_code", regex: /^\s*(?:\/)?append code\b[:\-]?\s*/i },
        { mode: "append_code", regex: /^\s*(?:\/)?add code block\b[:\-]?\s*/i },
      ];

      for (const command of commands) {
        if (command.regex.test(text)) {
          return {
            mode: command.mode,
            message: text.replace(command.regex, "").trim(),
          };
        }
      }

      return null;
    };
    const getSlashOptions = (query) => {
      const options = [
        {
          id: "add-below",
          label: "add below",
          sublabel: "Insert a new paragraph below the target block",
          insertText: "add below @",
        },
        {
          id: "add-above",
          label: "add above",
          sublabel: "Insert a new paragraph above the target block",
          insertText: "add above @",
        },
        {
          id: "append-code-block",
          label: "append code block",
          sublabel: "Insert a code block below the target block",
          insertText: "append code block @",
        },
        {
          id: "reformat",
          label: "reformat",
          sublabel: "Convert markdown-like text into blocks",
          insertText: "reformat @",
        },
      ];

      if (!query) {
        return options;
      }

      const queryLower = query.toLowerCase();
      return options.filter((option) => option.label.includes(queryLower));
    };
    const getBlockIndex = (clientId) => {
      const blockIndex = select("core/block-editor").getBlockIndex
        ? select("core/block-editor").getBlockIndex(clientId)
        : -1;
      if (blockIndex !== -1) {
        return blockIndex;
      }

      const allBlocks = select("core/block-editor").getBlocks();
      return allBlocks.findIndex((block) => block.clientId === clientId);
    };
    const resolveTargetBlockId = (mentionTokens) => {
      if (mentionTokens.length > 0) {
        const resolved = resolveBlockMentions(mentionTokens);
        if (resolved.length > 0) {
          return resolved[0];
        }
      }

      const selectedBlockId =
        select("core/block-editor").getSelectedBlockClientId();
      if (selectedBlockId) {
        return selectedBlockId;
      }

      const allBlocks = select("core/block-editor").getBlocks();
      return allBlocks.length > 0
        ? allBlocks[allBlocks.length - 1].clientId
        : null;
    };
    const insertRefinementBlock = async (
      mode,
      message,
      mentionTokens,
      originalMessage,
    ) => {
      const initialTargetBlockId = resolveTargetBlockId(mentionTokens);
      const initialTargetBlock = initialTargetBlockId
        ? select("core/block-editor").getBlock(initialTargetBlockId)
        : null;
      const listParentId =
        initialTargetBlock?.name === "core/list-item"
          ? getParentListId(initialTargetBlockId)
          : null;
      const targetBlockId = listParentId || initialTargetBlockId;
      if (!targetBlockId) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "No target block found. Select a block or mention one with @paragraph-1.",
          },
        ]);
        setIsLoading(false);
        return;
      }

      const insertIndexBase = getBlockIndex(targetBlockId);
      const insertIndex =
        insertIndexBase === -1
          ? undefined
          : insertIndexBase + (mode === "add_above" ? 0 : 1);
      const { insertBlocks } = dispatch("core/block-editor");
      const blockType = mode === "append_code" ? "core/code" : "core/paragraph";
      const newBlock = wp.blocks.createBlock(
        blockType,
        mode === "append_code"
          ? { content: "", language: "text" }
          : { content: "" },
      );

      insertBlocks(newBlock, insertIndex);

      let refinementMessage = stripMentionsFromText(message);

      if (initialTargetBlock?.name === "core/list-item") {
        const listItemText = extractBlockPreview(initialTargetBlock);
        if (listItemText) {
          refinementMessage = refinementMessage
            ? `${refinementMessage}\n\nAdd a short description for: "${listItemText}".`
            : `Add a short description for: "${listItemText}".`;
        }
      }

      const contextSnippets = getContextFromMentions(
        mentionTokens,
        initialTargetBlockId,
      );
      if (!contextSnippets.length) {
        const headingContext = getHeadingContextForBlock(targetBlockId);
        if (headingContext) {
          contextSnippets.push(`Heading: ${headingContext}`);
        }
        getNearbyParagraphContext(targetBlockId, 2).forEach(
          (snippet, index) => {
            contextSnippets.push(`Paragraph ${index + 1}: ${snippet}`);
          },
        );
      }

      if (contextSnippets.length) {
        refinementMessage = `${refinementMessage}\n\nContext snippets:\n${contextSnippets.map((snippet) => `- ${snippet}`).join("\n")}`;
      }

      const requestedBlockType = blockType;
      refinementMessage = `${refinementMessage}\n\nReturn only JSON: {"content":"...","blockType":"${requestedBlockType}"} with no extra text.`;

      if (mode === "append_code") {
        refinementMessage += ' Put the code in "content" only, no backticks.';
      }

      setInput("");
      setMessages((prev) => [
        ...prev,
        { role: "user", content: originalMessage },
      ]);
      await handleChatRefinement(refinementMessage, [newBlock.clientId], {
        skipUserMessage: true,
        useDiffPlan: false,
      });
    };
    const streamGeneratePlan = async (request, options = {}) => {
      const { resume = false, suggestKeywords = agentMode === "planning" } =
        options;
      const normalizedRequest = {
        ...request,
        postConfig: postConfig,
        chatHistory: buildChatHistoryPayload(),
      };
      lastGenerationRequestRef.current = normalizedRequest;
      const operationType =
        agentMode === "planning" ? "planning" : "generation";
      const operationController = beginAgentOperation(
        operationType,
        operationType === "planning"
          ? "outline generation"
          : "article generation",
      );
      setIsLoading(true);

      // Capture snapshot before generation (only if not resuming)
      if (!resume) {
        pushUndoSnapshot("Article Generation");
      }

      let timeout = null;
      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/generate-plan",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ ...normalizedRequest, resume: resume }),
            signal: operationController.signal,
          },
        );

        if (!response.ok) {
          const error = await response.json();
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: formatAiErrorMessage(
                error,
                "Failed to generate article",
              ),
              canRetry: true,
              retryType: "generation",
            },
          ]);
          return;
        }

        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();
        timeout = setTimeout(() => {
          if (isLoading) {
            wpawLog.error("Generation timeout - no response received");
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content: formatAiErrorMessage(
                  "cURL error 28: Operation timed out after 120000 milliseconds",
                  "Failed to generate article",
                ),
                canRetry: true,
                retryType: "generation",
              },
            ]);
            setIsLoading(false);
            reader.cancel();
          }
        }, 120000);

        while (true) {
          if (stopExecutionRef.current || operationController.signal.aborted) {
            await reader.cancel().catch(() => {});
            throw new DOMException("Operation stopped by user", "AbortError");
          }
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value, { stream: true });
          const lines = chunk.split("\n");

          for (const line of lines) {
            if (line.startsWith("data: ")) {
              try {
                const data = JSON.parse(line.slice(6));

                if (data.type === "plan") {
                  setCost({ ...cost, session: cost.session + data.cost });
                  if (agentMode === "planning" && data.plan) {
                    updateOrCreatePlanMessage(data.plan, { suggestKeywords });
                  }
                } else if (data.type === "title_update") {
                  dispatch("core/editor").editPost({ title: data.title });
                } else if (data.type === "status") {
                  if (data.status === "complete") {
                    continue;
                  }
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: data.status,
                        message: data.message,
                        icon: data.icon,
                      };
                    }
                    return newMessages;
                  });
                } else if (
                  data.type === "conversational" ||
                  data.type === "conversational_stream"
                ) {
                  const cleanContent = (data.content || "")
                    .replace(/~~~ARTICLE~+/g, "")
                    .replace(/~~~ARTICLE~~~[\r\n]*/g, "")
                    .trim();

                  if (
                    !cleanContent ||
                    shouldSkipPlanningCompletion(cleanContent)
                  ) {
                    continue;
                  }

                  const streamTarget =
                    streamTargetRef.current ||
                    resolveStreamTarget(cleanContent);
                  if (!streamTarget) {
                    continue;
                  }

                  streamTargetRef.current = streamTarget;

                  if (streamTarget === "timeline") {
                    updateOrCreateTimelineEntry(cleanContent);
                  } else if (data.type === "conversational") {
                    setMessages((prev) => [
                      ...prev,
                      { role: "assistant", content: cleanContent },
                    ]);
                  } else {
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastIdx = newMessages.length - 1;
                      if (
                        newMessages[lastIdx] &&
                        newMessages[lastIdx].role === "assistant"
                      ) {
                        newMessages[lastIdx] = {
                          ...newMessages[lastIdx],
                          content: cleanContent,
                        };
                      } else {
                        newMessages.push({
                          role: "assistant",
                          content: cleanContent,
                        });
                      }
                      return newMessages;
                    });
                  }
                } else if (data.type === "block") {
                  const { insertBlocks } = dispatch("core/block-editor");
                  let newBlock;

                  if (data.block.blockName === "core/paragraph") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/paragraph", {
                      content: content,
                    });
                  } else if (data.block.blockName === "core/heading") {
                    const level = data.block.attrs?.level || 2;
                    const content =
                      data.block.innerHTML?.match(
                        /<h[1-6]>(.*?)<\/h[1-6]>/,
                      )?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/heading", {
                      level: level,
                      content: content,
                    });
                  } else if (data.block.blockName === "core/list") {
                    const listItems = (data.block.innerBlocks || []).map(
                      (item) => {
                        const content =
                          item.innerHTML?.match(/<li>(.*?)<\/li>/)?.[1] || "";
                        return wp.blocks.createBlock("core/list-item", {
                          content: content,
                        });
                      },
                    );
                    newBlock = wp.blocks.createBlock(
                      "core/list",
                      {
                        ...(data.block.attrs || {}),
                        ordered: data.block.attrs?.ordered || false,
                      },
                      listItems,
                    );
                  } else if (data.block.blockName === "core/quote") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/quote", {
                      value: content,
                    });
                  } else if (data.block.blockName === "core/image") {
                    newBlock = wp.blocks.createBlock(
                      "core/image",
                      data.block.attrs || {},
                    );
                  } else if (data.block.blockName === "core/code") {
                    newBlock = wp.blocks.createBlock(
                      "core/code",
                      data.block.attrs || {},
                    );
                  }

                  if (newBlock) {
                    insertBlocks(newBlock);
                  }
                } else if (data.type === "complete") {
                  applyProviderMetadata(data);
                  clearTimeout(timeout);
                  setCost({ ...cost, session: cost.session + data.totalCost });

                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: "complete",
                        message:
                          agentMode === "planning"
                            ? "Outline ready."
                            : "Article generated successfully!",
                        completedAt: new Date(),
                      };
                    }
                    return newMessages;
                  });
                } else if (data.type === "error") {
                  clearTimeout(timeout);
                  setMessages((prev) => [
                    ...prev,
                    {
                      role: "system",
                      type: "error",
                      content: formatAiErrorMessage(
                        data.message ||
                          "An error occurred during article generation",
                        "Failed to generate article",
                      ),
                      canRetry: true,
                      retryType: "generation",
                    },
                  ]);
                }
              } catch (parseError) {
                wpawLog.error(
                  "Failed to parse streaming data:",
                  line,
                  parseError,
                );
              }
            }
          }
        }

        if (timeout) {
          clearTimeout(timeout);
        }
      } catch (error) {
        if (isAbortError(error)) {
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "stopped",
                message: "Generation stopped.",
              };
            }
            return newMessages;
          });
          return;
        }
        wpawLog.error("Article generation error:", error);
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: formatAiErrorMessage(error, "Failed to generate article"),
            canRetry: true,
            retryType: "generation",
          },
        ]);
      } finally {
        setIsLoading(false);
        finishAgentOperation(operationType);
      }
    };
    const retryLastGeneration = () => {
      if (!lastGenerationRequestRef.current) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "Cannot retry because the original generation request is no longer available. Please send the request again.",
          },
        ]);
        return;
      }
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "starting",
          message: "Resuming generation...",
          timestamp: new Date(),
        },
      ]);
      streamGeneratePlan(lastGenerationRequestRef.current, { resume: true });
    };
    const retryLastExecute = () => {
      if (!lastExecuteRequestRef.current) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "Cannot retry because the original writing request is no longer available. Please start writing again.",
          },
        ]);
        return;
      }
      executePlanFromCard({ retry: true });
    };
    const retryLastRefinement = () => {
      if (!lastRefineRequestRef.current) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "Cannot retry because the original refinement request is no longer available. Please send the refinement again.",
          },
        ]);
        return;
      }
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "starting",
          message: "Retrying refinement...",
          timestamp: new Date(),
        },
      ]);
      handleChatRefinement(
        lastRefineRequestRef.current.message,
        lastRefineRequestRef.current.blocksOverride,
        lastRefineRequestRef.current.options,
      );
    };
    const retryLastChat = async () => {
      if (!lastChatRequestRef.current) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "Cannot retry because the original chat request is no longer available. Please send the message again.",
          },
        ]);
        return;
      }
      const userMessage = lastChatRequestRef.current.message;

      // Remove the last error message
      setMessages((prev) =>
        prev.filter((m) => !(m.type === "error" && m.retryType === "chat")),
      );
      setIsLoading(true);
      const retryOperationController = beginAgentOperation(
        "chat",
        "chat retry",
      );

      try {
        const chatHistory = messages
          .filter((m) => m.role === "user" || m.role === "assistant")
          .map((m) => ({ role: m.role, content: m.content }));

        const response = await fetch(wpAgenticWriter.apiUrl + "/chat", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-WP-Nonce": wpAgenticWriter.nonce,
          },
          body: JSON.stringify({
            messages: [...chatHistory, { role: "user", content: userMessage }],
            postId: postId,
            sessionId: currentSessionId,
            type: "chat",
            stream: true,
            postConfig: postConfig,
          }),
          signal: retryOperationController.signal,
        });

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || "Failed to chat");
        }

        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();
        let streamBuffer = "";
        let fullContent = "";
        let streamError = null;
        let lastDataTime = Date.now();
        let heartbeatShown = false;

        // Heartbeat: show reassurance if no data for 30s
        const heartbeatInterval = setInterval(() => {
          if (Date.now() - lastDataTime > 30000 && !heartbeatShown) {
            heartbeatShown = true;
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "timeline",
                status: "active",
                message:
                  "⏳ Still waiting for response — the model is processing...",
                timestamp: new Date(),
              },
            ]);
          }
        }, 10000);

        try {
          while (true) {
            if (
              stopExecutionRef.current ||
              retryOperationController.signal.aborted
            ) {
              throw new DOMException("Chat retry stopped", "AbortError");
            }
            const { done, value } = await reader.read();
            if (done) break;
            lastDataTime = Date.now();
            heartbeatShown = false;

            streamBuffer += decoder.decode(value, { stream: true });
            const lines = streamBuffer.split("\n");
            streamBuffer = lines.pop() || "";

            for (const line of lines) {
              if (!line.startsWith("data: ")) continue;
              try {
                const data = JSON.parse(line.slice(6));
                if (data.type === "error") {
                  streamError = new Error(data.message || "Chat error");
                  break;
                }
                if (
                  data.type === "conversational_stream" ||
                  data.type === "conversational"
                ) {
                  fullContent = data.content;
                  setMessages((prev) => {
                    const lastMsg = prev[prev.length - 1];
                    if (
                      lastMsg &&
                      lastMsg.role === "assistant" &&
                      lastMsg.isStreaming
                    ) {
                      return [
                        ...prev.slice(0, -1),
                        { ...lastMsg, content: fullContent },
                      ];
                    }
                    return [
                      ...prev,
                      {
                        role: "assistant",
                        content: fullContent,
                        isStreaming: true,
                      },
                    ];
                  });
                } else if (data.type === "complete") {
                  // Apply provider metadata from completion.
                  applyProviderMetadata(data);

                  setMessages((prev) => {
                    const lastMsg = prev[prev.length - 1];
                    if (lastMsg && lastMsg.role === "assistant") {
                      return [
                        ...prev.slice(0, -1),
                        { ...lastMsg, isStreaming: false },
                      ];
                    }
                    return prev;
                  });
                  // Extract ALL focus keyword suggestions from completed response.
                  if (fullContent) {
                    const suggestions =
                      extractFocusKeywordSuggestions(fullContent);
                    if (suggestions.length > 0) {
                      addFocusKeywordSuggestions(suggestions);
                    }
                  }
                } else if (data.type === "provider" && data.fallback_used) {
                  // Show in-chat provider fallback warning
                  setMessages((prev) => [
                    ...prev,
                    {
                      role: "system",
                      type: "timeline",
                      status: "active",
                      message: `⚠️ ${data.selectedProvider || "Selected provider"} unavailable — using ${data.provider || "fallback"}`,
                      timestamp: new Date(),
                    },
                  ]);
                }
              } catch (e) {
                wpawLog.error("Failed to parse retry streaming data:", line, e);
              }
            }

            if (streamError) {
              throw streamError;
            }
          }
          if (
            stopExecutionRef.current ||
            retryOperationController.signal.aborted
          ) {
            throw new DOMException("Chat retry stopped", "AbortError");
          }
        } finally {
          clearInterval(heartbeatInterval);
        }
      } catch (error) {
        if (
          isAbortError(error) ||
          stopExecutionRef.current ||
          retryOperationController.signal.aborted
        ) {
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "system",
              type: "timeline",
              status: "stopped",
              message: "Chat retry stopped by user.",
              timestamp: new Date(),
            },
          ]);
        } else {
          const errorMsg = formatAiErrorMessage(error, "Failed to chat");
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: errorMsg,
              canRetry: true,
              retryType: "chat",
            },
          ]);
        }
      } finally {
        setIsLoading(false);
        finishAgentOperation("chat");
      }
    };
    const createBlockFromPlan = (action) => {
      const blockType = action.blockType || "core/paragraph";
      const content = action.content || "";

      if (blockType === "core/image") {
        const match = content.match(
          /^!\[(.*?)\]\(([^)\s]+)(?:\s+"[^"]*")?\)\s*$/,
        );
        const alt = match ? match[1] : "";
        const url = match ? match[2] : "";
        return wp.blocks.createBlock("core/image", {
          id: 0,
          url: url,
          alt: alt,
          caption: "",
          sizeSlug: "large",
          linkDestination: "none",
        });
      }

      if (blockType === "core/heading") {
        return wp.blocks.createBlock("core/heading", {
          level: action.level || 2,
          content: content,
        });
      }

      if (blockType === "core/list") {
        const items = content
          .split("\n")
          .map((line) => line.trim())
          .filter(Boolean);
        const listItems = items.map((item) =>
          wp.blocks.createBlock("core/list-item", { content: item }),
        );
        return wp.blocks.createBlock(
          "core/list",
          {
            ordered: action.ordered || false,
            ...(action.start ? { start: parseInt(action.start, 10) } : {}),
          },
          listItems,
        );
      }

      if (blockType === "core/code") {
        return wp.blocks.createBlock("core/code", {
          content: content,
          language: action.language || "text",
        });
      }

      if (blockType === "core/paragraph") {
        if (content.includes("<") && content.includes(">")) {
          // Contains HTML tags, parse it into an array of blocks
          return wp.blocks.rawHandler({ HTML: content });
        }
      }

      return wp.blocks.createBlock(blockType, { content: content });
    };
    const normalizePlanActions = (plan) => {
      if (!plan || !plan.actions) {
        return [];
      }
      if (Array.isArray(plan.actions)) {
        return plan.actions;
      }
      return Object.values(plan.actions);
    };
    const buildPlanPreviewItem = (action, index) => {
      if (!action || !action.action) {
        return { title: "Unknown action" };
      }

      const type = action.blockType
        ? ` (${action.blockType.replace("core/", "")})`
        : "";
      const content = (action.content || "").replace(/\s+/g, " ").trim();
      const contentPreview = content ? content : "";
      const before = getBlockPreviewById(action.blockId);
      const beforePreview = before ? before : "";
      const targetLabel = before
        ? ` "${before.substring(0, 40)}${before.length > 40 ? "..." : ""}"`
        : "";
      const targetPreview = beforePreview || '"Target block not found"';
      const blockId = action.blockId || null;

      switch (action.action) {
        case "keep":
          return { title: "Keep" };
        case "delete":
          return {
            title: `Delete${targetLabel}`,
            blockId,
            viewInEditor: true,
          };
        case "replace":
          return {
            title: `Replace${targetLabel}${type}`,
            blockId,
            viewInEditor: true,
          };
        case "change_type":
          return {
            title: `Change type${targetLabel}${type}`,
            blockId,
            viewInEditor: true,
          };
        case "insert":
          return {
            title: `Insert new block${type}`,
            blockId,
            viewInEditor: true,
          };
        case "insert_before":
          return {
            title: `Insert before${targetLabel}${type}`,
            blockId,
            viewInEditor: true,
          };
        case "insert_after":
          return {
            title: `Insert after${targetLabel}${type}`,
            blockId,
            viewInEditor: true,
          };
        default:
          return { title: `Unknown action: ${action.action}` };
      }
    };
    const normalizePlanSectionTitle = (section) => {
      const heading = (section?.heading || section?.title || "").toString();
      return heading
        .replace(/<[^>]+>/g, "")
        .trim()
        .toLowerCase();
    };
    const upsertSectionBlock = (sectionId, blockId) => {
      if (!sectionId || !blockId) {
        return;
      }

      const sectionMap = sectionBlocksRef.current[sectionId] || [];
      if (!sectionMap.includes(blockId)) {
        sectionBlocksRef.current[sectionId] = [...sectionMap, blockId];
      }
      blockSectionRef.current[blockId] = sectionId;
    };
    const removeSectionBlock = (sectionId, blockId) => {
      if (!sectionId || !blockId) {
        return;
      }
      const sectionMap = sectionBlocksRef.current[sectionId] || [];
      sectionBlocksRef.current[sectionId] = sectionMap.filter(
        (id) => id !== blockId,
      );
      delete blockSectionRef.current[blockId];
    };
    const loadSectionBlocks = async () => {
      if (!postId) {
        return;
      }
      try {
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/section-blocks/${postId}`,
          {
            method: "GET",
            headers: {
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
          },
        );

        if (!response.ok) {
          return;
        }

        const data = await response.json();
        if (
          data &&
          data.sectionBlocks &&
          typeof data.sectionBlocks === "object"
        ) {
          sectionBlocksRef.current = data.sectionBlocks;
          blockSectionRef.current = {};
          Object.entries(data.sectionBlocks).forEach(
            ([sectionId, blockIds]) => {
              if (Array.isArray(blockIds)) {
                blockIds.forEach((blockId) => {
                  blockSectionRef.current[blockId] = sectionId;
                });
              }
            },
          );
        }
      } catch (error) {
        // Ignore load failures for section mapping.
      }
    };
    const saveSectionBlocks = async (sectionId) => {
      if (!sectionId || !postId) {
        return;
      }
      const blockIds = sectionBlocksRef.current[sectionId] || [];
      try {
        await fetch(`${wpAgenticWriter.apiUrl}/section-blocks`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-WP-Nonce": wpAgenticWriter.nonce,
          },
          body: JSON.stringify({
            postId: postId,
            sessionId: currentSessionId,
            sectionId: sectionId,
            blockIds: blockIds,
          }),
        });
      } catch (error) {
        // Ignore save failures for section mapping.
      }
    };
    const ensurePlanTasks = (plan) => {
      if (!plan || !Array.isArray(plan.sections)) {
        return plan;
      }

      const nextSections = plan.sections.map((section, index) => {
        const id = section?.id || `section-${index + 1}`;
        const status = section?.status || "pending";
        return { ...section, id, status };
      });

      return { ...plan, sections: nextSections };
    };
    const getTargetedRefinementBlocks = (message) => {
      if (!message) {
        return null;
      }
      const codeKeywords = /(kode|coding|code|script|snippet|skrip)/i;
      if (!codeKeywords.test(message)) {
        return null;
      }
      const allBlocks = select("core/block-editor").getBlocks();
      const codeBlocks = allBlocks.filter(
        (block) => block.name === "core/code",
      );
      if (codeBlocks.length === 0) {
        return null;
      }
      const affectedSections = new Set();
      codeBlocks.forEach((block) => {
        const sectionId = blockSectionRef.current[block.clientId];
        if (sectionId) {
          affectedSections.add(sectionId);
        }
      });
      if (affectedSections.size === 0) {
        return null;
      }
      const targetIds = [];
      affectedSections.forEach((sectionId) => {
        const blockIds = sectionBlocksRef.current[sectionId] || [];
        blockIds.forEach((blockId) => {
          targetIds.push(blockId);
        });
      });
      return [...new Set(targetIds)];
    };
    const findBestPlanSectionMatch = (message) => {
      const plan = currentPlanRef.current;
      if (!plan || !Array.isArray(plan.sections) || !message) {
        return null;
      }

      const stopwords = new Set([
        "dalam",
        "poin",
        "bagian",
        "yang",
        "dan",
        "atau",
        "untuk",
        "dengan",
        "ada",
        "tidak",
        "lebih",
        "ini",
        "itu",
        "seperti",
        "agar",
        "akan",
        "jadi",
        "fokus",
        "tulis",
        "ulang",
        "hapus",
        "tambahkan",
        "pembahasan",
        "pada",
        "berikan",
        "gunakan",
        "jelaskan",
        "buat",
      ]);
      const tokens = message
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, " ")
        .split(/\s+/)
        .filter((token) => token.length > 3 && !stopwords.has(token));

      if (tokens.length === 0) {
        return null;
      }

      let best = null;
      let bestScore = 0;

      plan.sections.forEach((section) => {
        const sectionText = [
          section?.heading,
          section?.title,
          section?.description,
          Array.isArray(section?.content) && section.content.length > 0
            ? section.content[0]?.content
            : "",
        ]
          .filter(Boolean)
          .join(" ")
          .toLowerCase();

        if (!sectionText) {
          return;
        }

        let score = 0;
        tokens.forEach((token) => {
          if (sectionText.includes(token)) {
            score += 1;
          }
        });

        if (score > bestScore) {
          bestScore = score;
          best = section;
        }
      });

      if (!best || bestScore < 2) {
        return null;
      }

      return best;
    };
    const updatePlanSectionStatus = (sectionId, status) => {
      if (!sectionId) {
        return;
      }
      setMessages((prev) => {
        const newMessages = [...prev];
        for (let i = newMessages.length - 1; i >= 0; i--) {
          if (newMessages[i].type === "plan" && newMessages[i].plan?.sections) {
            const sections = newMessages[i].plan.sections.map((section) => {
              if (section.id === sectionId) {
                return { ...section, status: status };
              }
              return section;
            });
            const plan = { ...newMessages[i].plan, sections };
            newMessages[i] = { ...newMessages[i], plan };
            currentPlanRef.current = plan;
            break;
          }
        }
        return newMessages;
      });
    };
    const findSectionInsertIndex = (plan, sectionId) => {
      const allBlocks = select("core/block-editor").getBlocks();
      if (!plan || !Array.isArray(plan.sections) || !sectionId) {
        return allBlocks.length;
      }

      const sections = plan.sections;
      const sectionIndex = sections.findIndex(
        (section) => section.id === sectionId,
      );
      if (sectionIndex === -1) {
        return allBlocks.length;
      }

      for (let i = sectionIndex + 1; i < sections.length; i++) {
        const nextSection = sections[i];
        const nextStatus = nextSection?.status || "pending";
        if (nextStatus !== "done") {
          continue;
        }
        const nextHeading = normalizePlanSectionTitle(nextSection);
        if (!nextHeading) {
          continue;
        }
        const anchorIndex = allBlocks.findIndex((block) => {
          if (block.name !== "core/heading") {
            return false;
          }
          const content = normalizePlanSectionTitle({
            heading: block.attributes?.content,
          });
          return content === nextHeading;
        });
        if (anchorIndex !== -1) {
          return anchorIndex;
        }
      }

      return allBlocks.length;
    };

    // Check if Writing mode needs empty state
    const shouldShowWritingEmptyState = () => {
      if (agentMode !== "writing") return false;
      if (currentPlanRef.current) return false;

      // Check if editor has content blocks
      const allBlocks = select("core/block-editor").getBlocks();
      const hasContent = allBlocks.length > 0;

      // Only show empty state if no plan AND no content in editor
      return !hasContent;
    };

    // Summarize chat history for token optimization
    const summarizeChatHistory = async () => {
      const chatMessages = messages.filter((m) => m.role !== "system");

      if (chatMessages.length < 4) {
        return { summary: "", useFullHistory: true, cost: 0 };
      }

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/summarize-context",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              chatHistory: chatMessages,
              postId: postId,
              sessionId: currentSessionId,
            }),
          },
        );

        if (!response.ok) {
          throw new Error("Summarization failed");
        }

        const data = await response.json();
        applyProviderMetadata(data);

        if (data.tokens_saved > 0) {
          wpawLog.log(
            `Context optimized: ~${data.tokens_saved} tokens saved (~$${(data.tokens_saved * 0.0000002).toFixed(4)})`,
          );
        }

        return {
          summary: data.summary || "",
          useFullHistory: data.use_full_history || false,
          cost: data.cost || 0,
          tokensSaved: data.tokens_saved || 0,
        };
      } catch (error) {
        wpawLog.error("Summarization error:", error);
        return { summary: "", useFullHistory: true, cost: 0 };
      }
    };

    // Detect user intent for contextual actions
    const detectUserIntent = async (lastMessage) => {
      if (!lastMessage || lastMessage.trim().length === 0) {
        return { intent: "continue_chat", cost: 0 };
      }

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/detect-intent",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              lastMessage: lastMessage,
              hasPlan: Boolean(currentPlanRef.current),
              currentMode: agentMode,
              postId: postId,
              sessionId: currentSessionId,
            }),
          },
        );

        if (!response.ok) {
          let message = "Intent detection failed";
          try {
            const error = await response.json();
            message = error?.message || message;
          } catch (parseError) {
            // Keep the fallback message if the error response is not JSON.
          }
          throw new Error(message);
        }

        const data = await response.json();
        applyProviderMetadata(data);
        return {
          intent: data.intent || "continue_chat",
          cost: data.cost || 0,
        };
      } catch (error) {
        wpawLog.error(
          "Intent detection error:",
          formatAiErrorMessage(error, "Intent detection failed"),
        );
        return { intent: "continue_chat", cost: 0 };
      }
    };

    // Build optimized context (full or summarized)
    const buildOptimizedContext = async () => {
      const result = await summarizeChatHistory();

      if (result.useFullHistory) {
        return {
          type: "full",
          messages: messages.filter((m) => m.role !== "system"),
          cost: 0,
        };
      }

      return {
        type: "summary",
        summary: result.summary,
        cost: result.cost,
        tokensSaved: result.tokensSaved,
      };
    };

    // Handle reset/clear command
    const handleResetCommand = async () => {
      if (!confirm("Clear all conversation history? This cannot be undone.")) {
        return;
      }

      try {
        // Clear frontend state
        setMessages([]);
        currentPlanRef.current = null;

        // Clear backend chat history
        await fetch(wpAgenticWriter.apiUrl + "/clear-context", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-WP-Nonce": wpAgenticWriter.nonce,
          },
          body: JSON.stringify({ postId: postId }),
        });

        setMessages([
          {
            role: "system",
            type: "info",
            content: "✅ Context cleared. Starting fresh conversation.",
          },
        ]);
      } catch (error) {
        wpawLog.error("Reset error:", error);
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Failed to clear context. Please try again.",
          },
        ]);
      }
    };

    const updateOrCreatePlanMessage = (plan, options = {}) => {
      const { append = false, suggestKeywords = agentMode === "planning" } =
        options;
      const normalizedPlan = ensurePlanTasks(plan);
      currentPlanRef.current = normalizedPlan;
      setMessages((prev) => {
        const newMessages = [...prev];
        if (!append) {
          for (let i = newMessages.length - 1; i >= 0; i--) {
            if (newMessages[i].type === "plan") {
              newMessages[i] = { ...newMessages[i], plan: normalizedPlan };
              return newMessages;
            }
          }
        }
        newMessages.push({
          role: "assistant",
          type: "plan",
          plan: normalizedPlan,
        });
        return newMessages;
      });

      // Auto-suggest keywords after outline is generated
      if (suggestKeywords && normalizedPlan) {
        suggestKeywordsFromPlan(normalizedPlan);
      }
    };

    const suggestKeywordsFromPlan = async (plan) => {
      if (!plan || !plan.title || !plan.sections) {
        return;
      }

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/suggest-keywords",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              postId: postId,
              sessionId: currentSessionId,
              title: plan.title,
              sections: plan.sections,
            }),
          },
        );

        if (!response.ok) {
          throw new Error("Failed to suggest keywords");
        }

        const data = await response.json();

        // Update post config with suggested keywords
        if (data.focus_keyword) {
          updatePostConfig("seo_focus_keyword", data.focus_keyword);
        }
        if (data.secondary_keywords && Array.isArray(data.secondary_keywords)) {
          updatePostConfig(
            "seo_secondary_keywords",
            data.secondary_keywords.join(", "),
          );
        }

        // Track cost and apply provider metadata
        if (data.cost) {
          setCost({ ...cost, session: cost.session + data.cost });
        }
        applyProviderMetadata(data);

        // Add assistant message about keyword suggestions
        setMessages((prev) => [
          ...prev,
          {
            role: "assistant",
            content: `🎯 **SEO Keywords Suggested:**\n\n**Focus Keyword:** ${data.focus_keyword}\n\n**Secondary Keywords:** ${data.secondary_keywords.join(", ")}\n\n${data.reasoning || ""}\n\nYou can review and edit these in the Config panel before writing.`,
          },
        ]);
      } catch (error) {
        wpawLog.error("Keyword suggestion error:", error);
        // Silently fail - don't interrupt the workflow
      }
    };

    const buildChatHistoryPayload = React.useCallback(() => {
      return messages
        .filter(
          (m) =>
            (m.role === "user" || m.role === "assistant") &&
            typeof m.content === "string" &&
            m.content.trim(),
        )
        .filter((m) => m.type !== "plan")
        .map((m) => ({
          role: m.role,
          content: m.content.trim().slice(0, 2000),
        }))
        .slice(-10);
    }, [messages]);

    const getLastUserMessageText = React.useCallback(() => {
      for (let i = messages.length - 1; i >= 0; i -= 1) {
        const m = messages[i];
        if (
          m?.role === "user" &&
          typeof m.content === "string" &&
          m.content.trim()
        ) {
          return m.content.trim();
        }
      }
      return "";
    }, [messages]);

    const shouldSkipPlanningCompletion = (content) => {
      if (agentMode !== "planning") {
        return false;
      }

      const text = String(content || "").toLowerCase();
      return (
        text.includes("article generation complete") ||
        text.includes("content has been added to your editor") ||
        text.includes("article generated successfully")
      );
    };
    const getPlanRuntimeSummary = (plan = currentPlanRef.current) => {
      const sections = Array.isArray(plan?.sections) ? plan.sections : [];
      const done = sections.filter(
        (section) => section.status === "done",
      ).length;
      const inProgress = sections.filter(
        (section) => section.status === "in_progress",
      ).length;
      const pending = Math.max(0, sections.length - done);
      return {
        total: sections.length,
        done,
        inProgress,
        pending,
        label:
          sections.length > 0
            ? `${done}/${sections.length} written`
            : "No outline",
      };
    };
    const getPlanId = (plan = currentPlanRef.current) => {
      return plan?.id || plan?.meta?.id || plan?.title || "";
    };
    const classifyAgentIntent = (message) => {
      const text = String(message || "").toLowerCase();
      const outlinePattern =
        /\b(?:out?line|plan|structure|kerangka|rencana)(?:\s*[- ]?\s*(?:nya|kan))?\b/i;
      if (
        /@[a-z0-9-]/i.test(message) ||
        hasTitleMention(extractMentionsFromText(message))
      ) {
        return "targeted_refinement";
      }
      if (
        /\b(meta description|meta title|seo audit|seo score|keyword density|schema|faq)\b/i.test(
          text,
        )
      ) {
        if (/\b(meta description|description)\b/i.test(text)) {
          return "generate_meta";
        }
        return "seo_audit";
      }
      if (
        /\b(continue|resume|start writing|write article|write it|generate article|lanjut|tulis artikel|buat artikel)\b/i.test(
          text,
        )
      ) {
        return "write";
      }
      if (outlinePattern.test(text)) {
        return "outline";
      }
      if (
        /\b(refine|rewrite|improve|polish|fix|ai-ish|aiish|slop|humanize|natural|tone|clarity|rapikan|perbaiki)\b/i.test(
          text,
        )
      ) {
        return "refine";
      }
      return "chat";
    };
    const decideAgentAction = (message) => {
      const intent = classifyAgentIntent(message);
      const planSummary = getPlanRuntimeSummary();
      const refineableBlocks = getRefineableBlocks();
      const hasContent = refineableBlocks.length > 0;
      const hasPlan = Boolean(currentPlanRef.current && planSummary.total > 0);

      if (intent === "generate_meta") {
        return {
          action: "generate_meta",
          mode: "seo",
          reason: "SEO meta request",
        };
      }
      if (intent === "seo_audit") {
        return {
          action: "seo_audit",
          mode: "seo",
          reason: "SEO analysis request",
        };
      }
      if (intent === "targeted_refinement") {
        return {
          action: "targeted_refinement",
          mode: "refinement",
          reason: "Block mention detected",
        };
      }
      if (intent === "write" && hasPlan && planSummary.pending > 0) {
        return {
          action: "execute_plan",
          mode: "writing",
          reason: "Outline has pending sections",
        };
      }
      if ((intent === "write" || intent === "outline") && !hasContent) {
        return {
          action: "create_outline",
          mode: "planning",
          reason: "Fresh post needs outline first",
        };
      }
      if (intent === "outline") {
        return {
          action: hasPlan ? "revise_outline" : "create_outline",
          mode: "planning",
          reason: hasPlan
            ? "Existing outline can be revised"
            : "Outline requested",
        };
      }
      if (intent === "refine" && hasContent) {
        return {
          action: "article_refinement",
          mode: "refinement",
          reason: "Content refinement requested",
        };
      }
      return { action: "chat", mode: "chat", reason: "Conversation" };
    };
    const executePlanFromCard = async (options = {}) => {
      if (isLoading) {
        return;
      }

      // Check if plan exists
      if (!currentPlanRef.current) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "No outline found yet. Ask the agent for an outline first, then it can continue into writing.",
          },
        ]);
        setIsLoading(false);
        return;
      }

      const plan = currentPlanRef.current;

      // Confirmation: warn if editor already has content blocks
      const existingBlocks = select("core/block-editor").getBlocks();
      const hasExistingContent = existingBlocks.some(
        (b) =>
          b.name !== "core/paragraph" ||
          (b.attributes?.content && b.attributes.content.trim().length > 0),
      );
      if (hasExistingContent && !options.skipConfirm) {
        const pendingSections = Array.isArray(plan?.sections)
          ? plan.sections.filter((section) => section.status !== "done").length
          : 0;
        const confirmed = window.confirm(
          `This will write ${pendingSections} sections into the editor. Existing content will be preserved below the new content.\n\nContinue?`,
        );
        if (!confirmed) {
          return;
        }
      }

      setAgentMode("writing");
      const pendingCount = Array.isArray(plan?.sections)
        ? plan.sections.filter((section) => section.status !== "done").length
        : null;
      if (pendingCount === 0) {
        setMessages((prev) => [
          ...deactivateActiveTimelineEntries(prev),
          {
            role: "system",
            type: "timeline",
            status: "complete",
            message: "All outline items are already written.",
            timestamp: new Date(),
          },
        ]);
        persistWritingStatePatch({
          status: "completed",
          current_section_index: Array.isArray(plan?.sections)
            ? plan.sections.length
            : 0,
          sections_written: Array.isArray(plan?.sections)
            ? plan.sections
                .map((section) => section.id || section.heading || "")
                .filter(Boolean)
            : [],
          plan_id: getPlanId(plan),
          resume_token: "",
        });
        setAgentMode("chat");
        return;
      }

      const { retry = false } = options;
      lastExecuteRequestRef.current = {
        postId: postId,
        sessionId: currentSessionId,
        stream: true,
        postConfig: postConfig,
        detectedLanguage: detectedLanguage,
        chatHistory: messages.filter((m) => m.role !== "system"),
      };

      // Reset stop flag
      stopExecutionRef.current = false;
      setExecutionStopped(false);
      const operationController = beginAgentOperation("writing", "writing");

      setIsLoading(true);
      persistWritingStatePatch({
        status: "in_progress",
        current_section_index: 0,
        sections_written: retry ? writingState.sections_written : [],
        plan_id: getPlanId(plan),
        resume_token: "",
      });
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "writing",
          message: retry ? "Retrying outline..." : "Writing from outline...",
          timestamp: new Date(),
        },
      ]);
      sectionInsertIndexRef.current = {};
      activeSectionIdRef.current = null;

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/execute-article",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify(lastExecuteRequestRef.current),
            signal: operationController.signal,
          },
        );

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || "Failed to execute outline");
        }

        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();
        let streamBuffer = "";
        const timeout = setTimeout(() => {
          if (isLoading) {
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content:
                  "Request timeout. The AI is taking too long to respond. Please try again.",
              },
            ]);
            persistWritingStatePatch({
              status: "failed",
              plan_id: getPlanId(currentPlanRef.current),
              resume_token: activeSectionIdRef.current || "",
            });
            setIsLoading(false);
            reader.cancel();
          }
        }, 120000);

        while (true) {
          // Check if execution should stop
          if (stopExecutionRef.current || operationController.signal.aborted) {
            await reader.cancel().catch(() => {});
            clearTimeout(timeout);
            setExecutionStopped(true);
            setIsLoading(false);

            // Calculate completed sections
            const plan = currentPlanRef.current;
            const completedCount =
              plan?.sections?.filter((s) => s.status === "done").length || 0;
            const totalCount = plan?.sections?.length || 0;
            const pendingCount = totalCount - completedCount;
            persistWritingStatePatch({
              status: "paused",
              current_section_index: completedCount,
              sections_written:
                plan?.sections
                  ?.filter((s) => s.status === "done")
                  .map((s) => s.id || s.heading || "")
                  .filter(Boolean) || [],
              plan_id: getPlanId(plan),
              resume_token: activeSectionIdRef.current || "",
            });

            setMessages((prev) => [
              ...deactivateActiveTimelineEntries(prev),
              {
                role: "system",
                type: "timeline",
                status: "stopped",
                message: `⏸️ Execution stopped (${completedCount}/${totalCount} sections completed)`,
                timestamp: new Date(),
              },
              {
                role: "assistant",
                content: `**Execution Paused**\n\n✅ Completed: ${completedCount} section${completedCount !== 1 ? "s" : ""}\n⏳ Pending: ${pendingCount} section${pendingCount !== 1 ? "s" : ""}\n\nYour generated content has been preserved in the editor.`,
                showResumeActions: true,
                pendingCount: pendingCount,
              },
            ]);
            break;
          }

          const { done, value } = await reader.read();
          if (done) break;

          streamBuffer += decoder.decode(value, { stream: true });
          const lines = streamBuffer.split("\n");
          streamBuffer = lines.pop() || "";

          for (const line of lines) {
            if (!line.startsWith("data: ")) {
              continue;
            }

            try {
              const data = JSON.parse(line.slice(6));
              if (data.type === "title_update") {
                dispatch("core/editor").editPost({ title: data.title });
              } else if (data.type === "section_start") {
                activeSectionIdRef.current = data.sectionId || null;
                const insertIndex = findSectionInsertIndex(
                  currentPlanRef.current,
                  data.sectionId,
                );
                if (data.sectionId) {
                  sectionInsertIndexRef.current[data.sectionId] = insertIndex;
                  sectionBlocksRef.current[data.sectionId] =
                    sectionBlocksRef.current[data.sectionId] || [];
                }
                updatePlanSectionStatus(data.sectionId, "in_progress");
                persistWritingStatePatch({
                  status: "in_progress",
                  current_section_index: Number(data.index || 0),
                  plan_id: getPlanId(currentPlanRef.current),
                  resume_token: data.sectionId || "",
                });
              } else if (data.type === "status") {
                if (data.status === "complete") {
                  continue;
                }
                setMessages((prev) => {
                  const newMessages = [...prev];
                  const lastTimelineIndex =
                    findLastActiveTimelineIndex(newMessages);
                  if (lastTimelineIndex !== -1) {
                    newMessages[lastTimelineIndex] = {
                      ...newMessages[lastTimelineIndex],
                      status: data.status,
                      message: data.message,
                      icon: data.icon,
                    };
                  }
                  return newMessages;
                });
              } else if (data.type === "block") {
                const { insertBlocks } = dispatch("core/block-editor");
                const newBlock = createBlocksFromSerialized(data.block);
                if (newBlock) {
                  const sectionId =
                    data.sectionId || activeSectionIdRef.current;
                  const insertIndex = sectionId
                    ? sectionInsertIndexRef.current[sectionId]
                    : undefined;
                  if (typeof insertIndex === "number") {
                    insertBlocks(newBlock, insertIndex);
                    sectionInsertIndexRef.current[sectionId] = insertIndex + 1;
                  } else {
                    insertBlocks(newBlock);
                  }
                  if (sectionId) {
                    upsertSectionBlock(sectionId, newBlock.clientId);
                  }
                }
              } else if (data.type === "section_complete") {
                updatePlanSectionStatus(data.sectionId, "done");
                saveSectionBlocks(data.sectionId);
                const writtenSectionIds = Array.isArray(
                  currentPlanRef.current?.sections,
                )
                  ? currentPlanRef.current.sections
                      .filter((section) => section.status === "done")
                      .map((section) => section.id || section.heading || "")
                      .filter(Boolean)
                  : [
                      ...new Set(
                        [
                          ...(writingState.sections_written || []),
                          data.sectionId,
                        ].filter(Boolean),
                      ),
                    ];
                persistWritingStatePatch({
                  status: "in_progress",
                  current_section_index: writtenSectionIds.length,
                  sections_written: writtenSectionIds,
                  plan_id: getPlanId(currentPlanRef.current),
                  resume_token: "",
                });
                // Check if execution should stop after section completes
                if (stopExecutionRef.current) {
                  await reader.cancel().catch(() => {});
                  clearTimeout(timeout);
                  setExecutionStopped(true);
                  setIsLoading(false);
                  persistWritingStatePatch({
                    status: "paused",
                    current_section_index: writtenSectionIds.length,
                    sections_written: writtenSectionIds,
                    plan_id: getPlanId(currentPlanRef.current),
                    resume_token: data.sectionId || "",
                  });
                  setMessages((prev) => [
                    ...deactivateActiveTimelineEntries(prev),
                    {
                      role: "system",
                      type: "timeline",
                      status: "stopped",
                      message: "⏸️ Execution stopped by user",
                      timestamp: new Date(),
                    },
                  ]);
                  break;
                }
              } else if (data.type === "assistant_message") {
                // Add assistant message to chat
                setMessages((prev) => [
                  ...prev,
                  { role: "assistant", content: data.message },
                ]);
              } else if (data.type === "complete") {
                clearTimeout(timeout);
                if (data.totalCost) {
                  setCost({ ...cost, session: cost.session + data.totalCost });
                }
                applyProviderMetadata(data);
                setMessages((prev) => {
                  const newMessages = [...prev];
                  const lastTimelineIndex =
                    findLastActiveTimelineIndex(newMessages);
                  if (lastTimelineIndex !== -1) {
                    newMessages[lastTimelineIndex] = {
                      ...newMessages[lastTimelineIndex],
                      status: "complete",
                      message: "Article generated successfully!",
                      completedAt: new Date(),
                    };
                  }
                  return newMessages;
                });
                setAgentMode("chat");
                persistWritingStatePatch({
                  status: "completed",
                  current_section_index: Array.isArray(
                    currentPlanRef.current?.sections,
                  )
                    ? currentPlanRef.current.sections.length
                    : writingState.current_section_index,
                  sections_written: Array.isArray(
                    currentPlanRef.current?.sections,
                  )
                    ? currentPlanRef.current.sections
                        .map((section) => section.id || section.heading || "")
                        .filter(Boolean)
                    : writingState.sections_written,
                  plan_id: getPlanId(currentPlanRef.current),
                  resume_token: "",
                });
                setIsLoading(false);
              } else if (data.type === "error") {
                clearTimeout(timeout);
                throw new Error(data.message || "Failed to execute outline");
              }
            } catch (parseError) {
              wpawLog.error(
                "Failed to parse streaming data:",
                line,
                parseError,
              );
            }
          }
        }
        clearTimeout(timeout);
        // If stream ended without a 'complete' event, deactivate lingering timeline entries
        setMessages((prev) => {
          const hasActive = prev.some(
            (m) =>
              m.type === "timeline" &&
              m.status &&
              !["complete", "inactive", "stopped"].includes(m.status),
          );
          if (hasActive) {
            return deactivateActiveTimelineEntries(prev);
          }
          return prev;
        });
      } catch (error) {
        if (isAbortError(error) || stopExecutionRef.current) {
          const plan = currentPlanRef.current;
          const completedCount =
            plan?.sections?.filter((s) => s.status === "done").length || 0;
          const totalCount = plan?.sections?.length || 0;
          const pendingCount = totalCount - completedCount;
          persistWritingStatePatch({
            status: "paused",
            current_section_index: completedCount,
            sections_written:
              plan?.sections
                ?.filter((s) => s.status === "done")
                .map((s) => s.id || s.heading || "")
                .filter(Boolean) || [],
            plan_id: getPlanId(plan),
            resume_token: activeSectionIdRef.current || "",
          });
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "system",
              type: "timeline",
              status: "stopped",
              message: `Execution stopped (${completedCount}/${totalCount} sections completed)`,
              timestamp: new Date(),
            },
            {
              role: "assistant",
              content: `**Execution Paused**\n\nCompleted: ${completedCount} section${completedCount !== 1 ? "s" : ""}\nPending: ${pendingCount} section${pendingCount !== 1 ? "s" : ""}\n\nYour generated content has been preserved in the editor.`,
              showResumeActions: true,
              pendingCount: pendingCount,
            },
          ]);
          return;
        }
        setAgentMode(currentPlanRef.current ? "planning" : "chat");
        persistWritingStatePatch({
          status: "failed",
          plan_id: getPlanId(currentPlanRef.current),
          resume_token: activeSectionIdRef.current || "",
        });
        setMessages((prev) => [
          ...deactivateActiveTimelineEntries(prev),
          {
            role: "system",
            type: "error",
            content: formatAiErrorMessage(error, "Failed to execute outline"),
            canRetry: true,
            retryType: "execute",
          },
        ]);
      } finally {
        setIsLoading(false);
        finishAgentOperation("writing");
      }
    };

    const handleStopExecution = () => {
      if (!isLoading && !isSeoAuditing && !isGeneratingMeta) return;

      stopExecutionRef.current = true;
      setExecutionStopped(true);
      markActiveOperationStopping();

      if (activeOperationRef.current?.type === "writing") {
        persistWritingStatePatch({
          status: "paused",
          plan_id: getPlanId(currentPlanRef.current),
          resume_token: activeSectionIdRef.current || "",
        });
      }

      if (activeReaderRef.current) {
        activeReaderRef.current.cancel().catch(() => {});
      }
      if (
        activeAbortControllerRef.current &&
        !activeAbortControllerRef.current.signal.aborted
      ) {
        activeAbortControllerRef.current.abort();
      }
    };

    const clearChatContext = async () => {
      if (isLoading) {
        return;
      }

      const confirmMessage =
        "Start a new agent session for this post? The current session will stay available in Sessions.";
      if (!window.confirm(confirmMessage)) {
        return;
      }

      try {
        setIsSessionActionLoading(true);
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/conversations",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ post_id: postId }),
          },
        );
        if (!response.ok) {
          throw new Error("Failed to create a new conversation");
        }
        const data = await response.json();
        if (data?.session_id) {
          setCurrentSessionId(data.session_id);
        }
        await loadPostSessions();
        setMessages([]);
        setInClarification(false);
        setQuestions([]);
        setCurrentQuestionIndex(0);
        setAnswers([]);
        setPendingRefinement(null);
        setPendingEditPlan(null);
        streamTargetRef.current = null;
      } catch (error) {
        if (isAbortError(error) || stopExecutionRef.current) {
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "stopped",
                message: "Refinement stopped by user.",
              };
            }
            return newMessages;
          });
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content:
                "Refinement stopped. Already-applied block changes remain in the editor and can be undone from the top bar.",
            },
          ]);
          return;
        }
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: Failed to start a new conversation.",
          },
        ]);
      } finally {
        setIsSessionActionLoading(false);
      }
    };
    const createBlocksFromSerialized = (block) => {
      if (!block || !block.blockName) {
        return null;
      }

      const attrs = { ...(block.attrs || {}) };

      // Handle code blocks
      if (
        block.blockName === "core/code" &&
        !attrs.content &&
        block.innerHTML
      ) {
        const match = block.innerHTML.match(/<code>([\s\S]*?)<\/code>/i);
        if (match && match[1]) {
          attrs.content = match[1]
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">")
            .replace(/&amp;/g, "&")
            .replace(/&quot;/g, '"');
        }
      }

      // Handle table blocks - extract head and body from innerHTML
      if (block.blockName === "core/table" && block.innerHTML) {
        const headMatch = block.innerHTML.match(/<thead>([\s\S]*?)<\/thead>/i);
        const bodyMatch = block.innerHTML.match(/<tbody>([\s\S]*?)<\/tbody>/i);
        if (headMatch || bodyMatch) {
          attrs.head = [];
          attrs.body = [];

          // Parse thead rows
          if (headMatch) {
            const headRows = headMatch[1].match(/<tr>([\s\S]*?)<\/tr>/gi) || [];
            headRows.forEach((row) => {
              const cells = [];
              const cellMatches =
                row.match(/<t[hd]>([\s\S]*?)<\/t[hd]>/gi) || [];
              cellMatches.forEach((cell) => {
                const content = cell.replace(/<\/?t[hd]>/gi, "");
                cells.push({ content, tag: "th" });
              });
              if (cells.length > 0) attrs.head.push({ cells });
            });
          }

          // Parse tbody rows
          if (bodyMatch) {
            const bodyRows = bodyMatch[1].match(/<tr>([\s\S]*?)<\/tr>/gi) || [];
            bodyRows.forEach((row) => {
              const cells = [];
              const cellMatches = row.match(/<td>([\s\S]*?)<\/td>/gi) || [];
              cellMatches.forEach((cell) => {
                const content = cell.replace(/<\/?td>/gi, "");
                cells.push({ content, tag: "td" });
              });
              if (cells.length > 0) attrs.body.push({ cells });
            });
          }
        }
      }

      // Handle button blocks from [CTA:...] syntax
      if (
        block.blockName === "core/buttons" ||
        block.blockName === "core/button"
      ) {
        if (block.blockName === "core/button") {
          return wp.blocks.createBlock("core/buttons", {}, [
            wp.blocks.createBlock("core/button", attrs),
          ]);
        }
      }

      if (block.innerBlocks && block.innerBlocks.length > 0) {
        const innerBlocks = block.innerBlocks
          .map((innerBlock) => createBlocksFromSerialized(innerBlock))
          .filter(Boolean);
        return wp.blocks.createBlock(block.blockName, attrs, innerBlocks);
      }

      return wp.blocks.createBlock(block.blockName, attrs);
    };
    const reformatBlocks = async (blocksToReformat, originalMessage) => {
      if (isLoading) {
        return;
      }

      if (!blocksToReformat || blocksToReformat.length === 0) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "No blocks found to reformat.",
          },
        ]);
        return;
      }

      setIsLoading(true);
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "refining",
          message: `Reformatting ${blocksToReformat.length} block(s)...`,
          timestamp: new Date(),
        },
      ]);

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/reformat-blocks",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              blocks: blocksToReformat,
              postId: postId,
              sessionId: currentSessionId,
            }),
          },
        );

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || "Failed to reformat blocks");
        }

        const data = await response.json();
        applyProviderMetadata(data);
        const results = data.results || [];
        const { replaceBlocks } = dispatch("core/block-editor");
        const currentTitle =
          select("core/editor").getEditedPostAttribute("title") || "";

        results.forEach((result) => {
          const newBlocks = (result.blocks || [])
            .map(createBlocksFromSerialized)
            .filter(Boolean);
          if (newBlocks.length > 0) {
            replaceBlocks(result.clientId, newBlocks);
          }
        });

        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "timeline",
            status: "complete",
            message: `Reformatted ${results.length} block(s).`,
            timestamp: new Date(),
            completedAt: new Date(),
          },
        ]);
        if (data.recommended_title) {
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content: `Suggested title: ${data.recommended_title}`,
            },
          ]);
          if (data.title_updated || !currentTitle) {
            dispatch("core/editor").editPost({ title: data.recommended_title });
          }
        }
      } catch (error) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: " + (error.message || "Failed to reformat blocks"),
          },
        ]);
      } finally {
        setIsLoading(false);
      }
    };
    const revisePlanFromPrompt = async (instruction) => {
      if (isLoading) {
        return;
      }
      const existingPlan = currentPlanRef.current;
      if (!existingPlan) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "No outline found to revise. Generate an outline first.",
          },
        ]);
        return;
      }

      setIsLoading(true);
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "planning",
          message: "Updating outline...",
          timestamp: new Date(),
        },
      ]);

      try {
        const response = await fetch(wpAgenticWriter.apiUrl + "/revise-plan", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-WP-Nonce": wpAgenticWriter.nonce,
          },
          body: JSON.stringify({
            instruction: instruction,
            plan: existingPlan,
            postId: postId,
            sessionId: currentSessionId,
            postConfig: postConfig,
          }),
        });

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || "Failed to revise outline");
        }

        const data = await response.json();
        if (data.plan) {
          updateOrCreatePlanMessage(data.plan, { append: true });
        }

        if (data.cost) {
          setCost({ ...cost, session: cost.session + data.cost });
        }
        applyProviderMetadata(data);

        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "complete",
              message: "Outline updated.",
              completedAt: new Date(),
            };
          }
          return newMessages;
        });
      } catch (error) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: " + (error.message || "Failed to revise outline"),
          },
        ]);
      } finally {
        setIsLoading(false);
      }
    };
    const applyEditPlan = (plan) => {
      const actions = normalizePlanActions(plan);
      if (actions.length === 0) {
        setPendingEditPlan(null);
        return;
      }

      const {
        replaceBlocks,
        insertBlocks,
        removeBlocks,
        updateBlockAttributes,
      } = dispatch("core/block-editor");

      // Remove temporary green diff blocks BEFORE applying changes
      if (pendingDiffBlockIds.length > 0) {
        removeBlocks(pendingDiffBlockIds);
        setPendingDiffBlockIds([]);
      }

      // Capture snapshot before applying changes
      pushUndoSnapshot("Apply Edit Plan");
      const allBlocks = select("core/block-editor").getBlocks();
      const baseIndexById = new Map(
        allBlocks.map((block, index) => [block.clientId, index]),
      );
      const insertOffsets = {};
      const existingIds = new Set(allBlocks.map((block) => block.clientId));

      actions.forEach((action) => {
        if (action.action === "keep") {
          return;
        }
        if (action.blockId && !existingIds.has(action.blockId)) {
          return;
        }

        if (action.action === "delete" && action.blockId) {
          removeBlocks(action.blockId);
          return;
        }

        if (action.action === "change_type" && action.blockId) {
          const newBlock = [].concat(createBlockFromPlan(action));
          replaceBlocks(action.blockId, newBlock);
          return;
        }

        if (action.action === "replace" && action.blockId) {
          const newBlock = [].concat(createBlockFromPlan(action));
          replaceBlocks(action.blockId, newBlock);
          return;
        }

        if (
          action.action === "insert" ||
          action.action === "insert_after" ||
          action.action === "insert_before"
        ) {
          if (action.blockId && existingIds.has(action.blockId)) {
            const baseIndex = baseIndexById.get(action.blockId);
            const offsets = insertOffsets[action.blockId] || {
              before: 0,
              after: 0,
            };
            let insertIndex;

            if (typeof baseIndex === "number") {
              if (action.action === "insert_before") {
                insertIndex = baseIndex + offsets.before;
                offsets.before += 1;
              } else {
                insertIndex = baseIndex + offsets.before + 1 + offsets.after;
                offsets.after += 1;
              }
            }
            insertOffsets[action.blockId] = offsets;

            const newBlock = [].concat(createBlockFromPlan(action));
            insertBlocks(newBlock, insertIndex);
          } else {
            const newBlock = [].concat(createBlockFromPlan(action));
            insertBlocks(newBlock, allBlocks.length);
          }
        }
      });

      setPendingEditPlan(null);
      setMessages((prev) => [
        ...prev,
        {
          role: "system",
          type: "timeline",
          status: "complete",
          message: "Changes applied.",
        },
      ]);
    };
    const cancelEditPlan = () => {
      const { removeBlocks, updateBlockAttributes } =
        dispatch("core/block-editor");
      const allBlocks = select("core/block-editor").getBlocks();

      if (pendingDiffBlockIds.length > 0) {
        removeBlocks(pendingDiffBlockIds);
        setPendingDiffBlockIds([]);
      }

      const planActions = pendingEditPlan
        ? normalizePlanActions(pendingEditPlan)
        : [];
      planActions.forEach((action) => {
        if (
          (action.action === "replace" || action.action === "delete") &&
          action.blockId
        ) {
          const oldBlock = allBlocks.find((b) => b.clientId === action.blockId);
          if (oldBlock && oldBlock.attributes.className) {
            const cleanClass = oldBlock.attributes.className
              .replace("wpaw-diff-removed", "")
              .trim();
            updateBlockAttributes(action.blockId, { className: cleanClass });
          }
        }
      });

      setPendingEditPlan(null);
      setMessages((prev) => [
        ...prev,
        {
          role: "system",
          type: "timeline",
          status: "inactive",
          message: "Changes cancelled.",
        },
      ]);
    };

    const formatClarificationContext = (questionsList, answersMap) => {
      if (!questionsList || questionsList.length === 0) {
        return "";
      }

      const lines = [];
      questionsList.forEach((question) => {
        const answer = answersMap[question.id];
        if (!answer) {
          return;
        }
        lines.push(
          `- ${question.question || question.prompt || "Question"}: ${answer}`,
        );
      });

      if (lines.length === 0) {
        return "";
      }

      return `\n\nClarification Answers:\n${lines.join("\n")}`;
    };

    // Auto-select first option when question changes
    React.useEffect(() => {
      if (
        inClarification &&
        questions.length > 0 &&
        questions[currentQuestionIndex]
      ) {
        const currentQuestion = questions[currentQuestionIndex];
        if (
          currentQuestion.type === "single_choice" &&
          currentQuestion.options &&
          currentQuestion.options.length > 0 &&
          !answers[currentQuestion.id]
        ) {
          const newAnswers = { ...answers };
          newAnswers[currentQuestion.id] = currentQuestion.options[0].value;
          setAnswers(newAnswers);
        }
      }
    }, [currentQuestionIndex, questions, inClarification]);

    /**
     * Remove duplicate adjacent heading blocks
     */
    const removeDuplicateHeadings = (blocks) => {
      if (!blocks || blocks.length === 0) {
        return blocks;
      }

      const cleanedBlocks = [];
      let lastHeadingContent = null;

      for (const block of blocks) {
        if (block.name === "core/heading") {
          const currentHeading = (block.attributes?.content || "")
            .trim()
            .toLowerCase();

          if (currentHeading === lastHeadingContent) {
            wpawLog.log(
              "WP Agentic Writer: Removed duplicate heading:",
              block.attributes.content,
            );
            continue;
          }

          lastHeadingContent = currentHeading;
        } else {
          lastHeadingContent = null;
        }

        cleanedBlocks.push(block);
      }

      return cleanedBlocks;
    };

    // Send message and generate article.
    // Resolve block mentions to client IDs
    const getRefineableBlocks = (options = {}) => {
      const { textOnly = false } = options;
      const allBlocks = select("core/block-editor").getBlocks();
      const textBlockTypes = new Set([
        "core/paragraph",
        "core/heading",
        "core/list",
        "core/quote",
        "core/pullquote",
        "core/code",
        "core/preformatted",
        "core/table",
      ]);
      return allBlocks.filter((block) => {
        if (!block.name || !block.name.startsWith("core/")) {
          return false;
        }
        if (textOnly && !textBlockTypes.has(block.name)) {
          return false;
        }
        // Filter out empty blocks (e.g., default empty paragraph on new posts)
        const content = block.attributes?.content || "";
        const hasInnerBlocks =
          block.innerBlocks && block.innerBlocks.length > 0;
        // Consider block as refineable only if it has content or inner blocks
        return content.trim().length > 0 || hasInnerBlocks;
      });
    };
    const getListItemBlocks = () => {
      const allBlocks = select("core/block-editor").getBlocks();
      const listItems = [];
      let listBlockIndex = 0;

      allBlocks.forEach((block) => {
        if (block.name !== "core/list") {
          return;
        }

        listBlockIndex += 1;
        const innerItems = Array.isArray(block.innerBlocks)
          ? block.innerBlocks
          : [];
        innerItems.forEach((itemBlock, itemIndex) => {
          if (itemBlock.name !== "core/list-item") {
            return;
          }

          listItems.push({
            block: itemBlock,
            parentId: block.clientId,
            listIndex: listBlockIndex,
            itemIndex: itemIndex,
          });
        });
      });

      return listItems;
    };
    const resolveExplicitListItem = (listIndex, itemIndex) => {
      const listItems = getListItemBlocks();
      return listItems.find(
        (item) => item.listIndex === listIndex && item.itemIndex === itemIndex,
      );
    };
    const getParentListId = (blockId) => {
      const getParents = select("core/block-editor").getBlockParents;
      if (!getParents) {
        return null;
      }

      const parentIds = getParents(blockId);
      for (const parentId of parentIds) {
        const parentBlock = select("core/block-editor").getBlock(parentId);
        if (parentBlock?.name === "core/list") {
          return parentId;
        }
      }

      return null;
    };
    const getBlockContentForContext = (blockId) => {
      const block = blockId
        ? select("core/block-editor").getBlock(blockId)
        : null;
      if (!block) {
        return "";
      }

      const content = extractBlockPreview(block);
      return content ? content.trim() : "";
    };
    const getHeadingContextForBlock = (blockId) => {
      const allBlocks = select("core/block-editor").getBlocks();
      const startIndex = allBlocks.findIndex(
        (block) => block.clientId === blockId,
      );
      if (startIndex === -1) {
        return "";
      }

      for (let i = startIndex - 1; i >= 0; i -= 1) {
        if (allBlocks[i].name === "core/heading") {
          return extractBlockPreview(allBlocks[i]) || "";
        }
      }

      return "";
    };
    const getNearbyParagraphContext = (blockId, limit = 2) => {
      const allBlocks = select("core/block-editor").getBlocks();
      const startIndex = allBlocks.findIndex(
        (block) => block.clientId === blockId,
      );
      if (startIndex === -1) {
        return [];
      }

      const snippets = [];
      for (let i = startIndex - 1; i >= 0 && snippets.length < limit; i -= 1) {
        if (allBlocks[i].name === "core/paragraph") {
          const preview = extractBlockPreview(allBlocks[i]);
          if (preview) {
            snippets.push(preview.trim());
          }
        }
        if (allBlocks[i].name === "core/heading") {
          break;
        }
      }

      return snippets.reverse();
    };
    const getContextFromMentions = (mentionTokens, excludeId) => {
      const mentionIds = resolveBlockMentions(mentionTokens).filter(
        (id) => id && id !== excludeId,
      );
      const uniqueIds = [...new Set(mentionIds)];
      return uniqueIds
        .map((id) => getBlockContentForContext(id))
        .filter((content) => content);
    };
    const extractQuotedTermsFromMessage = (message) => {
      if (!message || typeof message !== "string") {
        return [];
      }
      const terms = [];
      const quoteRegex = /"([^"]+)"|'([^']+)'/g;
      let match;
      while ((match = quoteRegex.exec(message)) !== null) {
        const term = (match[1] || match[2] || "").trim().toLowerCase();
        if (term && term.length <= 40) {
          terms.push(term);
        }
      }
      return [...new Set(terms)];
    };
    const getAllTextRefineableBlocks = () =>
      getRefineableBlocks({ textOnly: true });
    const selectLikelySlangBlocks = (message) => {
      const textBlocks = getAllTextRefineableBlocks();
      const quotedTerms = extractQuotedTermsFromMessage(message);
      if (!quotedTerms.length) {
        return textBlocks;
      }
      const matches = textBlocks.filter((block) => {
        const content = (extractBlockPreview(block) || "").toLowerCase();
        return quotedTerms.some((term) => content.includes(term));
      });
      // Fallback to all text blocks when heuristic finds nothing.
      return matches.length > 0 ? matches : textBlocks;
    };
    const isAiSlopRequest = (message = "") =>
      /\b(ai-ish|aiish|ai-style|ai style|slop|humanize|natural|robotic|generic|fluffy|formulaic|tone)\b/i.test(
        String(message),
      );
    const getAiSlopFindingsForBlock = (block) => {
      const content = (extractBlockPreview(block) || "").trim();
      if (!content) {
        return [];
      }

      const findings = [];
      const rules = [
        {
          label: "formulaic contrast phrase",
          pattern: /\b(bukan sekadar|not just)\b/i,
        },
        {
          label: "template-like conclusion phrase",
          pattern:
            /\b(pada akhirnya|in conclusion|to summarize|in summary|kesimpulannya)\b/i,
        },
        {
          label: "instructional/meta leakage",
          pattern:
            /\b(refined version|key refinements|changes made|rationale|could you please share)\b/i,
        },
        { label: "dash-heavy sentence style", pattern: /\s[—–-]\s/u },
        {
          label: "generic AI phrase",
          pattern:
            /\b(delve|furthermore|moreover|crucial|paramount|landscape|testament|unlock|harness|leverage|seamless|robust)\b/i,
        },
        {
          label: "generic marketing claim",
          pattern:
            /\b(in today's digital world|plays a vital role|it is important to note|when it comes to)\b/i,
        },
      ];

      rules.forEach((rule) => {
        if (rule.pattern.test(content)) {
          findings.push(rule.label);
        }
      });

      if (
        block.name === "core/heading" &&
        /\b(introduction|conclusion|overview|benefits|key takeaways|final thoughts)\b/i.test(
          content,
        )
      ) {
        findings.push("weak generic heading");
      }

      return [...new Set(findings)];
    };
    const selectLikelyAiSlopBlocks = (
      message,
      candidateBlocks = getAllTextRefineableBlocks(),
    ) => {
      if (!isAiSlopRequest(message)) {
        return candidateBlocks;
      }

      const matches = candidateBlocks
        .map((block) => ({
          block,
          findings: getAiSlopFindingsForBlock(block),
        }))
        .filter((entry) => entry.findings.length > 0);

      return matches.length > 0 ? matches.map((entry) => entry.block) : [];
    };
    const buildContextBlocksForRefinement = (
      targetIds,
      normalizedAllBlocks,
    ) => {
      const targetSet = new Set(targetIds);
      return normalizedAllBlocks.filter((block, index) => {
        if (targetSet.has(block.clientId)) {
          return true;
        }
        const prev = normalizedAllBlocks[index + 1];
        const next = normalizedAllBlocks[index - 1];
        const neighborsTargeted =
          targetSet.has(prev?.clientId) || targetSet.has(next?.clientId);
        return neighborsTargeted && block.name === "core/heading";
      });
    };
    const buildRefinementDiagnosis = (message, blocks = [], options = {}) => {
      const auditContext = options.auditContext || null;
      if (auditContext?.source === "seo_audit") {
        const candidateCount = Number(
          auditContext.candidateBlockCount || blocks.length || 0,
        );
        const patternLabel = formatAuditPatternLabel(auditContext);
        const candidateLabel = formatCountLabel(
          candidateCount,
          "candidate block",
        );
        const scopeNote =
          Number(auditContext.refineableBlockCount || 0) > candidateCount
            ? ` I am not sending the full article; I narrowed the scope from ${formatCountLabel(auditContext.refineableBlockCount, "refineable block")} to ${candidateLabel}.`
            : "";
        return `Audit found ${patternLabel}. I mapped that audit signal to ${candidateLabel} in the editor.${scopeNote} I will report changed blocks separately after verification.`;
      }
      const genericHeadingPattern =
        /\b(introduction|conclusion|overview|benefits|key takeaways|final thoughts)\b/i;
      let aiishCount = 0;
      let weakHeadingCount = 0;
      let thinBlockCount = 0;

      blocks.forEach((block) => {
        const content = extractBlockPreview({
          name: block.name,
          attributes: block.attributes || {},
          innerBlocks: block.innerBlocks || [],
        });
        const trimmed = (content || "").trim();
        if (!trimmed) {
          return;
        }
        if (
          block.name === "core/heading" &&
          (trimmed.length < 18 || genericHeadingPattern.test(trimmed))
        ) {
          weakHeadingCount += 1;
        }
        if (
          block.name === "core/paragraph" &&
          getAiSlopFindingsForBlock(block).length > 0
        ) {
          aiishCount += 1;
        }
        if (trimmed.split(/\s+/).length < 14 && block.name !== "core/heading") {
          thinBlockCount += 1;
        }
      });

      const issueParts = [];
      if (aiishCount > 0) {
        issueParts.push(
          `${aiishCount} AI-ish paragraph${aiishCount === 1 ? "" : "s"}`,
        );
      }
      if (weakHeadingCount > 0) {
        issueParts.push(
          `${weakHeadingCount} weak heading${weakHeadingCount === 1 ? "" : "s"}`,
        );
      }
      if (thinBlockCount > 0) {
        issueParts.push(
          `${thinBlockCount} thin block${thinBlockCount === 1 ? "" : "s"}`,
        );
      }

      const targetText =
        blocks.length === 1 ? "1 block" : `${blocks.length} blocks`;
      const requestLabel = /\b(ai-ish|aiish|slop|humanize|natural)\b/i.test(
        message,
      )
        ? "tone and AI-slop cleanup"
        : "the requested refinement";

      if (issueParts.length === 0) {
        return `I inspected ${targetText}. I did not find obvious slop markers, so I will focus on ${requestLabel} while preserving structure.`;
      }

      return `I inspected ${targetText}. I found ${issueParts.join(", ")}. I will focus the refinement on ${requestLabel} and keep the surrounding structure stable.`;
    };

    const resolveBlockMentions = (mentions) => {
      const allBlocks = select("core/block-editor").getBlocks();
      const selectedBlockId =
        select("core/block-editor").getSelectedBlockClientId();
      const resolved = [];
      const listItems = getListItemBlocks();

      mentions.forEach((mention) => {
        const type = normalizeMentionToken(mention.replace("@", ""));
        const match = type.match(/^([a-z0-9-]+)-(\d+)$/i);
        const listItemMatch = type.match(/^(?:listitem|list-item|li)-(\d+)$/i);
        const explicitListItemMatch = type.match(
          /^list-(\d+)\.list-item-(\d+)$/i,
        );

        switch (type) {
          case "this":
            if (selectedBlockId) {
              resolved.push(selectedBlockId);
            }
            break;

          case "previous":
            if (selectedBlockId) {
              const selectedIndex = allBlocks.findIndex(
                (b) => b.clientId === selectedBlockId,
              );
              if (selectedIndex > 0) {
                resolved.push(allBlocks[selectedIndex - 1].clientId);
              }
            }
            break;

          case "next":
            if (selectedBlockId) {
              const selectedIndex = allBlocks.findIndex(
                (b) => b.clientId === selectedBlockId,
              );
              if (selectedIndex < allBlocks.length - 1) {
                resolved.push(allBlocks[selectedIndex + 1].clientId);
              }
            }
            break;

          case "all":
            // @all intentionally targets text-based content blocks only.
            getAllTextRefineableBlocks().forEach((block) => {
              resolved.push(block.clientId);
            });
            break;

          default:
            if (explicitListItemMatch) {
              const listIndex = parseInt(explicitListItemMatch[1], 10);
              const itemIndex = parseInt(explicitListItemMatch[2], 10);
              const item = resolveExplicitListItem(listIndex, itemIndex);
              if (item) {
                resolved.push(item.block.clientId);
              }
              break;
            }

            if (listItemMatch) {
              const rawIndex = parseInt(listItemMatch[1], 10);
              const targetIndex = rawIndex <= 0 ? 1 : rawIndex;
              const listItem = listItems[targetIndex - 1];
              if (listItem) {
                resolved.push(listItem.block.clientId);
              }
              break;
            }

            // Handle "paragraph-1", "heading-2", "list-1" format
            if (match) {
              const blockType = "core/" + match[1];
              const blockIndex = parseInt(match[2]) - 1; // 1-based to 0-based

              let currentIndex = 0;
              allBlocks.forEach((block) => {
                if (block.name === blockType) {
                  if (currentIndex === blockIndex) {
                    resolved.push(block.clientId);
                  }
                  currentIndex++;
                }
              });
            }
            break;
        }
      });

      return [...new Set(resolved)]; // Remove duplicates
    };

    // Handle chat-based refinement
    const handleChatRefinement = async (
      message,
      blocksOverride = null,
      options = {},
    ) => {
      const {
        skipUserMessage = false,
        useDiffPlan = true,
        auditContext = null,
      } = options;
      lastRefineRequestRef.current = { message, blocksOverride, options };

      // Capture snapshot before refinement
      pushUndoSnapshot("Block Refinement");

      // Parse mentions from message
      const mentionRegex = /@([a-z0-9-]+(?:-\d+)?|this|previous|next|all)/gi;
      const mentionMatches = [...message.matchAll(mentionRegex)];
      const mentions = mentionMatches.map((m) => "@" + m[1]);

      // Resolve to block client IDs
      const blocksToRefine = blocksOverride || resolveBlockMentions(mentions);
      const hasAllMention = mentions.some(
        (token) => normalizeMentionToken(token.replace("@", "")) === "all",
      );
      let resolvedIds = blocksToRefine;
      if (hasAllMention && !blocksOverride) {
        const likelyBlocks = isAiSlopRequest(message)
          ? selectLikelyAiSlopBlocks(message)
          : selectLikelySlangBlocks(message);
        resolvedIds = likelyBlocks.map((block) => block.clientId);
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "timeline",
            status: "inactive",
            message: `@all scope narrowed to ${resolvedIds.length} likely block(s) based on the request.`,
            timestamp: new Date(),
          },
        ]);
      }

      if (resolvedIds.length === 0) {
        // No valid mentions found - alert user
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "No valid blocks found to refine. Try @this, @previous, @next, @all, @paragraph-1, @listitem-3, or @list-3.list-item-0.",
          },
        ]);
        setIsLoading(false);
        return;
      }

      if (
        hasAllMention &&
        resolvedIds.length >= REFINEMENT_ALL_CONFIRM_THRESHOLD
      ) {
        const confirmed = await requestRefineAllConfirmation(
          resolvedIds.length,
        );
        if (!confirmed) {
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "timeline",
              status: "inactive",
              message: `Cancelled @all refinement (${resolvedIds.length} target blocks).`,
              timestamp: new Date(),
            },
          ]);
          setIsLoading(false);
          return;
        }
      }
      const effectiveUseDiffPlan = hasAllMention ? false : useDiffPlan;
      const shouldUseSelectiveRefine =
        hasAllMention || (isAiSlopRequest(message) && resolvedIds.length > 1);

      const serializeBlockForApi = (block) => {
        if (!block) {
          return null;
        }

        return {
          clientId: block.clientId,
          name: block.name,
          attributes: block.attributes || {},
          innerBlocks: Array.isArray(block.innerBlocks)
            ? block.innerBlocks.map(serializeBlockForApi).filter(Boolean)
            : [],
        };
      };

      // Get actual block data snapshot from editor
      const allBlocksSnapshot = select("core/block-editor").getBlocks();
      const normalizedAllBlocks = allBlocksSnapshot
        .map(serializeBlockForApi)
        .filter(Boolean);
      const blocksToRefineData = resolvedIds
        .map((clientId) =>
          normalizedAllBlocks.find((block) => block.clientId === clientId),
        )
        .filter(Boolean);
      const contextBlocksForApi = buildContextBlocksForRefinement(
        resolvedIds,
        normalizedAllBlocks,
      );
      const refinementDiagnosis = buildRefinementDiagnosis(
        message,
        blocksToRefineData,
        { auditContext },
      );
      const isAuditRefinement = auditContext?.source === "seo_audit";
      const targetLabel = isAuditRefinement
        ? `${resolvedIds.length} audit candidate block(s)`
        : `${resolvedIds.length} block(s)`;

      // Add user message to chat
      if (!skipUserMessage) {
        setMessages((prev) => [...prev, { role: "user", content: message }]);
      }

      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "checking",
          message: isAuditRefinement
            ? "Reading editor and mapping audit findings to candidate blocks..."
            : "Reading editor and inspecting target blocks...",
          timestamp: new Date(),
        },
        {
          role: "assistant",
          type: "agent_diagnosis",
          content: refinementDiagnosis,
        },
        {
          role: "system",
          type: "timeline",
          status: "refining",
          message: isAuditRefinement
            ? `Processing ${targetLabel}; changed blocks will be verified after streaming...`
            : `Refining ${targetLabel}...`,
          timestamp: new Date(),
        },
      ]);

      setIsRefinementLocked(true);
      setRefiningBlockIds(resolvedIds);
      const operationController = beginAgentOperation(
        "refinement",
        "refinement",
      );
      setIsLoading(true);

      try {
        // Get selected block
        const selectedBlockId =
          select("core/block-editor").getSelectedBlockClientId();

        // Call refinement endpoint with actual block data
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/refine-from-chat",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              topic: message,
              context: message,
              selectedBlockClientId: selectedBlockId,
              blocksToRefine: blocksToRefineData, // Send actual block objects
              allBlocks: contextBlocksForApi,
              postId: postId,
              sessionId: currentSessionId,
              stream: true,
              diffPlan: effectiveUseDiffPlan,
              selectiveRefine: shouldUseSelectiveRefine,
              auditContext: isAuditRefinement ? auditContext : null,
              postConfig: postConfig,
              chatHistory: messages.filter((m) => m.role !== "system"),
            }),
            signal: operationController.signal,
          },
        );

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || "Refinement failed");
        }

        // Handle streaming response
        streamTargetRef.current = null;
        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();
        let streamBuffer = "";
        let refinedCount = 0;
        const updatedSectionIds = new Set();
        const { replaceBlocks } = dispatch("core/block-editor");
        let refinementFailed = false;
        let refinementErrorMessage = "";

        while (true) {
          if (stopExecutionRef.current || operationController.signal.aborted) {
            await reader.cancel().catch(() => {});
            throw new DOMException("Operation stopped by user", "AbortError");
          }
          const { done, value } = await reader.read();
          if (done) break;

          streamBuffer += decoder.decode(value, { stream: true });
          const lines = streamBuffer.split("\n");
          streamBuffer = lines.pop() || "";

          for (const line of lines) {
            if (line.startsWith("data: ")) {
              try {
                const data = JSON.parse(line.slice(6));

                if (data.type === "error") {
                  refinementFailed = true;
                  refinementErrorMessage = data.message || "Refinement failed.";
                  break;
                } else if (data.type === "status") {
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        message:
                          data.message ||
                          newMessages[lastTimelineIndex].message,
                        timestamp: new Date(),
                      };
                    }
                    return newMessages;
                  });
                } else if (data.type === "edit_plan") {
                  setPendingEditPlan(data.plan);

                  // Render Diff Blocks in Gutenberg
                  const { insertBlocks, updateBlockAttributes } =
                    dispatch("core/block-editor");
                  const allBlocks = select("core/block-editor").getBlocks();
                  const existingIds = new Set(allBlocks.map((b) => b.clientId));
                  const newDiffIds = [];
                  const actions = normalizePlanActions(data.plan);

                  // Reverse to not mess up indices when inserting multiple blocks
                  const insertOffsets = {};
                  [...actions].reverse().forEach((action) => {
                    if (
                      action.action === "replace" &&
                      action.blockId &&
                      existingIds.has(action.blockId)
                    ) {
                      const oldBlock = allBlocks.find(
                        (b) => b.clientId === action.blockId,
                      );
                      if (oldBlock) {
                        const oldClass = oldBlock.attributes.className || "";
                        if (!oldClass.includes("wpaw-diff-removed")) {
                          updateBlockAttributes(action.blockId, {
                            className:
                              (oldClass ? oldClass + " " : "") +
                              "wpaw-diff-removed",
                          });
                        }

                        const generatedBlocks = [].concat(
                          createBlockFromPlan(action),
                        );
                        generatedBlocks.forEach((tb) => {
                          const tempClass = tb.attributes.className || "";
                          tb.attributes.className =
                            (tempClass ? tempClass + " " : "") +
                            "wpaw-diff-added";
                          newDiffIds.push(tb.clientId);
                        });

                        const baseIndex = allBlocks.findIndex(
                          (b) => b.clientId === action.blockId,
                        );
                        if (baseIndex !== -1) {
                          insertBlocks(generatedBlocks, baseIndex + 1);
                        }
                      }
                    } else if (
                      action.action === "delete" &&
                      action.blockId &&
                      existingIds.has(action.blockId)
                    ) {
                      const oldBlock = allBlocks.find(
                        (b) => b.clientId === action.blockId,
                      );
                      if (oldBlock) {
                        const oldClass = oldBlock.attributes.className || "";
                        if (!oldClass.includes("wpaw-diff-removed")) {
                          updateBlockAttributes(action.blockId, {
                            className:
                              (oldClass ? oldClass + " " : "") +
                              "wpaw-diff-removed",
                          });
                        }
                      }
                    } else if (
                      action.action === "insert" ||
                      action.action === "insert_after" ||
                      action.action === "insert_before"
                    ) {
                      const generatedBlocks = [].concat(
                        createBlockFromPlan(action),
                      );
                      generatedBlocks.forEach((tb) => {
                        const tempClass = tb.attributes.className || "";
                        tb.attributes.className =
                          (tempClass ? tempClass + " " : "") +
                          "wpaw-diff-added";
                        newDiffIds.push(tb.clientId);
                      });

                      if (action.blockId && existingIds.has(action.blockId)) {
                        const baseIndex = allBlocks.findIndex(
                          (b) => b.clientId === action.blockId,
                        );
                        if (baseIndex !== -1) {
                          const offsets = insertOffsets[action.blockId] || {
                            before: 0,
                            after: 0,
                          };
                          let insertIndex = baseIndex;

                          if (action.action === "insert_before") {
                            insertIndex = baseIndex + offsets.before;
                            offsets.before += 1;
                          } else {
                            insertIndex =
                              baseIndex + offsets.before + 1 + offsets.after;
                            offsets.after += 1;
                          }
                          insertOffsets[action.blockId] = offsets;

                          insertBlocks(generatedBlocks, insertIndex);
                        }
                      } else {
                        // Append to end of post if no blockId
                        insertBlocks(generatedBlocks, allBlocks.length);
                      }
                    }
                  });
                  setPendingDiffBlockIds(newDiffIds);

                  setMessages((prev) => [
                    ...prev,
                    {
                      role: "system",
                      type: "edit_plan",
                      plan: data.plan,
                    },
                  ]);
                } else if (data.type === "block") {
                  // Replace block in editor
                  const blockData = data.block;

                  if (blockData.blockName && blockData.attrs) {
                    let newBlock;

                    // Create block using WordPress createBlock API
                    if (
                      blockData.innerBlocks &&
                      blockData.innerBlocks.length > 0
                    ) {
                      // For lists with inner blocks
                      const innerBlocks = blockData.innerBlocks.map(
                        (innerB) => {
                          return wp.blocks.createBlock(
                            innerB.blockName,
                            innerB.attrs,
                          );
                        },
                      );

                      newBlock = wp.blocks.createBlock(
                        blockData.blockName,
                        blockData.attrs,
                        innerBlocks,
                      );
                    } else {
                      // For simple blocks (paragraph, heading)
                      newBlock = wp.blocks.createBlock(
                        blockData.blockName,
                        blockData.attrs,
                      );
                    }

                    // Replace the target block
                    if (newBlock && newBlock.name) {
                      const sectionId =
                        blockSectionRef.current[blockData.clientId];
                      replaceBlocks(blockData.clientId, newBlock);
                      setRefiningBlockIds((prevIds) =>
                        prevIds.map((id) =>
                          id === blockData.clientId ? newBlock.clientId : id,
                        ),
                      );
                      if (sectionId) {
                        removeSectionBlock(sectionId, blockData.clientId);
                        upsertSectionBlock(sectionId, newBlock.clientId);
                        updatedSectionIds.add(sectionId);
                      }
                    }
                  }

                  refinedCount++;
                } else if (data.type === "complete") {
                  // Apply provider metadata from completion
                  applyProviderMetadata(data);

                  // Update timeline
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      const failedLabel =
                        Number(data.failed || 0) > 0
                          ? `, ${Number(data.failed)} failed`
                          : "";
                      const auditPatternLabel =
                        formatAuditPatternLabel(auditContext);
                      const auditCandidateLabel = formatCountLabel(
                        auditContext?.candidateBlockCount || resolvedIds.length,
                        "candidate block",
                      );
                      const auditChangedLabel = formatCountLabel(
                        refinedCount,
                        "changed block",
                      );
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: data.aborted ? "error" : "complete",
                        message: isAuditRefinement
                          ? data.aborted
                            ? `Audit fix stopped early: ${auditPatternLabel} -> ${auditCandidateLabel} inspected -> ${auditChangedLabel}${failedLabel}`
                            : `Audit fix complete: ${auditPatternLabel} -> ${auditCandidateLabel} inspected -> ${auditChangedLabel}${failedLabel}`
                          : data.aborted
                            ? `Refinement stopped early: ${refinedCount} updated${failedLabel}`
                            : `Refined ${refinedCount} block(s) successfully${failedLabel}`,
                        timestamp: new Date(),
                      };
                    }
                    return newMessages;
                  });

                  // Show completion message
                  setMessages((prev) => [
                    ...prev,
                    {
                      role: "assistant",
                      content: data.aborted
                        ? isAuditRefinement
                          ? `Audit fix stopped early after provider errors.\n\n- Audit signal: ${formatAuditPatternLabel(auditContext)}\n- Candidate scope: ${formatCountLabel(auditContext?.candidateBlockCount || resolvedIds.length, "candidate block")} inspected\n- Editor changes: ${formatCountLabel(refinedCount, "block")} changed${Number(data.failed || 0) > 0 ? `\n- Failed attempts: ${Number(data.failed)}` : ""}`
                          : `⚠️ I stopped early after provider errors. Updated ${refinedCount} block(s)${Number(data.failed || 0) > 0 ? `, ${Number(data.failed)} failed` : ""}.`
                        : isAuditRefinement
                          ? `Audit fix complete.\n\n- Audit signal: ${formatAuditPatternLabel(auditContext)}\n- Candidate scope: ${formatCountLabel(auditContext?.candidateBlockCount || resolvedIds.length, "candidate block")} inspected\n- Editor changes: ${formatCountLabel(refinedCount, "block")} changed${Number(data.failed || 0) > 0 ? `\n- Failed attempts: ${Number(data.failed)}` : ""}\n\nVerification: changed blocks were written back to the editor and can be undone from the top bar.`
                          : `✅ Done! I've refined ${refinedCount} block(s) as requested${Number(data.failed || 0) > 0 ? `, with ${Number(data.failed)} failed attempts` : ""}.\n\nVerification: updated blocks were written back to the editor and can be undone from the top bar.`,
                    },
                  ]);

                  // Update cost
                  if (data.totalCost) {
                    setCost({
                      ...cost,
                      session: cost.session + data.totalCost,
                    });
                  }
                  updatedSectionIds.forEach((sectionId) => {
                    saveSectionBlocks(sectionId);
                  });
                }
              } catch (e) {
                wpawLog.error("Failed to parse streaming data:", line, e);
              }
            }
            if (refinementFailed) {
              break;
            }
          }
          if (refinementFailed) {
            break;
          }
        }

        if (stopExecutionRef.current || operationController.signal.aborted) {
          throw new DOMException("Operation stopped by user", "AbortError");
        }

        if (refinementFailed) {
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: `Refinement stopped: ${refinementErrorMessage}`,
              canRetry: true,
              retryType: "refine",
            },
          ]);

          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "error",
                message: "Refinement stopped (edit plan failed)",
              };
            }
            return newMessages;
          });
        }
      } catch (error) {
        if (isAbortError(error) || stopExecutionRef.current) {
          setMessages((prev) => {
            const newMessages = [...prev];
            const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
            if (lastTimelineIndex !== -1) {
              newMessages[lastTimelineIndex] = {
                ...newMessages[lastTimelineIndex],
                status: "stopped",
                message: "Refinement stopped by user.",
              };
            }
            return newMessages;
          });
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content:
                "Refinement stopped. Already-applied block changes remain in the editor and can be undone from the top bar.",
            },
          ]);
          return;
        }
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: " + error.message,
            canRetry: true,
            retryType: "refine",
          },
        ]);

        // Update timeline to show error
        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "error",
              message: "Refinement failed",
            };
          }
          return newMessages;
        });
      } finally {
        setIsRefinementLocked(false);
        setRefiningBlockIds([]);
        setIsLoading(false);
        finishAgentOperation("refinement");
      }
    };
    const renderRefineAllConfirmModal = () => {
      if (!refineAllConfirm.isOpen) {
        return null;
      }

      return (
        <div
          className="wpaw-refine-confirm-overlay"
          role="dialog"
          aria-modal="true"
          aria-label="Confirm large @all refinement"
        >
          <div className="wpaw-refine-confirm-modal">
            <div className="wpaw-refine-confirm-title">
              Confirm @all Refinement
            </div>
            <div className="wpaw-refine-confirm-body">
              {`This will refine ${refineAllConfirm.blockCount} text block(s) in batches of 5. ` +
                "This may take time and consume API credits."}
            </div>
            <CheckboxControl
              label="Don’t ask again for this session"
              checked={refineAllConfirm.dontAskAgain}
              onChange={(checked) => {
                setRefineAllConfirm((prev) => ({
                  ...prev,
                  dontAskAgain: Boolean(checked),
                }));
              }}
            />
            <div className="wpaw-refine-confirm-actions">
              <Button
                isSecondary={true}
                onClick={() => resolveRefineAllConfirmation(false)}
              >
                Cancel
              </Button>
              <Button
                isPrimary={true}
                onClick={() => {
                  if (refineAllConfirm.dontAskAgain) {
                    skipRefineAllConfirmRef.current = true;
                  }
                  resolveRefineAllConfirmation(true);
                }}
              >
                Continue
              </Button>
            </div>
          </div>
        </div>
      );
    };

    // Get mention options for autocomplete
    const getMentionOptions = (query) => {
      const allBlocks = select("core/block-editor").getBlocks();
      const selectedBlockId =
        select("core/block-editor").getSelectedBlockClientId();
      const options = [];

      // Add special mentions
      if (!query || "this".includes(query.toLowerCase())) {
        options.push({
          id: "this",
          label: "@this",
          sublabel: "Currently selected block",
          type: "special",
        });
      }
      if (!query || "previous".includes(query.toLowerCase())) {
        options.push({
          id: "previous",
          label: "@previous",
          sublabel: "Block before current selection",
          type: "special",
        });
      }
      if (!query || "next".includes(query.toLowerCase())) {
        options.push({
          id: "next",
          label: "@next",
          sublabel: "Block after current selection",
          type: "special",
        });
      }
      if (!query || "all".includes(query.toLowerCase())) {
        options.push({
          id: "all",
          label: "@all",
          sublabel: "All content blocks",
          type: "special",
        });
      }
      if (!query || "title".includes(query.toLowerCase())) {
        options.push({
          id: "title",
          label: "@title",
          sublabel: "Refine post title with instruction",
          type: "special",
        });
      }

      // Add numbered blocks for core blocks
      const blockCounters = {};
      const queryLower = query.toLowerCase();
      let listItemIndex = 0;
      let listBlockIndex = 0;

      allBlocks.forEach((block) => {
        if (!block.name || !block.name.startsWith("core/")) {
          return;
        }

        const typeName = block.name.replace("core/", "");
        blockCounters[typeName] = (blockCounters[typeName] || 0) + 1;
        const blockLabel = `@${typeName}-${blockCounters[typeName]}`;

        const content = extractBlockPreview(block);
        const contentLower = content.toLowerCase();
        if (
          !query ||
          blockLabel.includes(queryLower) ||
          contentLower.startsWith(queryLower)
        ) {
          const truncatedContent =
            content.length > 40 ? content.substring(0, 40) + "..." : content;
          options.push({
            id: blockLabel,
            label: String(blockLabel),
            sublabel: truncatedContent || String(typeName),
            type: "block",
            clientId: block.clientId,
          });
        }

        if (block.name === "core/list") {
          listBlockIndex += 1;
          const innerItems = Array.isArray(block.innerBlocks)
            ? block.innerBlocks
            : [];
          innerItems.forEach((itemBlock, itemIndex) => {
            if (itemBlock.name !== "core/list-item") {
              return;
            }

            listItemIndex += 1;
            const itemLabel = `@listitem-${listItemIndex}`;
            const explicitLabel = `@list-${listBlockIndex}.list-item-${itemIndex}`;
            const itemContent = extractBlockPreview(itemBlock);
            const itemLower = itemContent.toLowerCase();
            if (
              !query ||
              itemLabel.includes(queryLower) ||
              explicitLabel.includes(queryLower) ||
              itemLower.startsWith(queryLower)
            ) {
              const truncatedItem =
                itemContent.length > 40
                  ? itemContent.substring(0, 40) + "..."
                  : itemContent;
              options.push({
                id: itemLabel,
                label: String(explicitLabel),
                sublabel: truncatedItem
                  ? `List ${listBlockIndex}: ${truncatedItem}`
                  : `List ${listBlockIndex} item`,
                type: "list-item",
                clientId: itemBlock.clientId,
                parentClientId: block.clientId,
              });
            }
          });
        }
      });

      return options;
    };

    React.useEffect(() => {
      const handleInsertMention = (event) => {
        const token = event?.detail?.token;
        if (!token) {
          return;
        }

        setActiveTab("chat");
        setInput((prev) => {
          const prefix = prev && !/\s$/.test(prev) ? prev + " " : prev;
          return `${prefix}${token}`;
        });

        setTimeout(() => {
          const inputNode = inputRef.current?.textarea || inputRef.current;
          if (inputNode) {
            inputNode.focus();
            inputNode.selectionStart = inputNode.selectionEnd =
              inputNode.value.length;
          }

          const mentionOptionsList = getMentionOptions("");
          setMentionOptions(mentionOptionsList);
          setShowMentionAutocomplete(mentionOptionsList.length > 0);
        }, 0);
      };

      window.addEventListener("wpaw:insert-mention", handleInsertMention);
      return () =>
        window.removeEventListener("wpaw:insert-mention", handleInsertMention);
    }, [getMentionOptions]);

    // Handle input change for mention detection
    const handleInputChange = (value) => {
      setInput(value);

      // Check if user is typing a mention
      const inputNode = inputRef.current?.textarea || inputRef.current;
      const cursorPosition =
        typeof inputNode?.selectionStart === "number"
          ? inputNode.selectionStart
          : value.length;
      const textBeforeCursor = value.substring(0, cursorPosition);
      const mentionMatch = textBeforeCursor.match(/@(\w*)$/);
      const slashMatch = textBeforeCursor.match(/\/([\w\s]*)$/);

      if (mentionMatch) {
        const query = mentionMatch[1];
        setMentionQuery(query);
        const options = getMentionOptions(query);
        setMentionOptions(options);
        setShowMentionAutocomplete(options.length > 0);
        setMentionCursorIndex(0);
        setShowSlashAutocomplete(false);
        setSlashOptions([]);
      } else if (slashMatch) {
        const query = slashMatch[1];
        setSlashQuery(query);
        const options = getSlashOptions(query);
        setSlashOptions(options);
        setShowSlashAutocomplete(options.length > 0);
        setSlashCursorIndex(0);
        setShowMentionAutocomplete(false);
        setMentionOptions([]);
      } else {
        setShowMentionAutocomplete(false);
        setMentionOptions([]);
        setShowSlashAutocomplete(false);
        setSlashOptions([]);
      }
    };

    // Handle keyboard navigation in autocomplete
    const handleKeyDown = (e) => {
      if (!showMentionAutocomplete && !showSlashAutocomplete) {
        if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
          sendMessage();
        }
        return;
      }

      if (showMentionAutocomplete && e.keyCode === 40) {
        // Down arrow
        e.preventDefault();
        setMentionCursorIndex((prev) => (prev + 1) % mentionOptions.length);
      } else if (showMentionAutocomplete && e.keyCode === 38) {
        // Up arrow
        e.preventDefault();
        setMentionCursorIndex(
          (prev) => (prev - 1 + mentionOptions.length) % mentionOptions.length,
        );
      } else if (showMentionAutocomplete && e.keyCode === 13) {
        // Enter
        e.preventDefault();
        if (mentionOptions[mentionCursorIndex]) {
          insertMention(mentionOptions[mentionCursorIndex]);
        }
      } else if (showSlashAutocomplete && e.keyCode === 40) {
        // Down arrow
        e.preventDefault();
        setSlashCursorIndex((prev) => (prev + 1) % slashOptions.length);
      } else if (showSlashAutocomplete && e.keyCode === 38) {
        // Up arrow
        e.preventDefault();
        setSlashCursorIndex(
          (prev) => (prev - 1 + slashOptions.length) % slashOptions.length,
        );
      } else if (showSlashAutocomplete && e.keyCode === 13) {
        // Enter
        e.preventDefault();
        if (slashOptions[slashCursorIndex]) {
          insertSlashCommand(slashOptions[slashCursorIndex]);
        }
      } else if (e.keyCode === 27) {
        // Escape
        e.preventDefault();
        setShowMentionAutocomplete(false);
        setShowSlashAutocomplete(false);
      }
    };

    // Insert selected mention
    const insertMention = (option) => {
      const value = input;
      const inputNode = inputRef.current?.textarea || inputRef.current;
      const cursorPosition =
        typeof inputNode?.selectionStart === "number"
          ? inputNode.selectionStart
          : value.length;
      const textBeforeCursor = value.substring(0, cursorPosition);
      const mentionStart = textBeforeCursor.lastIndexOf("@");

      const beforeMention = value.substring(0, mentionStart);
      const afterMention = value.substring(cursorPosition);
      const newValue = beforeMention + option.label + " " + afterMention;

      setInput(newValue);
      setShowMentionAutocomplete(false);
      setMentionOptions([]);

      // Focus back on input
      setTimeout(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, 0);
    };
    const insertSlashCommand = (option) => {
      const value = input;
      const inputNode = inputRef.current?.textarea || inputRef.current;
      const cursorPosition =
        typeof inputNode?.selectionStart === "number"
          ? inputNode.selectionStart
          : value.length;
      const textBeforeCursor = value.substring(0, cursorPosition);
      const slashStart = textBeforeCursor.lastIndexOf("/");

      const beforeSlash = value.substring(0, slashStart);
      const afterSlash = value.substring(cursorPosition);
      const newValue = beforeSlash + option.insertText + afterSlash;

      setInput(newValue);
      setShowSlashAutocomplete(false);
      setSlashOptions([]);
      if (option.insertText.endsWith("@")) {
        const mentionOptionsList = getMentionOptions("");
        setMentionQuery("");
        setMentionOptions(mentionOptionsList);
        setShowMentionAutocomplete(mentionOptionsList.length > 0);
        setMentionCursorIndex(0);
      }

      setTimeout(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, 0);
    };

    const sendMessage = async () => {
      if (!input.trim() || isLoading) {
        return;
      }

      const userMessage = input.trim();
      // Collapse textarea to give more space for response
      setIsTextareaExpanded(false);

      // Check for reset command
      if (/^\s*(\/reset|\/clear)\s*$/i.test(userMessage)) {
        setInput("");
        await handleResetCommand();
        return;
      }

      const agentRoute = decideAgentAction(userMessage);
      const effectiveAgentMode = agentRoute.mode || agentMode || "chat";
      if (
        ["chat", "planning", "writing"].includes(effectiveAgentMode) &&
        effectiveAgentMode !== agentMode
      ) {
        setAgentMode(effectiveAgentMode);
      }

      if (agentRoute.action === "execute_plan") {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        addActivityTimeline(
          "checking",
          "Checking outline and editor context...",
        );
        await executePlanFromCard({ skipConfirm: true });
        return;
      }

      if (agentRoute.action === "generate_meta") {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        addActivityTimeline(
          "checking",
          "Reading article and generating meta description...",
        );
        await generateMetaDescription();
        return;
      }

      if (agentRoute.action === "seo_audit") {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        addActivityTimeline(
          "checking",
          "Reading editor and running SEO audit...",
        );
        await runSeoAudit();
        return;
      }

      const parsedCommand = parseInsertCommand(userMessage);
      const commandMessage = parsedCommand
        ? parsedCommand.message
        : userMessage;
      const mentionTokens = extractMentionsFromText(commandMessage);
      const hasMentions = mentionTokens.length > 0;
      const titleMentioned = hasTitleMention(mentionTokens);
      const refineableBlocks = getRefineableBlocks();
      const shouldShowPlan = effectiveAgentMode === "planning";
      const generationLabel =
        effectiveAgentMode === "planning"
          ? "Creating outline..."
          : "Generating article...";
      const reformatCommand = /^\s*(?:\/)?reformat\b/i;

      if (parsedCommand) {
        setIsLoading(true);
        setMessages((prev) => [
          ...deactivateActiveTimelineEntries(prev),
          {
            role: "system",
            type: "timeline",
            status: "refining",
            message: "Preparing insertion...",
            timestamp: new Date(),
          },
        ]);
        await insertRefinementBlock(
          parsedCommand.mode,
          commandMessage,
          mentionTokens,
          userMessage,
        );
        setIsLoading(false);
        return;
      }

      if (reformatCommand.test(userMessage)) {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        const targetIds = hasMentions
          ? resolveBlockMentions(mentionTokens)
          : getRefineableBlocks().map((block) => block.clientId);
        const allBlocks = select("core/block-editor").getBlocks();
        const blocksToReformat = allBlocks.filter((block) =>
          targetIds.includes(block.clientId),
        );
        await reformatBlocks(blocksToReformat, userMessage);
        return;
      }

      if (titleMentioned) {
        setInput("");
        await handleTitleRefinement(userMessage, mentionTokens);
        return;
      }

      if (
        effectiveAgentMode === "planning" &&
        !hasMentions &&
        currentPlanRef.current
      ) {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        await revisePlanFromPrompt(userMessage);
        return;
      }

      if (effectiveAgentMode === "chat" && !hasMentions) {
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        const operationController = beginAgentOperation(
          "chat",
          "chat response",
        );
        setIsLoading(true);

        // User message is NOT an AI suggestion - don't extract from user input

        // Store for retry
        lastChatRequestRef.current = { message: userMessage };

        try {
          const chatHistory = messages
            .filter((m) => m.role === "user" || m.role === "assistant")
            .map((m) => ({ role: m.role, content: m.content }));

          const response = await fetch(wpAgenticWriter.apiUrl + "/chat", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              messages: [
                ...chatHistory,
                { role: "user", content: userMessage },
              ],
              postId: postId,
              sessionId: currentSessionId,
              type: "chat",
              stream: true,
              postConfig: postConfig,
            }),
            signal: operationController.signal,
          });

          if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || "Failed to chat");
          }

          const reader = registerActiveReader(response.body.getReader());
          const decoder = new TextDecoder();
          let streamBuffer = "";
          let streamError = null;
          streamTargetRef.current = null;

          while (true) {
            if (
              stopExecutionRef.current ||
              operationController.signal.aborted
            ) {
              await reader.cancel().catch(() => {});
              throw new DOMException("Operation stopped by user", "AbortError");
            }
            const { done, value } = await reader.read();
            if (done) break;

            streamBuffer += decoder.decode(value, { stream: true });
            const lines = streamBuffer.split("\n");
            streamBuffer = lines.pop() || "";

            for (const line of lines) {
              if (!line.startsWith("data: ")) {
                continue;
              }

              try {
                const data = JSON.parse(line.slice(6));
                if (data.type === "error") {
                  streamError = new Error(data.message || "Failed to chat");
                  break;
                }
                if (
                  data.type === "conversational" ||
                  data.type === "conversational_stream"
                ) {
                  const cleanContent = (data.content || "").trim();
                  if (!cleanContent) {
                    continue;
                  }

                  const streamTarget =
                    streamTargetRef.current ||
                    resolveStreamTarget(cleanContent);
                  if (!streamTarget) {
                    continue;
                  }

                  streamTargetRef.current = streamTarget;

                  if (data.type === "conversational") {
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastIdx = newMessages.length - 1;
                      const lastMessage = newMessages[lastIdx];
                      if (
                        lastMessage &&
                        lastMessage.role === "assistant" &&
                        lastMessage.content === cleanContent
                      ) {
                        return newMessages;
                      }
                      newMessages.push({
                        role: "assistant",
                        content: cleanContent,
                      });
                      return newMessages;
                    });
                  } else {
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastIdx = newMessages.length - 1;
                      if (
                        newMessages[lastIdx] &&
                        newMessages[lastIdx].role === "assistant"
                      ) {
                        newMessages[lastIdx] = {
                          ...newMessages[lastIdx],
                          content: cleanContent,
                        };
                      } else {
                        newMessages.push({
                          role: "assistant",
                          content: cleanContent,
                        });
                      }
                      return newMessages;
                    });
                  }
                } else if (data.type === "complete") {
                  if (data.totalCost) {
                    setCost({
                      ...cost,
                      session: cost.session + data.totalCost,
                    });
                  }
                  applyProviderMetadata(data);
                  // Extract ALL focus keyword suggestions from AI response
                  setMessages((prev) => {
                    const lastAssistantMsg = prev
                      .filter((m) => m.role === "assistant")
                      .pop();
                    if (lastAssistantMsg && lastAssistantMsg.content) {
                      const suggestions = extractFocusKeywordSuggestions(
                        lastAssistantMsg.content,
                      );
                      if (suggestions.length > 0) {
                        addFocusKeywordSuggestions(suggestions);
                      }
                    }
                    return prev;
                  });
                }
              } catch (parseError) {
                wpawLog.error(
                  "Failed to parse streaming data:",
                  line,
                  parseError,
                );
              }
            }

            if (streamError) {
              throw streamError;
            }
          }

          // Detect intent after chat completes
          try {
            const intentResult = await detectUserIntent(userMessage);

            // Track intent detection cost
            if (intentResult.cost > 0) {
              setCost((prev) => ({
                ...prev,
                session: prev.session + intentResult.cost,
              }));
            }

            if (
              intentResult.intent &&
              intentResult.intent !== "continue_chat"
            ) {
              setMessages((prev) => {
                const newMessages = [...prev];
                const lastIdx = newMessages.length - 1;
                if (
                  newMessages[lastIdx] &&
                  newMessages[lastIdx].role === "assistant"
                ) {
                  newMessages[lastIdx] = {
                    ...newMessages[lastIdx],
                    detectedIntent: intentResult.intent,
                  };
                }
                return newMessages;
              });
            }
          } catch (intentError) {
            wpawLog.error(
              "Intent detection failed:",
              formatAiErrorMessage(intentError, "Intent detection failed"),
            );
          }
        } catch (error) {
          if (isAbortError(error)) {
            setMessages((prev) => [
              ...deactivateActiveTimelineEntries(prev),
              {
                role: "system",
                type: "timeline",
                status: "stopped",
                message: "Chat response stopped.",
                timestamp: new Date(),
              },
            ]);
            // Continue to shared cleanup below.
          } else {
            const errorMsg = formatAiErrorMessage(error, "Failed to chat");
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content: errorMsg,
                canRetry: true,
                retryType: "chat",
              },
            ]);
          }
        }

        setIsLoading(false);
        finishAgentOperation("chat");
        return;
      }

      if (
        !hasMentions &&
        refineableBlocks.length > 0 &&
        agentRoute.action === "article_refinement"
      ) {
        // Content exists - run clarity check before full-article refinement
        const targetedBlocks = getTargetedRefinementBlocks(userMessage);
        const matchedSection = !targetedBlocks
          ? findBestPlanSectionMatch(userMessage)
          : null;
        const matchedSectionBlocks = matchedSection
          ? sectionBlocksRef.current[matchedSection.id] || []
          : [];
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        if (matchedSectionBlocks.length > 0) {
          setMessages((prev) => [
            ...prev,
            {
              role: "assistant",
              content: `Targeting section: ${matchedSection.heading || matchedSection.title || "Selected section"} (${matchedSectionBlocks.length} block(s)).`,
            },
          ]);
        }
        setIsLoading(true);
        setMessages((prev) => [
          ...deactivateActiveTimelineEntries(prev),
          {
            role: "system",
            type: "timeline",
            status: "checking",
            message: matchedSection
              ? `Analyzing request (targeting: ${matchedSection.heading || matchedSection.title || "section"})...`
              : "Analyzing request...",
            timestamp: new Date(),
          },
        ]);

        const fallbackBlocks = isAiSlopRequest(userMessage)
          ? selectLikelyAiSlopBlocks(userMessage, refineableBlocks).map(
              (block) => block.clientId,
            )
          : refineableBlocks.map((block) => block.clientId);
        if (
          isAiSlopRequest(userMessage) &&
          fallbackBlocks.length === 0 &&
          !targetedBlocks &&
          matchedSectionBlocks.length === 0
        ) {
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "assistant",
              content:
                "I inspected the article and did not find blocks matching the AI-ish/slop detector, so I did not send the whole article to refinement.",
            },
          ]);
          setIsLoading(false);
          return;
        }
        await handleChatRefinement(
          userMessage,
          targetedBlocks && targetedBlocks.length > 0
            ? targetedBlocks
            : matchedSectionBlocks.length > 0
              ? matchedSectionBlocks
              : fallbackBlocks,
          { skipUserMessage: true },
        );
        return;
      }

      if (!hasMentions) {
        // No mentions - check clarity first before article generation
        setInput("");
        setMessages((prev) => [
          ...prev,
          { role: "user", content: userMessage },
        ]);
        const operationType =
          effectiveAgentMode === "planning" ? "planning" : "generation";
        const operationController = beginAgentOperation(
          operationType,
          effectiveAgentMode === "planning"
            ? "outline generation"
            : "article generation",
        );
        setIsLoading(true);

        // Check clarity first
        setMessages((prev) => [
          ...deactivateActiveTimelineEntries(prev),
          {
            role: "system",
            type: "timeline",
            status: "checking",
            message: "Analyzing request...",
            timestamp: new Date(),
          },
        ]);

        // First try clarity check
        let requestDetectedLanguage = detectedLanguage;
        try {
          const clarityResponse = await fetch(
            wpAgenticWriter.apiUrl + "/check-clarity",
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify({
                topic: userMessage,
                answers: [],
                postId: postId,
                sessionId: currentSessionId,
                mode: "generation",
                postConfig: postConfig,
                chatHistory: buildChatHistoryPayload(),
              }),
              signal: operationController.signal,
            },
          );

          if (clarityResponse.ok) {
            const clarityData = await clarityResponse.json();
            const clarityResult = clarityData.result;

            // Store detected language for article generation
            if (clarityResult.detected_language) {
              requestDetectedLanguage = clarityResult.detected_language;
              setDetectedLanguage(clarityResult.detected_language);
            }

            if (
              !clarityResult.is_clear &&
              clarityResult.questions &&
              clarityResult.questions.length > 0
            ) {
              // Need clarification - show quiz
              setQuestions(clarityResult.questions);
              setInClarification(true);
              setCurrentQuestionIndex(0);
              setAnswers([]);
              setIsLoading(false);

              // Update timeline
              setMessages((prev) => {
                const newMessages = [...prev];
                const lastTimelineIndex =
                  findLastActiveTimelineIndex(newMessages);
                if (lastTimelineIndex !== -1) {
                  newMessages[lastTimelineIndex] = {
                    ...newMessages[lastTimelineIndex],
                    status: "waiting",
                    message: "Waiting for clarification...",
                  };
                }
                return newMessages;
              });
              finishAgentOperation(operationType);
              return;
            }
          }
          // If clarity check fails, proceed with generation anyway
        } catch (clarityError) {
          if (isAbortError(clarityError)) {
            setMessages((prev) => [
              ...deactivateActiveTimelineEntries(prev),
              {
                role: "system",
                type: "timeline",
                status: "stopped",
                message: "Generation stopped.",
                timestamp: new Date(),
              },
            ]);
            setIsLoading(false);
            finishAgentOperation(operationType);
            return;
          }
          wpawLog.warn(
            "Clarity check failed, proceeding with generation:",
            clarityError,
          );
          // Continue to article generation
        }

        // Clear enough - proceed with article generation
        // Update timeline
        setMessages((prev) => {
          const newMessages = [...prev];
          const lastTimelineIndex = findLastActiveTimelineIndex(newMessages);
          if (lastTimelineIndex !== -1) {
            newMessages[lastTimelineIndex] = {
              ...newMessages[lastTimelineIndex],
              status: "starting",
              message: generationLabel,
            };
          }
          return newMessages;
        });

        // Now call generate-plan
        let timeout = null;
        try {
          const response = await fetch(
            wpAgenticWriter.apiUrl + "/generate-plan",
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": wpAgenticWriter.nonce,
              },
              body: JSON.stringify({
                topic: userMessage,
                context: "",
                postId: postId,
                sessionId: currentSessionId,
                answers: [],
                autoExecute: effectiveAgentMode !== "planning",
                stream: true,
                articleLength: postConfig.article_length,
                detectedLanguage: requestDetectedLanguage,
                postConfig: postConfig,
                chatHistory: buildChatHistoryPayload(),
              }),
              signal: operationController.signal,
            },
          );

          if (!response.ok) {
            const error = await response.json();
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content: formatAiErrorMessage(
                  error,
                  "Failed to generate article",
                ),
                canRetry: true,
                retryType: "generation",
              },
            ]);
            setIsLoading(false);
            return;
          }

          // Handle streaming response
          streamTargetRef.current = null;
          const reader = registerActiveReader(response.body.getReader());
          const decoder = new TextDecoder();

          // Add timeout to detect hanging responses
          timeout = setTimeout(() => {
            if (isLoading) {
              wpawLog.error("Generation timeout - no response received");
              setMessages((prev) => [
                ...prev,
                {
                  role: "system",
                  type: "error",
                  content: formatAiErrorMessage(
                    "cURL error 28: Operation timed out after 120000 milliseconds",
                    "Failed to generate article",
                  ),
                  canRetry: true,
                  retryType: "generation",
                },
              ]);
              setIsLoading(false);
              reader.cancel();
            }
          }, 120000); // 2 minute timeout

          while (true) {
            if (
              stopExecutionRef.current ||
              operationController.signal.aborted
            ) {
              await reader.cancel().catch(() => {});
              throw new DOMException("Operation stopped by user", "AbortError");
            }
            const { done, value } = await reader.read();
            if (done) break;

            const chunk = decoder.decode(value, { stream: true });
            const lines = chunk.split("\n");

            for (const line of lines) {
              if (line.startsWith("data: ")) {
                try {
                  const data = JSON.parse(line.slice(6));

                  if (data.type === "plan") {
                    setCost({ ...cost, session: cost.session + data.cost });
                    if (shouldShowPlan && data.plan) {
                      updateOrCreatePlanMessage(data.plan, {
                        suggestKeywords: effectiveAgentMode === "planning",
                      });
                    }
                  } else if (data.type === "title_update") {
                    dispatch("core/editor").editPost({ title: data.title });
                  } else if (data.type === "status") {
                    if (data.status === "complete") {
                      continue;
                    }

                    // Update timeline
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastTimelineIndex =
                        findLastActiveTimelineIndex(newMessages);
                      if (lastTimelineIndex !== -1) {
                        newMessages[lastTimelineIndex] = {
                          ...newMessages[lastTimelineIndex],
                          status: data.status,
                          message: data.message,
                          icon: data.icon,
                        };
                      }
                      return newMessages;
                    });
                  } else if (
                    data.type === "conversational" ||
                    data.type === "conversational_stream"
                  ) {
                    // Remove article marker and clean content
                    const cleanContent = (data.content || "")
                      .replace(/~~~ARTICLE~+/g, "")
                      .replace(/~~~ARTICLE~~~[\r\n]*/g, "")
                      .trim();

                    // Skip if content is empty after cleaning
                    if (
                      !cleanContent ||
                      shouldSkipPlanningCompletion(cleanContent)
                    ) {
                      continue;
                    }

                    const streamTarget =
                      streamTargetRef.current ||
                      resolveStreamTarget(cleanContent);

                    if (!streamTarget) {
                      continue;
                    }

                    streamTargetRef.current = streamTarget;

                    if (streamTarget === "timeline") {
                      updateOrCreateTimelineEntry(cleanContent);
                    } else {
                      // This is actual conversational content - add as chat bubble
                      if (data.type === "conversational") {
                        setMessages((prev) => [
                          ...prev,
                          { role: "assistant", content: cleanContent },
                        ]);
                      } else {
                        setMessages((prev) => {
                          const newMessages = [...prev];
                          const lastIdx = newMessages.length - 1;
                          if (
                            newMessages[lastIdx] &&
                            newMessages[lastIdx].role === "assistant"
                          ) {
                            newMessages[lastIdx] = {
                              ...newMessages[lastIdx],
                              content: cleanContent,
                            };
                          } else {
                            newMessages.push({
                              role: "assistant",
                              content: cleanContent,
                            });
                          }
                          return newMessages;
                        });
                      }
                    }
                  } else if (data.type === "block") {
                    const { insertBlocks } = dispatch("core/block-editor");
                    let newBlock;

                    if (data.block.blockName === "core/paragraph") {
                      const content =
                        data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                      newBlock = wp.blocks.createBlock("core/paragraph", {
                        content: content,
                      });
                    } else if (data.block.blockName === "core/heading") {
                      const level = data.block.attrs?.level || 2;
                      const content =
                        data.block.innerHTML?.match(
                          /<h[1-6]>(.*?)<\/h[1-6]>/,
                        )?.[1] || "";
                      newBlock = wp.blocks.createBlock("core/heading", {
                        level: level,
                        content: content,
                      });
                    } else if (data.block.blockName === "core/list") {
                      const listItems = (data.block.innerBlocks || []).map(
                        (item) => {
                          const content =
                            item.innerHTML?.match(/<li>(.*?)<\/li>/)?.[1] || "";
                          return wp.blocks.createBlock("core/list-item", {
                            content: content,
                          });
                        },
                      );
                      newBlock = wp.blocks.createBlock(
                        "core/list",
                        {
                          ...(data.block.attrs || {}),
                          ordered: data.block.attrs?.ordered || false,
                        },
                        listItems,
                      );
                    } else if (data.block.blockName === "core/quote") {
                      const content =
                        data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                      newBlock = wp.blocks.createBlock("core/quote", {
                        value: content,
                      });
                    } else if (data.block.blockName === "core/image") {
                      newBlock = wp.blocks.createBlock(
                        "core/image",
                        data.block.attrs || {},
                      );
                    }

                    if (newBlock) {
                      insertBlocks(newBlock);
                    }
                  } else if (data.type === "complete") {
                    applyProviderMetadata(data);
                    clearTimeout(timeout);
                    setCost({
                      ...cost,
                      session: cost.session + data.totalCost,
                    });

                    // Update timeline to complete
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastTimelineIndex =
                        findLastActiveTimelineIndex(newMessages);
                      if (lastTimelineIndex !== -1) {
                        newMessages[lastTimelineIndex] = {
                          ...newMessages[lastTimelineIndex],
                          status: "complete",
                          message:
                            effectiveAgentMode === "planning"
                              ? "Outline ready."
                              : "Article generated successfully!",
                        };
                      }
                      return newMessages;
                    });
                    setIsLoading(false);
                  } else if (data.type === "error") {
                    clearTimeout(timeout);
                    setMessages((prev) => [
                      ...prev,
                      {
                        role: "system",
                        type: "error",
                        content: formatAiErrorMessage(
                          data.message ||
                            "An error occurred during article generation",
                          "Failed to generate article",
                        ),
                        canRetry: true,
                        retryType: "generation",
                      },
                    ]);
                    setIsLoading(false);
                  }
                } catch (parseError) {
                  wpawLog.error(
                    "Failed to parse streaming data:",
                    line,
                    parseError,
                  );
                }
              }

              if (streamError) {
                throw streamError;
              }
            }
          }
          // Clear timeout when streaming completes normally
          clearTimeout(timeout);
        } catch (error) {
          if (timeout) {
            clearTimeout(timeout);
          }
          if (isAbortError(error)) {
            setMessages((prev) => [
              ...deactivateActiveTimelineEntries(prev),
              {
                role: "system",
                type: "timeline",
                status: "stopped",
                message: "Generation stopped.",
                timestamp: new Date(),
              },
            ]);
          } else {
            wpawLog.error("Article generation error:", error);
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content: formatAiErrorMessage(
                  error,
                  "Failed to generate article",
                ),
                canRetry: true,
                retryType: "generation",
              },
            ]);
          }
          setIsLoading(false);
        } finally {
          finishAgentOperation(operationType);
        }

        return;
      }

      // Has mentions - check if mentioned blocks exist
      let blocksToRefine = [];
      if (hasMentions) {
        blocksToRefine = resolveBlockMentions(mentionTokens);
      }

      if (hasMentions && blocksToRefine.length === 0) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content:
              "No valid blocks found to refine. Select a block and use @this, or target an existing block like @paragraph-1.",
          },
        ]);
        setIsLoading(false);
        return;
      }

      if (blocksToRefine.length > 0) {
        // Blocks exist - this is a refinement request
        setInput("");
        await handleChatRefinement(userMessage);
        return;
      }

      if (refineableBlocks.length > 0) {
        if (userMessage.includes("@")) {
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content:
                "No valid blocks found to refine. Try @this, @previous, @next, @all, or @paragraph-1.",
            },
          ]);
          setIsLoading(false);
          return;
        }
        // No valid mentions, but content exists - refine the whole article
        setInput("");
        await handleChatRefinement(
          userMessage,
          refineableBlocks.map((block) => block.clientId),
        );
        return;
      }

      // Blocks don't exist yet - this is article generation
      // User is specifying structure for new article
      setInput("");
      setMessages((prev) => [...prev, { role: "user", content: userMessage }]);
      const fallbackOperationType =
        effectiveAgentMode === "planning" ? "planning" : "generation";
      const fallbackOperationController = beginAgentOperation(
        fallbackOperationType,
        effectiveAgentMode === "planning"
          ? "outline generation"
          : "article generation",
      );
      setIsLoading(true);

      // Add loading timeline entry
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "starting",
          message: "Initializing...",
          timestamp: new Date(),
        },
      ]);

      try {
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/generate-plan",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              topic: userMessage,
              context: "",
              postId: postId,
              sessionId: currentSessionId,
              answers: [],
              autoExecute: effectiveAgentMode !== "planning",
              stream: true,
              articleLength: postConfig.article_length,
              postConfig: postConfig,
              chatHistory: buildChatHistoryPayload(),
            }),
            signal: fallbackOperationController.signal,
          },
        );

        if (!response.ok) {
          const error = await response.json();
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: formatAiErrorMessage(
                error,
                "Failed to generate article",
              ),
              canRetry: true,
              retryType: "generation",
            },
          ]);
          setIsLoading(false);
          return;
        }

        // Handle streaming response
        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();

        while (true) {
          if (
            stopExecutionRef.current ||
            fallbackOperationController.signal.aborted
          ) {
            await reader.cancel().catch(() => {});
            throw new DOMException("Operation stopped by user", "AbortError");
          }
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value, { stream: true });
          const lines = chunk.split("\n");

          for (const line of lines) {
            if (line.startsWith("data: ")) {
              try {
                const data = JSON.parse(line.slice(6));

                if (data.type === "plan") {
                  setCost({ ...cost, session: cost.session + data.cost });
                  if (effectiveAgentMode === "planning" && data.plan) {
                    updateOrCreatePlanMessage(data.plan);
                  }
                } else if (data.type === "title_update") {
                  dispatch("core/editor").editPost({ title: data.title });
                } else if (data.type === "status") {
                  if (data.status === "complete") {
                    continue;
                  }

                  // Update timeline
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: data.status,
                        message: data.message,
                        icon: data.icon,
                      };
                    }
                    return newMessages;
                  });
                } else if (
                  data.type === "conversational" ||
                  data.type === "conversational_stream"
                ) {
                  const cleanContent = (data.content || "")
                    .replace(/~~~ARTICLE~+/g, "")
                    .replace(/~~~ARTICLE~~~[\r\n]*/g, "")
                    .trim();

                  if (
                    !cleanContent ||
                    shouldSkipPlanningCompletion(cleanContent)
                  ) {
                    continue;
                  }

                  const streamTarget =
                    streamTargetRef.current ||
                    resolveStreamTarget(cleanContent);

                  if (!streamTarget) {
                    continue;
                  }

                  streamTargetRef.current = streamTarget;

                  if (streamTarget === "timeline") {
                    updateOrCreateTimelineEntry(cleanContent);
                  } else if (data.type === "conversational") {
                    setMessages((prev) => [
                      ...prev,
                      { role: "assistant", content: cleanContent },
                    ]);
                  } else {
                    setMessages((prev) => {
                      const newMessages = [...prev];
                      const lastIdx = newMessages.length - 1;
                      if (
                        newMessages[lastIdx] &&
                        newMessages[lastIdx].role === "assistant"
                      ) {
                        newMessages[lastIdx] = {
                          ...newMessages[lastIdx],
                          content: cleanContent,
                        };
                      } else {
                        newMessages.push({
                          role: "assistant",
                          content: cleanContent,
                        });
                      }
                      return newMessages;
                    });
                  }
                } else if (data.type === "block") {
                  const { insertBlocks } = dispatch("core/block-editor");
                  let newBlock;

                  if (data.block.blockName === "core/paragraph") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/paragraph", {
                      content: content,
                    });
                  } else if (data.block.blockName === "core/heading") {
                    const level = data.block.attrs?.level || 2;
                    const content =
                      data.block.innerHTML?.match(
                        /<h[1-6]>(.*?)<\/h[1-6]>/,
                      )?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/heading", {
                      level: level,
                      content: content,
                    });
                  } else if (data.block.blockName === "core/list") {
                    const listItems = (data.block.innerBlocks || []).map(
                      (item) => {
                        const content =
                          item.innerHTML?.match(/<li>(.*?)<\/li>/)?.[1] || "";
                        return wp.blocks.createBlock("core/list-item", {
                          content: content,
                        });
                      },
                    );
                    newBlock = wp.blocks.createBlock(
                      "core/list",
                      {
                        ...(data.block.attrs || {}),
                        ordered: data.block.attrs?.ordered || false,
                      },
                      listItems,
                    );
                  } else if (data.block.blockName === "core/quote") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/quote", {
                      value: content,
                    });
                  } else if (data.block.blockName === "core/image") {
                    newBlock = wp.blocks.createBlock(
                      "core/image",
                      data.block.attrs || {},
                    );
                  } else {
                    const parsed = wp.blocks.parse(data.block.innerHTML);
                    newBlock = parsed && parsed.length > 0 ? parsed[0] : null;
                  }

                  if (newBlock) {
                    insertBlocks(newBlock);
                  }
                } else if (data.type === "complete") {
                  applyProviderMetadata(data);
                  setCost({ ...cost, session: cost.session + data.totalCost });

                  // Update timeline to complete
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: "complete",
                        message:
                          effectiveAgentMode === "planning"
                            ? "Outline ready."
                            : "Article generation complete!",
                      };
                    }
                    return newMessages;
                  });

                  // Check for image placeholders and open modal if found
                  if (effectiveAgentMode !== "planning") {
                    setTimeout(() => {
                      const blocks = select("core/block-editor").getBlocks();
                      const imagePlaceholders = blocks.filter(
                        (block) =>
                          block.name === "core/image" &&
                          block.attributes["data-agent-image-id"],
                      );

                      if (imagePlaceholders.length > 0) {
                        window.dispatchEvent(
                          new CustomEvent("wpaw:open-image-review-modal", {
                            detail: {
                              postId: postId,
                              sessionId: currentSessionId,
                              imageCount: imagePlaceholders.length,
                            },
                          }),
                        );
                      }
                    }, 500);
                  }
                } else if (data.type === "error") {
                  throw new Error(data.message);
                }
              } catch (parseError) {
                wpawLog.error(
                  "Failed to parse streaming data:",
                  line,
                  parseError,
                );
              }
            }
          }
        }

        setTimeout(() => {
          setIsLoading(false);
        }, 1500);
      } catch (error) {
        if (isAbortError(error)) {
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "system",
              type: "timeline",
              status: "stopped",
              message: "Generation stopped.",
              timestamp: new Date(),
            },
          ]);
        } else {
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: "Error: " + error.message,
            },
          ]);
        }
        setIsLoading(false);
      } finally {
        finishAgentOperation(fallbackOperationType);
      }
    };

    // Submit answers and continue generation.
    const submitAnswers = async () => {
      if (isLoading) {
        return;
      }

      const resolvedPostConfig = buildPostConfigFromAnswers(answers);

      // Process config answers and update post config
      // Handle language selection
      if (answers.config_language) {
        let languageValue = answers.config_language;
        // Handle custom language input
        if (languageValue === "__custom__" && answers.config_language_custom) {
          languageValue = answers.config_language_custom.toLowerCase().trim();
        }
        if (languageValue && languageValue !== "__skipped__") {
          updatePostConfig("language", languageValue);
        }
      }

      // Handle other config settings
      if (answers.config_all) {
        try {
          const configData = JSON.parse(answers.config_all);

          // Apply config to post config
          if (configData.web_search !== undefined) {
            updatePostConfig("web_search", configData.web_search);
          }
          if (configData.seo !== undefined) {
            updatePostConfig("seo_enabled", configData.seo);
          }
          if (configData.focus_keyword) {
            updatePostConfig("focus_keyword", configData.focus_keyword);
            updatePostConfig("seo_focus_keyword", configData.focus_keyword);
          }
          if (configData.secondary_keywords) {
            updatePostConfig(
              "seo_secondary_keywords",
              configData.secondary_keywords,
            );
          }
        } catch (e) {
          wpawLog.error("Failed to parse config answers:", e);
        }
      }

      if (clarificationMode === "refinement" && pendingRefinement) {
        setInClarification(false);
        const clarificationContext = formatClarificationContext(
          questions,
          answers,
        );
        const refinedMessage = `${pendingRefinement.message}${clarificationContext}`;
        const blocks = pendingRefinement.blocks || [];
        setPendingRefinement(null);
        setClarificationMode("generation");
        await handleChatRefinement(refinedMessage, blocks, {
          skipUserMessage: true,
        });
        return;
      }

      const submissionMode = agentMode || "chat";
      const submissionOperationType =
        submissionMode === "planning" ? "planning" : "generation";
      const submissionOperationController = beginAgentOperation(
        submissionOperationType,
        submissionMode === "planning"
          ? "outline generation"
          : "article generation",
      );
      setIsLoading(true);

      // Exit quiz mode and return to chat immediately so user can see progress
      setInClarification(false);

      // Add timeline entry showing generation is starting
      setMessages((prev) => [
        ...deactivateActiveTimelineEntries(prev),
        {
          role: "system",
          type: "timeline",
          status: "starting",
          message:
            submissionMode === "planning"
              ? "Creating outline..."
              : "Generating article...",
          timestamp: new Date(),
        },
      ]);

      let timeout = null;
      try {
        const topic =
          getLastUserMessageText() ||
          messages
            .map((m) => (typeof m.content === "string" ? m.content : ""))
            .filter(Boolean)
            .join("\n");

        const response = await fetch(
          wpAgenticWriter.apiUrl + "/generate-plan",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({
              topic: topic,
              context: "",
              postId: postId,
              sessionId: currentSessionId,
              clarificationAnswers: answers,
              autoExecute: submissionMode !== "planning",
              stream: true,
              articleLength: resolvedPostConfig.article_length,
              detectedLanguage: detectedLanguage,
              postConfig: resolvedPostConfig,
              chatHistory: buildChatHistoryPayload(),
            }),
            signal: submissionOperationController.signal,
          },
        );

        if (!response.ok) {
          const error = await response.json();
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: formatAiErrorMessage(error, "Failed to generate plan"),
              canRetry: true,
              retryType: "generation",
            },
          ]);
          setIsLoading(false);
          return;
        }

        // Handle streaming response (similar to sendMessage)
        streamTargetRef.current = null;
        const reader = registerActiveReader(response.body.getReader());
        const decoder = new TextDecoder();

        // Add timeout to detect hanging responses
        timeout = setTimeout(() => {
          if (isLoading) {
            wpawLog.error("Generation timeout - no response received");
            setMessages((prev) => [
              ...prev,
              {
                role: "system",
                type: "error",
                content: formatAiErrorMessage(
                  "cURL error 28: Operation timed out after 120000 milliseconds",
                  "Failed to generate plan",
                ),
                canRetry: true,
                retryType: "generation",
              },
            ]);
            setIsLoading(false);
            reader.cancel();
          }
        }, 120000); // 2 minute timeout

        while (true) {
          if (
            stopExecutionRef.current ||
            submissionOperationController.signal.aborted
          ) {
            await reader.cancel().catch(() => {});
            throw new DOMException("Operation stopped by user", "AbortError");
          }
          const { done, value } = await reader.read();
          if (done) break;

          const chunk = decoder.decode(value, { stream: true });
          const lines = chunk.split("\n");

          for (const line of lines) {
            if (line.startsWith("data: ")) {
              try {
                const data = JSON.parse(line.slice(6));

                if (data.type === "plan") {
                  setCost({ ...cost, session: cost.session + data.cost });
                  if (submissionMode === "planning" && data.plan) {
                    updateOrCreatePlanMessage(data.plan);
                  }
                } else if (data.type === "title_update") {
                  dispatch("core/editor").editPost({ title: data.title });
                } else if (data.type === "status") {
                  if (data.status === "complete") {
                    continue;
                  }

                  // Update timeline
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: data.status,
                        message: data.message,
                        icon: data.icon,
                      };
                    }
                    return newMessages;
                  });
                } else if (
                  data.type === "conversational" ||
                  data.type === "conversational_stream"
                ) {
                  // Remove article marker and clean content
                  const cleanContent = (data.content || "")
                    .replace(/~~~ARTICLE~+/g, "")
                    .replace(/~~~ARTICLE~~~[\r\n]*/g, "")
                    .trim();

                  // Skip if content is empty after cleaning
                  if (
                    !cleanContent ||
                    shouldSkipPlanningCompletion(cleanContent)
                  ) {
                    continue;
                  }

                  const streamTarget =
                    streamTargetRef.current ||
                    resolveStreamTarget(cleanContent);

                  if (!streamTarget) {
                    continue;
                  }

                  streamTargetRef.current = streamTarget;

                  if (streamTarget === "timeline") {
                    updateOrCreateTimelineEntry(cleanContent);
                  } else {
                    // This is actual conversational content - add as chat bubble
                    if (data.type === "conversational") {
                      setMessages((prev) => [
                        ...prev,
                        { role: "assistant", content: cleanContent },
                      ]);
                    } else {
                      setMessages((prev) => {
                        const newMessages = [...prev];
                        const lastIdx = newMessages.length - 1;
                        if (
                          newMessages[lastIdx] &&
                          newMessages[lastIdx].role === "assistant"
                        ) {
                          newMessages[lastIdx] = {
                            ...newMessages[lastIdx],
                            content: cleanContent,
                          };
                        } else {
                          newMessages.push({
                            role: "assistant",
                            content: cleanContent,
                          });
                        }
                        return newMessages;
                      });
                    }
                  }
                } else if (data.type === "block") {
                  // Insert blocks (same as above)
                  const { insertBlocks } = dispatch("core/block-editor");
                  let newBlock;

                  if (data.block.blockName === "core/paragraph") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/paragraph", {
                      content: content,
                    });
                  } else if (data.block.blockName === "core/heading") {
                    const level = data.block.attrs?.level || 2;
                    const content =
                      data.block.innerHTML?.match(
                        /<h[1-6]>(.*?)<\/h[1-6]>/,
                      )?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/heading", {
                      level: level,
                      content: content,
                    });
                  } else if (data.block.blockName === "core/list") {
                    const listItems = (data.block.innerBlocks || []).map(
                      (item) => {
                        const content =
                          item.innerHTML?.match(/<li>(.*?)<\/li>/)?.[1] || "";
                        return wp.blocks.createBlock("core/list-item", {
                          content: content,
                        });
                      },
                    );
                    newBlock = wp.blocks.createBlock(
                      "core/list",
                      {
                        ...(data.block.attrs || {}),
                        ordered: data.block.attrs?.ordered || false,
                      },
                      listItems,
                    );
                  } else if (data.block.blockName === "core/quote") {
                    const content =
                      data.block.innerHTML?.match(/<p>(.*?)<\/p>/)?.[1] || "";
                    newBlock = wp.blocks.createBlock("core/quote", {
                      value: content,
                    });
                  } else if (data.block.blockName === "core/image") {
                    newBlock = wp.blocks.createBlock(
                      "core/image",
                      data.block.attrs || {},
                    );
                  }

                  if (newBlock) {
                    insertBlocks(newBlock);
                  }
                } else if (data.type === "complete") {
                  applyProviderMetadata(data);
                  clearTimeout(timeout);
                  setCost({ ...cost, session: cost.session + data.totalCost });

                  // Update timeline to complete
                  setMessages((prev) => {
                    const newMessages = [...prev];
                    const lastTimelineIndex =
                      findLastActiveTimelineIndex(newMessages);
                    if (lastTimelineIndex !== -1) {
                      newMessages[lastTimelineIndex] = {
                        ...newMessages[lastTimelineIndex],
                        status: "complete",
                        message:
                          submissionMode === "planning"
                            ? "Outline ready."
                            : "Article generated successfully!",
                      };
                    }
                    return newMessages;
                  });
                  setIsLoading(false);
                } else if (data.type === "error") {
                  clearTimeout(timeout);
                  setMessages((prev) => [
                    ...prev,
                    {
                      role: "system",
                      type: "error",
                      content: formatAiErrorMessage(
                        data.message ||
                          "An error occurred during article generation",
                        "Failed to generate plan",
                      ),
                      canRetry: true,
                      retryType: "generation",
                    },
                  ]);
                  setIsLoading(false);
                }
              } catch (parseError) {
                wpawLog.error(
                  "Failed to parse streaming data:",
                  line,
                  parseError,
                );
              }
            }
          }
          // Clear timeout when streaming completes normally
          clearTimeout(timeout);
        }
      } catch (error) {
        if (timeout) {
          clearTimeout(timeout);
        }
        if (isAbortError(error)) {
          setMessages((prev) => [
            ...deactivateActiveTimelineEntries(prev),
            {
              role: "system",
              type: "timeline",
              status: "stopped",
              message: "Generation stopped.",
              timestamp: new Date(),
            },
          ]);
        } else {
          wpawLog.error("Article generation error:", error);
          setMessages((prev) => [
            ...prev,
            {
              role: "system",
              type: "error",
              content: formatAiErrorMessage(
                error,
                "Failed to generate article",
              ),
              canRetry: true,
              retryType: "generation",
            },
          ]);
        }
        setIsLoading(false);
      } finally {
        finishAgentOperation(submissionOperationType);
      }
    };

    // Render clarification quiz UI.
    const renderClarification = () => {
      if (!inClarification || questions.length === 0) {
        return null;
      }

      const currentQuestion = questions[currentQuestionIndex];
      const currentAnswer = answers[currentQuestion.id] || "";

      // Helper to render single choice options
      const renderSingleChoice = () => {
        const customInputKey = `${currentQuestion.id}_custom`;
        const customValue = answers[customInputKey] || "";
        const isCustomSelected = currentAnswer === "__custom__";

        return <div className={"wpaw-answer-options"}>
  {currentQuestion.options.map((option, idx) => {
              const isSelected = currentAnswer === option.value;
              return <label key={idx}>
    <input
      type={"radio"}
      name={currentQuestion.id}
      checked={isSelected}
      onChange={() => {
                      const newAnswers = { ...answers };
                      newAnswers[currentQuestion.id] = option.value;
                      setAnswers(newAnswers);
                    }}
     />
    <span>
      {option.value}
    </span>
  </label>;
            })}
  {/* Add custom text input option */}
  {
            <div className={"wpaw-custom-answer-wrapper"} key={"custom"}>
    <label>
      <input
        type={"radio"}
        name={currentQuestion.id}
        checked={isCustomSelected}
        onChange={() => {
                        const newAnswers = { ...answers };
                        newAnswers[currentQuestion.id] = "__custom__";
                        setAnswers(newAnswers);
                      }}
       />
      <span>
        {"Other (specify):"}
      </span>
    </label>
    {isCustomSelected &&
                  <textarea
      className={"wpaw-custom-text-input"}
      placeholder={"Type your answer here..."}
      value={customValue}
      rows={3}
      onChange={(e) => {
                      const newAnswers = { ...answers };
                      newAnswers[customInputKey] = e.target.value;
                      setAnswers(newAnswers);
                    }}
      autoFocus={true}
      style={{ resize: "vertical" }}
     />}
  </div>}
</div>;
      };

      // Helper to render multiple choice options
      const renderMultipleChoice = () => {
        const selectedValues = currentAnswer ? currentAnswer.split(", ") : [];

        return <div className={"wpaw-answer-options"}>
  {currentQuestion.options.map((option, idx) => {
              const isSelected = selectedValues.includes(option.value);
              return <label key={idx}>
    <input
      type={"checkbox"}
      checked={isSelected}
      onChange={() => {
                      const newAnswers = { ...answers };
                      let newSelected = isSelected
                        ? selectedValues.filter((v) => v !== option.value)
                        : [...selectedValues, option.value];
                      newAnswers[currentQuestion.id] = newSelected.join(", ");
                      setAnswers(newAnswers);
                    }}
     />
    <span>
      {option.value}
    </span>
  </label>;
            })}
</div>;
      };

      // Helper to render open text textarea
      const renderOpenText = () => {
        return <div className={"wpaw-answer-options"}>
  <TextareaControl
    placeholder={currentQuestion.placeholder || "Type your answer here..."}
    value={currentAnswer}
    onChange={(value) => {
                const newAnswers = { ...answers };
                newAnswers[currentQuestion.id] = value;
                setAnswers(newAnswers);
              }}
    rows={4}
    maxLength={currentQuestion.max_length || 500}
   />
</div>;
      };

      // Helper to render config form (consolidated config page)
      const renderConfigForm = () => {
        // Initialize with defaults if no answer exists
        let configData = {};
        if (currentAnswer) {
          try {
            configData = JSON.parse(currentAnswer);
          } catch (e) {
            configData = {};
          }
        }

        // Set defaults from field definitions if not already set
        const fields = currentQuestion.fields || [];
        fields.forEach((field) => {
          if (
            configData[field.id] === undefined &&
            field.default !== undefined
          ) {
            configData[field.id] = field.default;
          }
        });

        // Initialize answer with defaults on first render
        if (!currentAnswer && Object.keys(configData).length > 0) {
          const newAnswers = { ...answers };
          newAnswers[currentQuestion.id] = JSON.stringify(configData);
          setAnswers(newAnswers);
        }

        return <div className={"wpaw-config-form"}>
  {fields.map((field, idx) => {
              const fieldValue =
                configData[field.id] !== undefined
                  ? configData[field.id]
                  : field.default;
              const isConditional =
                field.conditional && !configData[field.conditional];

              if (isConditional) {
                return null;
              }

              return <div key={idx} className={"wpaw-config-field"}>
    {field.type === "toggle"
                    ? <React.Fragment>
      <label className={"wpaw-config-label"}>
        <span className={"wpaw-config-label-text"}>
          {field.label}
        </span>
        {field.description &&
                                <span className={"wpaw-config-description"}>
          {field.description}
        </span>}
      </label>
      <label className={"wpaw-config-toggle"}>
        <input
          type={"checkbox"}
          checked={fieldValue || false}
          onChange={(e) => {
                                  const newConfig = { ...configData };
                                  newConfig[field.id] = e.target.checked;
                                  const newAnswers = { ...answers };
                                  newAnswers[currentQuestion.id] =
                                    JSON.stringify(newConfig);
                                  setAnswers(newAnswers);
                                }}
         />
        <span className={"wpaw-toggle-slider"} />
      </label>
    </React.Fragment>
                    : <React.Fragment>
      <label className={"wpaw-config-label"}>
        <span className={"wpaw-config-label-text"}>
          {field.label}
        </span>
        {field.description &&
                                <span className={"wpaw-config-description"}>
          {field.description}
        </span>}
      </label>
      <input
        type={"text"}
        className={"wpaw-config-text-input"}
        placeholder={field.placeholder || ""}
        value={fieldValue || ""}
        maxLength={field.max_length || 200}
        onChange={(e) => {
                              const newConfig = { ...configData };
                              newConfig[field.id] = e.target.value;
                              const newAnswers = { ...answers };
                              newAnswers[currentQuestion.id] =
                                JSON.stringify(newConfig);
                              setAnswers(newAnswers);
                            }}
       />
    </React.Fragment>}
  </div>;
            })}
</div>;
      };

      // Render appropriate input type based on question type
      let answerInput;
      switch (currentQuestion.type) {
        case "single_choice":
          answerInput = renderSingleChoice();
          break;
        case "multiple_choice":
          answerInput = renderMultipleChoice();
          break;
        case "open_text":
          answerInput = renderOpenText();
          break;
        case "config_form":
          answerInput = renderConfigForm();
          break;
        default:
          answerInput = renderSingleChoice();
      }

      return <div className={"wpaw-clarification-quiz dark-theme"}>
  <div className={"wpaw-quiz-header"}>
    <h3>
      {" Clarification Questions"}
    </h3>
    <div className={"wpaw-progress-bar"}>
      <div
        className={"wpaw-progress-fill"}
        style={{
                      width:
                        ((currentQuestionIndex + 1) / questions.length) * 100 + "%",
                    }}
       />
    </div>
    <span>
      {`${currentQuestionIndex + 1} of ${questions.length}`}
    </span>
  </div>
  <div className={"wpaw-question-card"}>
    <h4>
      {currentQuestion.question}
    </h4>
    {answerInput}
    <div className={"wpaw-quiz-actions"}>
      {/* Previous button */}
      {
                  currentQuestionIndex > 0 &&
                    <Button
        isSecondary={true}
        onClick={() =>
                          setCurrentQuestionIndex(currentQuestionIndex - 1)}
        disabled={isLoading}
      >
        {"Previous"}
      </Button>}
      {/* Skip button for optional questions */}
      {
                  <Button
        isSecondary={true}
        onClick={() => {
                        const newAnswers = { ...answers };
                        newAnswers[currentQuestion.id] = "__skipped__";
                        setAnswers(newAnswers);
                        if (currentQuestionIndex === questions.length - 1) {
                          submitAnswers();
                        } else {
                          setCurrentQuestionIndex(currentQuestionIndex + 1);
                        }
                      }}
        disabled={isLoading}
      >
        {"Skip"}
      </Button>}
      {/* Continue/Finish button */}
      {
                  <Button
        isPrimary={true}
        onClick={() => {
                        if (currentQuestionIndex === questions.length - 1) {
                          submitAnswers();
                        } else {
                          setCurrentQuestionIndex(currentQuestionIndex + 1);
                        }
                      }}
        disabled={isLoading ||
                        (!currentAnswer.trim() && currentAnswer !== "__custom__")}
      >
        {currentQuestionIndex === questions.length - 1 ? "Finish" : "Next"}
      </Button>}
    </div>
  </div>
</div>;
    };

    const startNewConversation = async () => {
      if (isLoading || isSessionActionLoading) {
        return;
      }
      try {
        setIsSessionActionLoading(true);
        const response = await fetch(
          wpAgenticWriter.apiUrl + "/conversations",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
            body: JSON.stringify({ post_id: postId || 0 }),
          },
        );
        if (!response.ok) {
          throw new Error("Failed to create a new conversation");
        }
        const data = await response.json();
        // Fully reset state for clean slate
        // Release lock on previous session + cancel pending debounce
        if (currentSessionId) {
          stopLockHeartbeat();
          releaseSessionLock(currentSessionId);
        }
        if (messagesSaveTimeoutRef.current) {
          clearTimeout(messagesSaveTimeoutRef.current);
          messagesSaveTimeoutRef.current = null;
        }
        isHydratingSessionRef.current = true;
        if (data?.session_id) {
          setCurrentSessionId(data.session_id);
        }
        lastPersistedMessagesRef.current = JSON.stringify([]);
        setMessages([]);
        currentPlanRef.current = null;
        setAgentMode("chat");
        setShowWelcome(false);
        setFocusKeywordSuggestions([]);
        setSelectedFocusKeyword("");
        setProviderInfo(null);
        setMemantoRestore({
          restored: false,
          summary: "",
          memories: [],
          preferences: [],
          systemMessage: "",
        });
        // loadPostSessions filters out sessions with 0 messages, so the brand-new
        // session won't appear. Load existing sessions, then prepend the new one.
        const existingSessions = await loadPostSessions();
        if (data?.session_id) {
          setAvailableSessions((prev) => {
            // Avoid duplicating if somehow already present
            if (prev.some((s) => s?.session_id === data.session_id)) {
              return prev;
            }
            return [data, ...prev];
          });
          // Acquire lock on the new session
          acquireSessionLock(data.session_id).then((acquired) => {
            if (acquired) startLockHeartbeat(data.session_id);
          });
        }
        setTimeout(() => {
          isHydratingSessionRef.current = false;
          // Focus input
          if (inputRef.current) {
            inputRef.current.focus();
          }
        }, 50);
      } catch (error) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: Failed to start a new conversation.",
          },
        ]);
      } finally {
        setIsSessionActionLoading(false);
      }
    };

    const deleteConversationSession = async (sessionId) => {
      if (!sessionId || isSessionActionLoading) {
        return;
      }
      if (!window.confirm("Delete this session permanently?")) {
        return;
      }
      try {
        setIsSessionActionLoading(true);
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/conversations/${sessionId}`,
          {
            method: "DELETE",
            headers: {
              "X-WP-Nonce": wpAgenticWriter.nonce,
            },
          },
        );
        if (!response.ok) {
          throw new Error("Failed to delete session");
        }
        const sessions = await loadPostSessions();
        if (currentSessionId === sessionId) {
          const replacement = sessions[0]?.session_id || "";
          setCurrentSessionId(replacement);
          setMessages(
            Array.isArray(sessions[0]?.messages) ? sessions[0].messages : [],
          );
        }
      } catch (error) {
        setMessages((prev) => [
          ...prev,
          {
            role: "system",
            type: "error",
            content: "Error: Failed to delete session.",
          },
        ]);
      } finally {
        setIsSessionActionLoading(false);
      }
    };

    const getSessionDisplayTitle = (session, index) => {
      if (session?.title && session.title.trim()) {
        return session.title.trim();
      }
      const firstUser = Array.isArray(session?.messages)
        ? session.messages.find(
            (m) =>
              m?.role === "user" &&
              typeof m?.content === "string" &&
              m.content.trim(),
          )
        : null;
      if (firstUser?.content) {
        return firstUser.content.trim().slice(0, 56);
      }
      const updatedRaw = session?.updated_at || session?.last_activity || "";
      if (updatedRaw) {
        const d = new Date(updatedRaw);
        if (!Number.isNaN(d.getTime())) {
          return `Session ${index + 1} - ${d.toLocaleDateString()}`;
        }
      }
      return `Session ${index + 1}`;
    };
    const getSessionContinuityLabel = (session) => {
      const status = String(session?.status || "active").toLowerCase();
      if (status === "completed") {
        return "Continuable";
      }
      if (status === "archived") {
        return "Archived";
      }
      return "Active";
    };
    const getSessionDebugMeta = (session) => {
      const id = Number(session?.id || 0);
      const sid = String(session?.session_id || "-");
      const pid = Number(session?.post_id || 0);
      const sessionStatus = String(session?.status || "active");
      const postStatus = String(session?.post_status || "").toLowerCase();
      const statusLabel = pid === 0 ? "unassigned" : postStatus || "unknown";
      return `id: ${id || "-"} | sid: ${sid} | post_id: ${pid} | post: ${statusLabel} | session: ${sessionStatus}`;
    };

    // Render Welcome Screen (chatty, friendly)
    const renderWelcomeScreen = () => {
      const recentSession =
        availableSessions.length > 0 ? availableSessions[0] : null;

      return <div className={"wpaw-welcome-screen"}>
  <div className={"wpaw-welcome-content"}>
    <span
      className={"wpaw-welcome-icon"}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M12 5a3 3 0 1 0-5.997.125a4 4 0 0 0-2.526 5.77a4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4M6.003 5.125A3 3 0 0 0 6.401 6.5m-2.924 4.396a4 4 0 0 1 .585-.396M6 18a4 4 0 0 1-1.967-.516M12 13h4m-4 5h6a2 2 0 0 1 2 2v1M12 8h8m-4 0V5a2 2 0 0 1 2-2"/><circle cx="16" cy="13" r=".5"/><circle cx="18" cy="3" r=".5"/><circle cx="20" cy="21" r=".5"/><circle cx="20" cy="8" r=".5"/></g></svg>',
                }}
     />
    <h2 className={"wpaw-welcome-title"}>
      {"Agentic Writer"}
    </h2>
    <p className={"wpaw-welcome-subtitle"}>
      {"What are we writing today?"}
    </p>
    {/* Show single "Continue last conversation" button if available */}
    {
              recentSession &&
                <button
      className={"wpaw-welcome-pill"}
      style={{ width: "100%", marginBottom: "12px" }}
      disabled={isSessionActionLoading}
      onClick={() => openSessionById(recentSession.session_id || "")}
    >
      {`↩ Continue: ${getSessionDisplayTitle(recentSession, 0)}`}
    </button>}
    {/* Show older sessions in collapsible */}
    {
              availableSessions.length > 1 &&
                <details style={{ marginBottom: "12px", width: "100%" }}>
      <summary
        style={{
                          fontSize: "12px",
                          color: "#8b95a5",
                          cursor: "pointer",
                          marginBottom: "8px",
                        }}
      >
        {`${availableSessions.length - 1} more session${availableSessions.length > 2 ? "s" : ""}`}
      </summary>
      <div className={"wpaw-session-list"}>
        {availableSessions.slice(1).map((session, idx) =>
                          <div
          key={session.session_id || idx}
          className={"wpaw-welcome-pill"}
          style={{
                                width: "100%",
                                marginBottom: "6px",
                                display: "flex",
                                alignItems: "center",
                                justifyContent: "space-between",
                                gap: "8px",
                              }}
        >
          <button
            type={"button"}
            disabled={isSessionActionLoading}
            className={"wpaw-session-open-btn"}
            style={{
                                    flex: 1,
                                    background: "transparent",
                                    border: "none",
                                    color: "inherit",
                                    textAlign: "left",
                                    cursor: isSessionActionLoading ? "wait" : "pointer",
                                  }}
            onClick={() =>
                                    openSessionById(session.session_id || "")}
          >
            <div>
              {getSessionDisplayTitle(session, idx + 1)}
            </div>
            <div style={{ opacity: 0.7, fontSize: "11px" }}>
              {`${Number(session?.message_count ?? (Array.isArray(session?.messages) ? session.messages.length : 0))} msgs · ${getSessionContinuityLabel(session)}`}
            </div>
          </button>
          <button
            type={"button"}
            title={"Delete session"}
            disabled={isSessionActionLoading}
            style={{
                                    background: "transparent",
                                    border: "1px solid rgba(255,255,255,0.25)",
                                    color: "inherit",
                                    borderRadius: "6px",
                                    padding: "2px 6px",
                                    cursor: "pointer",
                                  }}
            onClick={() =>
                                    deleteConversationSession(session.session_id)}
          >
            {"×"}
          </button>
        </div>,
                        )}
      </div>
    </details>}
    {/* Focus keyword input */}
    {
              <input
      type={"text"}
      className={"wpaw-welcome-input"}
      placeholder={"Focus keyword (optional)"}
      value={welcomeKeywordInput}
      onChange={(e) => setWelcomeKeywordInput(e.target.value)}
      onKeyDown={(e) => {
                  if (e.key === "Enter") {
                    handleWelcomeStart();
                  }
                }}
     />}
    {/* Mode pills */}
    {
              <div className={"wpaw-welcome-pills"}>
      <button
        className={"wpaw-welcome-pill" +
                        (welcomeStartMode === "chat" ? " active" : "")}
        onClick={() => setWelcomeStartMode("chat")}
      >
        {"Explore First"}
      </button>
      <button
        className={"wpaw-welcome-pill" +
                        (welcomeStartMode === "planning" ? " active" : "")}
        onClick={() => setWelcomeStartMode("planning")}
      >
        {"Start Outline"}
      </button>
    </div>}
    {/* Start button */}
    {
              <Button
      isPrimary={true}
      onClick={handleWelcomeStart}
      className={"wpaw-welcome-start-btn"}
    >
      {"Start Writing"}
    </Button>}
  </div>
</div>;
    };

    // Render Writing mode empty state
    const renderWritingEmptyState = () => {
      return <div className={"wpaw-writing-empty-state"}>
  <div className={"wpaw-empty-state-content"}>
    <span
      className={"wpaw-empty-state-icon"}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="width: 60px; height: 60px;"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M12 5a3 3 0 1 0-5.997.125a4 4 0 0 0-2.526 5.77a4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4M6.003 5.125A3 3 0 0 0 6.401 6.5m-2.924 4.396a4 4 0 0 1 .585-.396M6 18a4 4 0 0 1-1.967-.516M12 13h4m-4 5h6a2 2 0 0 1 2 2v1M12 8h8m-4 0V5a2 2 0 0 1 2-2"/><circle cx="16" cy="13" r=".5"/><circle cx="18" cy="3" r=".5"/><circle cx="20" cy="21" r=".5"/><circle cx="20" cy="8" r=".5"/></g></svg>',
                }}
     />
    <h3>
      {"Create an Outline First"}
    </h3>
    <p>
      {"Before writing, the agent needs an outline to structure the article and keep costs predictable. Ask for the article topic and the agent will create one first."}
    </p>
    <Button
      isPrimary={true}
      onClick={() => setAgentMode("planning")}
      className={"wpaw-empty-state-button"}
    >
      <div
        style={{
                        display: "inline-flex",
                        alignItems: "center",
                        gap: "8px",
                      }}
      >
        <svg
          xmlns={"http://www.w3.org/2000/svg"}
          width={"18"}
          height={"18"}
          viewBox={"0 0 24 24"}
        >
          <path
            fill={"none"}
            stroke={"currentColor"}
            strokeLinecap={"round"}
            strokeLinejoin={"round"}
            strokeWidth={"1"}
            d={"M16 5H3m13 7H3m8 7H3m12-1l2 2l4-4"}
           />
        </svg>
        {"Create Outline"}
      </div>
    </Button>
    <p
      className={"wpaw-empty-state-hint"}
      style={{ marginTop: "16px", fontSize: "13px", color: "#a7aaad" }}
    >
      {"Tip: tell the agent what you want to publish, then approve or adjust the outline before writing."}
    </p>
  </div>
</div>;
    };

    // Render Focus Keyword Bar (replaces context indicator)
    const renderFocusKeywordBar = () => {
      const hasKeyword =
        selectedFocusKeyword && selectedFocusKeyword.length > 0;

      // Expanded mode
      if (isTextareaExpanded) {
        return <div className={"wpaw-focus-keyword-bar wpaw-expanded"}>
  {/* Header */}
  {
            <div className={"wpaw-fk-header"}>
    <span>
      {"🎯 FOCUS KEYWORD"}
    </span>
    <button
      className={"wpaw-fk-collapse"}
      onClick={() => setIsTextareaExpanded(false)}
      title={"Collapse"}
    >
      {"↓"}
    </button>
  </div>}
  {/* Main input - always show input field in expanded mode */}
  {
            <div className={"wpaw-fk-main-input"}>
    <input
      type={"text"}
      className={"wpaw-fk-custom-input"}
      placeholder={hasKeyword
                    ? "Edit focus keyword..."
                    : "Enter focus keyword..."}
      value={selectedFocusKeyword || ""}
      onChange={(e) => {
                    const value = e.target.value;
                    setSelectedFocusKeyword(value);
                  }}
      onBlur={(e) => {
                    // Save on blur
                    if (e.target.value !== postConfig.focus_keyword) {
                      handleFocusKeywordChange(e.target.value);
                    }
                  }}
      onKeyDown={(e) => {
                    if (e.key === "Enter" && e.target.value.trim()) {
                      handleFocusKeywordChange(e.target.value.trim());
                      e.target.blur();
                    }
                  }}
     />
  </div>}
  {/* Suggestions list */}
  {
            focusKeywordSuggestions.length > 0 &&
              <div className={"wpaw-fk-suggestions"}>
    <div className={"wpaw-fk-suggestions-label"}>
      {"📝 AI Suggestions:"}
    </div>
    {focusKeywordSuggestions.map((kw, i) =>
                    <div
      key={i}
      className={"wpaw-fk-suggestion-item" +
                          (kw === selectedFocusKeyword ? " selected" : "")}
      onClick={() => handleFocusKeywordChange(kw)}
    >
      <span className={"wpaw-fk-radio"}>
        {kw === selectedFocusKeyword ? "●" : "○"}
      </span>
      <span className={"wpaw-fk-suggestion-text"}>
        {kw}
      </span>
      <span className={"wpaw-fk-suggestion-source"}>
        {`(#${i + 1})`}
      </span>
    </div>,
                  )}
  </div>}
  {/* Stats */}
  {
            <div className={"wpaw-fk-stats"}>
    <span>
      {`💰 $${(cost.session || 0).toFixed(4)}`}
    </span>
    {providerInfo &&
                  <span
      className={"wpaw-provider-info"}
      title={providerInfo.warnings.length > 0
                          ? providerInfo.warnings.join("; ")
                          : "AI provider used"}
    >
      {providerInfo.fallbackUsed
                        ? " ⚠️ " + (providerInfo.provider || "fallback")
                        : " 📡 " + (providerInfo.provider || "AI")}
    </span>}
    <span className={"wpaw-fk-divider"}>
      {"│"}
    </span>
    <span>
      {`📊 ~${messages.filter((m) => m.role !== "system").length * 500} tokens`}
    </span>
  </div>}
</div>;
      }

      // Compact mode (default) - use input instead of dropdown
      return <div className={"wpaw-focus-keyword-bar wpaw-compact"}>
  <div className={"wpaw-fk-left"}>
    <span className={"wpaw-fk-icon"}>
      {"🎯"}
    </span>
    <input
      type={"text"}
      className={"wpaw-fk-input"}
      placeholder={"Enter focus keyword..."}
      value={selectedFocusKeyword || ""}
      onChange={(e) => {
                  const value = e.target.value;
                  setSelectedFocusKeyword(value);
                  // Debounce save to config
                  if (configSaveTimeoutRef.current) {
                    clearTimeout(configSaveTimeoutRef.current);
                  }
                  configSaveTimeoutRef.current = setTimeout(() => {
                    handleFocusKeywordChange(value);
                  }, 500);
                }}
      onBlur={(e) => {
                  // Save immediately on blur
                  if (e.target.value !== postConfig.focus_keyword) {
                    handleFocusKeywordChange(e.target.value);
                  }
                }}
      disabled={isLoading}
     />
  </div>
  <span className={"wpaw-fk-cost"}>
    {`$${(cost.session || 0).toFixed(4)}`}
    {providerInfo &&
                <span
      className={"wpaw-provider-badge"}
      title={providerInfo.warnings.length > 0
                        ? providerInfo.warnings.join("; ")
                        : "AI provider"}
    >
      {providerInfo.fallbackUsed ? "⚠" : "📡"}
    </span>}
  </span>
  <button
    className={"wpaw-fk-expand"}
    onClick={() => setIsTextareaExpanded(true)}
    title={"Expand"}
  >
    <svg
      xmlns={"http://www.w3.org/2000/svg"}
      width={"16"}
      height={"16"}
      viewBox={"0 0 24 24"}
    >
      <path
        fill={"none"}
        stroke={"currentColor"}
        strokeLinecap={"round"}
        strokeLinejoin={"round"}
        strokeWidth={"1.5"}
        d={"m7 15l5 5l5-5M7 9l5-5l5 5"}
       />
    </svg>
  </button>
</div>;
    };

    const renderAgentWorkspaceCard = () => {
      const planSummary = getPlanRuntimeSummary();
      const messageCount = messages.filter(
        (m) => m.role === "user" || m.role === "assistant",
      ).length;
      const providerLabel = providerInfo?.provider
        ? `${providerInfo.provider}${providerInfo.model ? ` / ${providerInfo.model}` : ""}`
        : "Provider not used yet";
      const activeWorkspaceStatus =
        activeOperation.status && activeOperation.status !== "idle"
          ? activeOperation.status
          : writingState.status || "idle";
      const writingLabel =
        activeOperation.status && activeOperation.status !== "idle"
          ? `${activeOperation.status === "stopping" ? "Stopping" : "Running"} ${activeOperation.label || activeOperation.type}`
          : isWritingStateLoading
            ? "Loading..."
            : String(writingState.status || "idle").replace(/_/g, " ");
      const canResume =
        !isLoading &&
        currentPlanRef.current &&
        ["in_progress", "paused", "failed"].includes(writingState.status);
      const selectedPreview = workspaceSnapshot.selectedBlockPreview
        ? `: ${workspaceSnapshot.selectedBlockPreview}`
        : "";

      return <div
  className={`wpaw-agent-workspace-card${isWorkspaceCollapsed ? " is-collapsed" : ""}`}
>
  <div className={"wpaw-agent-workspace-header"}>
    <div className={"wpaw-agent-workspace-heading"}>
      <div className={"wpaw-agent-workspace-kicker"}>
        {"Agent Workspace"}
      </div>
      <div className={"wpaw-agent-workspace-title"}>
        {workspaceSnapshot.title || "Untitled draft"}
      </div>
    </div>
    <div className={"wpaw-agent-workspace-actions"}>
      <span className={`wpaw-agent-workspace-status status-${activeWorkspaceStatus}`}>
        {writingLabel}
      </span>
    </div>
  </div>
  {!isWorkspaceCollapsed &&
            <div className={"wpaw-agent-context-grid"}>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Post blocks"}
      </span>
      <strong>
        {String(workspaceSnapshot.blockCount || 0)}
      </strong>
    </div>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Outline"}
      </span>
      <strong>
        {planSummary.label}
      </strong>
    </div>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Selected"}
      </span>
      <strong>
        {`${workspaceSnapshot.selectedBlockLabel}${selectedPreview}`}
      </strong>
    </div>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Focus keyword"}
      </span>
      <input
        type={"text"}
        className={"wpaw-agent-keyword-input"}
        placeholder={"Optional"}
        value={selectedFocusKeyword || ""}
        onChange={(e) => {
                        const value = e.target.value;
                        setSelectedFocusKeyword(value);
                        if (configSaveTimeoutRef.current) {
                          clearTimeout(configSaveTimeoutRef.current);
                        }
                        configSaveTimeoutRef.current = setTimeout(() => {
                          handleFocusKeywordChange(value);
                        }, 500);
                      }}
        onBlur={(e) => {
                        if (e.target.value !== postConfig.focus_keyword) {
                          handleFocusKeywordChange(e.target.value);
                        }
                      }}
        disabled={isLoading}
       />
    </div>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Conversation"}
      </span>
      <strong>
        {`${messageCount} message${messageCount === 1 ? "" : "s"}`}
      </strong>
    </div>
    <div className={"wpaw-agent-context-item"}>
      <span>
        {"Provider"}
      </span>
      <strong>
        {providerLabel}
      </strong>
    </div>
  </div>}
  {!isWorkspaceCollapsed &&
            canResume &&
            <div className={"wpaw-agent-resume-card"}>
    <div>
      <strong>
        {writingState.status === "failed"
                          ? "Writing can be retried"
                          : "Writing can resume"}
      </strong>
      <span>
        {`Last saved section: ${writingState.current_section_index || 0}`}
      </span>
    </div>
    <Button
      isPrimary={true}
      isSmall={true}
      onClick={() =>
                      executePlanFromCard({ retry: true, skipConfirm: true })}
    >
      {writingState.status === "failed" ? "Retry" : "Resume"}
    </Button>
  </div>}
</div>;
    };

    // Keep old function name for backward compatibility
    const renderContextIndicator = renderAgentWorkspaceCard;

    // Render contextual action card
    const renderContextualAction = (intent) => {
      if (!intent || intent === "continue_chat") return null;

      const actions = {
        create_outline: {
          icon: "📝",
          title: "Ready to create an outline?",
          description:
            "I'll generate a structured outline based on our conversation.",
          button: "Create Outline Now",
          onClick: async () => {
            // Switch to planning mode
            setAgentMode("planning");

            // Get topic from focus keyword or chat history
            const focusKw =
              selectedFocusKeyword ||
              postConfig.focus_keyword ||
              postConfig.seo_focus_keyword;
            const firstUserMsg = messages.find((m) => m.role === "user");
            const topic =
              focusKw ||
              (firstUserMsg ? firstUserMsg.content.substring(0, 100) : "");

            // Don't add any user message - directly trigger outline generation
            setInput("");
            setIsLoading(true);

            // Add timeline entry
            setMessages((prev) => [
              ...deactivateActiveTimelineEntries(prev),
              {
                role: "system",
                type: "timeline",
                status: "checking",
                message: "Analyzing request...",
                timestamp: new Date(),
              },
            ]);

            // Call clarity check - MANDATORY before outline generation
            let requestDetectedLanguage = detectedLanguage;
            const contextualOperationController = beginAgentOperation(
              "planning",
              "outline generation",
            );
            try {
              wpawLog.log("[WPAW] Calling clarity check with topic:", topic);
              const clarityResponse = await fetch(
                wpAgenticWriter.apiUrl + "/check-clarity",
                {
                  method: "POST",
                  headers: {
                    "Content-Type": "application/json",
                    "X-WP-Nonce": wpAgenticWriter.nonce,
                  },
                  body: JSON.stringify({
                    topic: topic || "article outline",
                    answers: [],
                    postId: postId,
                    sessionId: currentSessionId,
                    mode: "generation",
                    postConfig: postConfig,
                    chatHistory: buildChatHistoryPayload(),
                  }),
                  signal: contextualOperationController.signal,
                },
              );

              wpawLog.log(
                "[WPAW] Clarity response status:",
                clarityResponse.status,
              );

              if (!clarityResponse.ok) {
                const errorText = await clarityResponse.text();
                wpawLog.error("[WPAW] Clarity check failed:", errorText);
                throw new Error("Clarity check failed: " + errorText);
              }

              const clarityData = await clarityResponse.json();
              applyProviderMetadata(clarityData);
              const clarityResult = clarityData.result;
              wpawLog.log("[WPAW] Clarity result:", clarityResult);

              if (clarityResult.detected_language) {
                requestDetectedLanguage = clarityResult.detected_language;
                setDetectedLanguage(clarityResult.detected_language);
              }

              // MANDATORY: Always show quiz if questions exist
              if (
                clarityResult.questions &&
                clarityResult.questions.length > 0
              ) {
                wpawLog.log(
                  "[WPAW] Showing quiz with",
                  clarityResult.questions.length,
                  "questions",
                );
                setQuestions(clarityResult.questions);
                setInClarification(true);
                setCurrentQuestionIndex(0);
                setAnswers([]);
                setIsLoading(false);

                setMessages((prev) => {
                  const newMessages = [...prev];
                  const lastTimelineIndex =
                    findLastActiveTimelineIndex(newMessages);
                  if (lastTimelineIndex !== -1) {
                    newMessages[lastTimelineIndex] = {
                      ...newMessages[lastTimelineIndex],
                      status: "waiting",
                      message: "Waiting for clarification...",
                    };
                  }
                  return newMessages;
                });
                finishAgentOperation("planning");
                return; // Stop here - quiz must be completed first
              } else {
                wpawLog.warn(
                  "[WPAW] No questions returned from clarity check!",
                );
              }
            } catch (clarityError) {
              wpawLog.error("[WPAW] Clarity check error:", clarityError);
              if (
                isAbortError(clarityError) ||
                stopExecutionRef.current ||
                contextualOperationController.signal.aborted
              ) {
                setMessages((prev) => [
                  ...deactivateActiveTimelineEntries(prev),
                  {
                    role: "system",
                    type: "timeline",
                    status: "stopped",
                    message: "Outline generation stopped by user.",
                    timestamp: new Date(),
                  },
                ]);
              } else {
                // Show error to user instead of silently proceeding
                setMessages((prev) => [
                  ...prev,
                  {
                    role: "system",
                    type: "error",
                    content: "Clarity check failed. Please try again.",
                    canRetry: true,
                  },
                ]);
              }
              setIsLoading(false);
              finishAgentOperation("planning");
              return; // Don't proceed without clarity check
            }

            // Proceed with plan generation
            setMessages((prev) => {
              const newMessages = [...prev];
              const lastTimelineIndex =
                findLastActiveTimelineIndex(newMessages);
              if (lastTimelineIndex !== -1) {
                newMessages[lastTimelineIndex] = {
                  ...newMessages[lastTimelineIndex],
                  status: "starting",
                  message: "Creating outline...",
                };
              }
              return newMessages;
            });

            try {
              const response = await fetch(
                wpAgenticWriter.apiUrl + "/generate-plan",
                {
                  method: "POST",
                  headers: {
                    "Content-Type": "application/json",
                    "X-WP-Nonce": wpAgenticWriter.nonce,
                  },
                  body: JSON.stringify({
                    topic: topic || "article outline",
                    context: "",
                    postId: postId,
                    sessionId: currentSessionId,
                    answers: [],
                    autoExecute: false,
                    stream: true,
                    articleLength: postConfig.article_length,
                    detectedLanguage: requestDetectedLanguage,
                    postConfig: postConfig,
                    chatHistory: buildChatHistoryPayload(),
                  }),
                  signal: contextualOperationController.signal,
                },
              );

              if (!response.ok) {
                const error = await response.json();
                setMessages((prev) => [
                  ...prev,
                  {
                    role: "system",
                    type: "error",
                    content: formatAiErrorMessage(
                      error,
                      "Failed to generate outline",
                    ),
                    canRetry: true,
                    retryType: "generation",
                  },
                ]);
                setIsLoading(false);
                finishAgentOperation("planning");
                return;
              }

              // Handle streaming response
              streamTargetRef.current = null;
              const reader = registerActiveReader(response.body.getReader());
              const decoder = new TextDecoder();

              while (true) {
                if (
                  stopExecutionRef.current ||
                  contextualOperationController.signal.aborted
                ) {
                  throw new DOMException(
                    "Outline generation stopped",
                    "AbortError",
                  );
                }
                const { done, value } = await reader.read();
                if (done) break;

                const chunk = decoder.decode(value, { stream: true });
                const lines = chunk.split("\n");

                for (const line of lines) {
                  if (line.startsWith("data: ")) {
                    try {
                      const data = JSON.parse(line.slice(6));

                      if (data.type === "plan") {
                        setCost((prev) => ({
                          ...prev,
                          session: prev.session + (data.cost || 0),
                        }));
                        if (data.plan) {
                          updateOrCreatePlanMessage(data.plan, {
                            suggestKeywords: true,
                          });
                        }
                      } else if (data.type === "status") {
                        if (data.status === "complete") {
                          continue;
                        }
                        setMessages((prev) => {
                          const newMessages = [...prev];
                          const lastTimelineIndex =
                            findLastActiveTimelineIndex(newMessages);
                          if (lastTimelineIndex !== -1) {
                            newMessages[lastTimelineIndex] = {
                              ...newMessages[lastTimelineIndex],
                              status: data.status,
                              message: data.message,
                              icon: data.icon,
                            };
                          }
                          return newMessages;
                        });
                      }
                    } catch (parseError) {
                      wpawLog.error(
                        "Failed to parse streaming data:",
                        parseError,
                      );
                    }
                  }
                }
              }
              if (
                stopExecutionRef.current ||
                contextualOperationController.signal.aborted
              ) {
                throw new DOMException(
                  "Outline generation stopped",
                  "AbortError",
                );
              }

              setIsLoading(false);
              finishAgentOperation("planning");
            } catch (error) {
              if (
                isAbortError(error) ||
                stopExecutionRef.current ||
                contextualOperationController.signal.aborted
              ) {
                setMessages((prev) => [
                  ...deactivateActiveTimelineEntries(prev),
                  {
                    role: "system",
                    type: "timeline",
                    status: "stopped",
                    message: "Outline generation stopped by user.",
                    timestamp: new Date(),
                  },
                ]);
              } else {
                setMessages((prev) => [
                  ...prev,
                  {
                    role: "system",
                    type: "error",
                    content: formatAiErrorMessage(
                      error,
                      "Failed to generate outline",
                    ),
                    canRetry: true,
                    retryType: "generation",
                  },
                ]);
              }
              setIsLoading(false);
              finishAgentOperation("planning");
            }
          },
        },
      };

      const action = actions[intent];
      if (!action) return null;

      return <div className={"wpaw-contextual-action"}>
  <div className={"wpaw-action-icon"}>
    {action.icon}
  </div>
  <div className={"wpaw-action-content"}>
    <h4>
      {action.title}
    </h4>
    <p>
      {action.description}
    </p>
    <Button isPrimary={true} onClick={action.onClick}>
      {action.button}
    </Button>
  </div>
</div>;
    };

    // Render chat messages with timeline
    const renderMessages = () => {
      const normalizeMessageContent = (content) => {
        if (content === null || content === undefined) {
          return "";
        }
        if (typeof content === "string" || typeof content === "number") {
          return String(content);
        }
        return JSON.stringify(content);
      };
      const escapeHtml = (value) => {
        return String(value)
          .replace(/&/g, "&amp;")
          .replace(/</g, "&lt;")
          .replace(/>/g, "&gt;")
          .replace(/"/g, "&quot;")
          .replace(/'/g, "&#39;");
      };
      const inlineMarkdownToHtml = (text) => {
        let html = escapeHtml(text);
        html = html.replace(
          /\[([^\]]+)\]\(([^)]+)\)/g,
          (match, label, url) =>
            `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer">${label}</a>`,
        );
        html = html.replace(
          /`([^`]+)`/g,
          (match, code) => `<code>${escapeHtml(code)}</code>`,
        );
        html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
        html = html.replace(/__([^_]+)__/g, "<strong>$1</strong>");
        html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
        html = html.replace(/_([^_]+)_/g, "<em>$1</em>");
        return html;
      };
      const markdownToHtml = (markdown) => {
        const raw = normalizeMessageContent(markdown);
        if (!raw) {
          return "";
        }

        if (window.markdownit && window.DOMPurify) {
          if (!markdownRendererRef.current) {
            const renderer = window.markdownit({
              html: false,
              linkify: true,
              breaks: false,
            });
            if (window.markdownitTaskLists) {
              renderer.use(window.markdownitTaskLists, {
                enabled: true,
                label: true,
                labelAfter: true,
              });
            }
            const defaultLinkOpen =
              renderer.renderer.rules.link_open ||
              function (tokens, idx, options, env, self) {
                return self.renderToken(tokens, idx, options);
              };
            renderer.renderer.rules.link_open = function (
              tokens,
              idx,
              options,
              env,
              self,
            ) {
              const token = tokens[idx];
              const targetIndex = token.attrIndex("target");
              if (targetIndex < 0) {
                token.attrPush(["target", "_blank"]);
              } else {
                token.attrs[targetIndex][1] = "_blank";
              }
              const relIndex = token.attrIndex("rel");
              if (relIndex < 0) {
                token.attrPush(["rel", "noopener noreferrer"]);
              } else {
                token.attrs[relIndex][1] = "noopener noreferrer";
              }
              return defaultLinkOpen(tokens, idx, options, env, self);
            };
            markdownRendererRef.current = renderer;
          }

          const rendered = markdownRendererRef.current.render(raw);
          return window.DOMPurify.sanitize(rendered, {
            USE_PROFILES: { html: true },
            ADD_TAGS: ["input", "label"],
            ADD_ATTR: ["type", "checked", "disabled", "class"],
          });
        }

        const codeBlocks = [];
        let text = raw.replace(
          /```(\w+)?\n([\s\S]*?)```/g,
          (match, lang, code) => {
            const safeLang = lang
              ? ` class="language-${escapeHtml(lang)}"`
              : "";
            const index = codeBlocks.length;
            codeBlocks.push(
              `<pre><code${safeLang}>${escapeHtml(code)}</code></pre>`,
            );
            return `@@CODEBLOCK${index}@@`;
          },
        );

        const lines = text.split(/\r?\n/);
        let html = "";
        let paragraph = [];
        let list = null;
        let detailBreak = false;
        let lastLineWasListItem = false;

        const flushParagraph = () => {
          if (paragraph.length) {
            html += `<p>${inlineMarkdownToHtml(paragraph.join(" "))}</p>`;
            paragraph = [];
          }
        };
        const flushList = () => {
          if (list) {
            const items = list.items
              .map((item) => {
                const details =
                  item.details && item.details.length > 0
                    ? item.details
                        .map(
                          (detail) => `<p>${inlineMarkdownToHtml(detail)}</p>`,
                        )
                        .join("")
                    : "";
                const children =
                  item.children && item.children.length > 0
                    ? `<ul>${item.children.map((child) => `<li>${inlineMarkdownToHtml(child)}</li>`).join("")}</ul>`
                    : "";
                return `<li>${inlineMarkdownToHtml(item.content)}${details}${children}</li>`;
              })
              .join("");
            html += `<${list.type}>${items}</${list.type}>`;
            list = null;
          }
        };
        const addListItem = (targetList, value) => {
          targetList.items.push({ content: value, children: [], details: [] });
          lastLineWasListItem = true;
        };
        const addDetailToLastItem = (targetList, value, newParagraph) => {
          const lastItem = targetList.items[targetList.items.length - 1];
          if (!lastItem) {
            return;
          }
          if (newParagraph || lastItem.details.length === 0) {
            lastItem.details.push(value);
          } else {
            lastItem.details[lastItem.details.length - 1] += ` ${value}`;
          }
          lastLineWasListItem = false;
        };

        const getListType = (value) => {
          if (/^\d+\.\s+/.test(value)) {
            return "ol";
          }
          if (/^[-*+]\s+/.test(value)) {
            return "ul";
          }
          return null;
        };

        for (let i = 0; i < lines.length; i++) {
          const trimmed = lines[i].trim();
          if (trimmed === "") {
            let nextIndex = i + 1;
            while (nextIndex < lines.length && lines[nextIndex].trim() === "") {
              nextIndex += 1;
            }
            const nextLine =
              nextIndex < lines.length ? lines[nextIndex].trim() : "";
            const nextType = getListType(nextLine);
            if (list && nextType && nextType === list.type) {
              continue;
            }
            if (
              list &&
              list.type === "ol" &&
              nextLine &&
              !nextType &&
              !nextLine.startsWith("@@CODEBLOCK") &&
              !/^(#{1,6})\s+/.test(nextLine)
            ) {
              detailBreak = true;
              lastLineWasListItem = false;
              continue;
            }
            flushList();
            flushParagraph();
            lastLineWasListItem = false;
            continue;
          }

          if (trimmed.startsWith("@@CODEBLOCK")) {
            flushList();
            flushParagraph();
            html += trimmed;
            lastLineWasListItem = false;
            continue;
          }

          const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)$/);
          if (headingMatch) {
            flushList();
            flushParagraph();
            const level = headingMatch[1].length;
            html += `<h${level}>${inlineMarkdownToHtml(headingMatch[2])}</h${level}>`;
            lastLineWasListItem = false;
            continue;
          }

          const unorderedMatch = trimmed.match(/^[-*+]\s+(.*)$/);
          const orderedMatch = trimmed.match(/^\d+\.\s+(.*)$/);
          if (unorderedMatch || orderedMatch) {
            flushParagraph();
            detailBreak = false;
            const type = orderedMatch ? "ol" : "ul";
            let value =
              (orderedMatch ? orderedMatch[1] : unorderedMatch[1]) || "";
            if (orderedMatch) {
              value = value.replace(/^\d+\.\s+/, "");
            }
            if (
              !orderedMatch &&
              list &&
              list.type === "ol" &&
              list.items.length > 0
            ) {
              list.items[list.items.length - 1].children.push(value);
              continue;
            }
            if (!list || list.type !== type) {
              flushList();
              list = { type, items: [] };
            }
            addListItem(list, value);
            continue;
          }

          if (
            list &&
            list.type === "ol" &&
            (lastLineWasListItem || detailBreak)
          ) {
            addDetailToLastItem(list, trimmed, detailBreak);
            detailBreak = false;
            continue;
          }

          if (list) {
            flushList();
          }
          paragraph.push(trimmed);
          lastLineWasListItem = false;
        }

        flushList();
        flushParagraph();

        codeBlocks.forEach((block, index) => {
          html = html.replace(`@@CODEBLOCK${index}@@`, block);
        });

        return html;
      };
      const renderMessageContent = (content, allowMarkdown) => {
        if (!allowMarkdown) {
          return normalizeMessageContent(content);
        }
        return <RawHTML>
  {markdownToHtml(content)}
</RawHTML>;
      };

      const lastActiveTimelineIndex = findLastActiveTimelineIndex(messages);
      const groups = [];
      let currentAiGroup = null;

      messages.forEach((message, index) => {
        if (message.role === "user") {
          groups.push({ type: "user", message, key: `user-${index}` });
          currentAiGroup = null;
          return;
        }

        if (!currentAiGroup) {
          currentAiGroup = { type: "ai", items: [], key: `ai-${index}` };
          groups.push(currentAiGroup);
        }

        currentAiGroup.items.push({ message, index });
      });

      return groups.map((group, groupIndex) => {
        if (group.type === "user") {
          return <div key={group.key} className={"wpaw-message wpaw-message-user"}>
  <div className={"wpaw-message-content"}>
    {renderMessageContent(group.message.content, false)}
  </div>
</div>;
        }

        const isLastGroup = groupIndex === groups.length - 1;
        let streamingLabel = "Streaming...";
        for (let i = group.items.length - 1; i >= 0; i--) {
          const item = group.items[i].message;
          if (item.type === "timeline" && item.status) {
            if (item.status === "checking") {
              streamingLabel = "Analyzing...";
            } else if (
              item.status === "planning" ||
              item.status === "plan_complete"
            ) {
              streamingLabel = "Planning...";
            } else if (
              item.status === "writing" ||
              item.status === "writing_section"
            ) {
              streamingLabel = "Writing...";
            } else if (item.status === "refining") {
              streamingLabel = "Refining...";
            } else {
              streamingLabel = "Streaming...";
            }
            break;
          }
        }

        return <div key={group.key} className={"wpaw-ai-response"}>
  {group.items.map((item, itemIndex) => {
              const message = item.message;
              const index = item.index;
              const isLastItem = itemIndex === group.items.length - 1;

              if (message.type === "timeline") {
                const statusClass =
                  message.status === "complete"
                    ? "complete"
                    : message.status === "inactive"
                      ? "inactive"
                      : "active";
                const showProcessing = isLoading && message.status === "refining";
                const elapsedTime =
                  message.status === "complete" &&
                  message.timestamp &&
                  message.completedAt
                    ? (
                        (new Date(message.completedAt) -
                          new Date(message.timestamp)) /
                        1000
                      ).toFixed(1) + "s"
                    : null;
                return <div
    key={`timeline-${index}`}
    className={"wpaw-ai-item wpaw-timeline-entry " +
                      statusClass +
                      (index === lastActiveTimelineIndex ? " is-current" : "")}
  >
    <div className={"wpaw-timeline-dot"} aria-hidden={"true"} />
    <div className={"wpaw-timeline-content"}>
      <div className={"wpaw-timeline-message"}>
        {normalizeMessageContent(message.message)}
      </div>
      {message.status === "complete" &&
                          <div
        className={"wpaw-timeline-complete-row"}
        style={{
                                display: "flex",
                                justifyContent: "space-between",
                                alignItems: "center",
                              }}
      >
        <div className={"wpaw-timeline-complete"}>
          {"✓ Complete"}
          {elapsedTime &&
                                    <span className={"wpaw-timeline-elapsed"}>
            {` (${elapsedTime})`}
          </span>}
        </div>
        {aiUndoStack.length > 0 &&
                                isLastGroup &&
                                isLastItem &&
                                (message.message.toLowerCase().includes("refine") ||
                                  message.message
                                    .toLowerCase()
                                    .includes("generated")) &&
                                <button
          className={"wpaw-inline-undo-btn"}
          onClick={undoLastAiOperation}
          disabled={isLoading}
          title={`Undo: ${aiUndoStack[aiUndoStack.length - 1]?.label || "Last AI operation"}`}
          style={{
                                      background: "transparent",
                                      border: "none",
                                      color: "var(--aw-primary)",
                                      cursor: isLoading ? "not-allowed" : "pointer",
                                      fontSize: "11px",
                                      fontFamily: "inherit",
                                      fontWeight: "500",
                                      display: "flex",
                                      alignItems: "center",
                                      gap: "4px",
                                      padding: "2px 6px",
                                      borderRadius: "4px",
                                    }}
        >
          <span
            dangerouslySetInnerHTML={{
                                        __html:
                                          '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>',
                                      }}
           />
          {"Undo"}
        </button>}
      </div>}
      {showProcessing &&
                          <div className={"wpaw-processing-indicator"}>
        <span className={"wpaw-dots-loader"} />
        <span>
          {"Processing updates…"}
        </span>
      </div>}
      {!showProcessing &&
                          isLoading &&
                          isLastGroup &&
                          isLastItem &&
                          <div className={"wpaw-typing-indicator"} aria-label={"Agent is typing"}>
        {streamingLabel}
        <span className={"wpaw-typing-dots"}>
          <span />
          <span />
          <span />
        </span>
      </div>}
    </div>
  </div>;
              }

              if (message.type === "plan") {
                const plan = ensurePlanTasks(message.plan);
                const sections = Array.isArray(plan?.sections)
                  ? plan.sections
                  : [];
                const getSectionSummary = (section) => {
                  if (section.description) {
                    return section.description;
                  }
                  if (
                    Array.isArray(section.content) &&
                    section.content.length > 0
                  ) {
                    const firstItem = section.content.find(
                      (item) => item && item.content,
                    );
                    return firstItem ? firstItem.content : "";
                  }
                  return "";
                };
                const pendingCount = sections.filter(
                  (section) => section.status !== "done",
                ).length;
                const buttonLabel = pendingCount
                  ? `Write ${pendingCount} Pending`
                  : "Write Article";

                // Build config summary
                const configSummary = [];
                const languageLabel =
                  postConfig.language === "auto"
                    ? "Auto-detect"
                    : postConfig.language.charAt(0).toUpperCase() +
                      postConfig.language.slice(1);
                configSummary.push(`🌍 Language: ${languageLabel}`);

                const lengthLabels = {
                  short: "Short (~800 words)",
                  medium: "Medium (~1500 words)",
                  long: "Long (~2500 words)",
                };
                configSummary.push(
                  `📏 Length: ${lengthLabels[postConfig.article_length] || "Medium"}`,
                );

                if (postConfig.audience) {
                  configSummary.push(`👥 Audience: ${postConfig.audience}`);
                }
                if (postConfig.web_search) {
                  configSummary.push("🔍 Web Search: Enabled");
                }
                if (postConfig.seo_enabled) {
                  const seoDetails = [];
                  if (postConfig.seo_focus_keyword) {
                    seoDetails.push(`Focus: "${postConfig.seo_focus_keyword}"`);
                  }
                  if (postConfig.seo_secondary_keywords) {
                    seoDetails.push(
                      `Secondary: "${postConfig.seo_secondary_keywords}"`,
                    );
                  }
                  configSummary.push(
                    `📊 SEO: Enabled${seoDetails.length ? " (" + seoDetails.join(", ") + ")" : ""}`,
                  );
                }

                return <div key={`plan-${index}`} className={"wpaw-ai-item wpaw-plan-card"}>
    <div className={"wpaw-plan-title"}>
      {plan?.title || "Proposed Outline"}
    </div>
    <div className={"wpaw-plan-config-summary"}>
      {configSummary.map((item, idx) =>
                          <div key={idx} className={"wpaw-config-summary-item"}>
        {item}
      </div>,
                        )}
    </div>
    {sections.length > 0 &&
                      <ol className={"wpaw-plan-sections"}>
      {sections.map((section, sectionIndex) =>
                            <li
        key={`plan-section-${sectionIndex}`}
        className={`wpaw-plan-section ${section.status || "pending"}`}
      >
        <div className={"wpaw-plan-section-row"}>
          <input
            className={"wpaw-plan-section-check"}
            type={"checkbox"}
            checked={section.status === "done"}
            readOnly={true}
            disabled={true}
           />
          <div className={"wpaw-plan-section-body"}>
            <div className={"wpaw-plan-section-title"}>
              {section.title ||
                                              section.heading ||
                                              `Section ${sectionIndex + 1}`}
            </div>
            {getSectionSummary(section) &&
                                          <div className={"wpaw-plan-section-desc"}>
              {getSectionSummary(section)}
            </div>}
          </div>
          <div className={"wpaw-plan-section-status"}>
            {section.status === "done"
                                          ? "Done"
                                          : section.status === "in_progress"
                                            ? "Writing"
                                            : "Pending"}
          </div>
        </div>
      </li>,
                          )}
    </ol>}
    {!sections.length &&
                      plan?.summary &&
                      <div className={"wpaw-plan-section-desc"}>
      {plan.summary}
    </div>}
    <div className={"wpaw-plan-actions"}>
      <Button
        isPrimary={true}
        onClick={executePlanFromCard}
        disabled={isLoading}
      >
        {buttonLabel}
      </Button>
    </div>
  </div>;
              }

              if (message.type === "edit_plan") {
                const plan = message.plan || pendingEditPlan;
                const isPlanActive =
                  Boolean(pendingEditPlan) && plan === pendingEditPlan;
                const actions = normalizePlanActions(plan);
                const allBlocks = select("core/block-editor").getBlocks();
                const existingIds = new Set(
                  allBlocks.map((block) => block.clientId),
                );
                const previewActions = actions.filter((action) => {
                  if (action.action === "keep") {
                    return false;
                  }
                  if (action.blockId && !existingIds.has(action.blockId)) {
                    return false;
                  }
                  return true;
                });
                const actionCount = previewActions.length;
                const summary =
                  plan?.summary || `Proposed changes: ${actionCount}`;
                const previewItems = previewActions.map((action, actionIndex) =>
                  buildPlanPreviewItem(action, actionIndex),
                );

                return <div key={`plan-${index}`} className={"wpaw-ai-item wpaw-edit-plan"}>
    <div className={"wpaw-edit-plan-title"}>
      {"Proposed Changes"}
    </div>
    <div className={"wpaw-edit-plan-summary"}>
      {summary}
    </div>
    {previewItems.length > 0 &&
                      <div className={"wpaw-edit-plan-preview-label"}>
      {"Apply preview"}
    </div>}
    {previewItems.length > 0 &&
                      <ol className={"wpaw-edit-plan-list"}>
      {previewItems.map((item, itemIndex) =>
                            <li key={`plan-action-${itemIndex}`} className={"wpaw-edit-plan-item"}>
        <div className={"wpaw-edit-plan-item-title"} style={{ marginBottom: "6px" }}>
          {item.title}
        </div>
        {item.viewInEditor &&
                                  <button
          type={"button"}
          className={"wpaw-edit-plan-item-target"}
          disabled={!isPlanActive}
          title={"Scroll to changes in editor"}
          onClick={() => {
                                        if (!isPlanActive || !item.blockId) {
                                          return;
                                        }
                                        dispatch("core/block-editor").selectBlock(
                                          item.blockId,
                                        );
                                        const targetNode = document.querySelector(
                                          `[data-block="${item.blockId}"]`,
                                        );
                                        if (targetNode) {
                                          targetNode.scrollIntoView({
                                            behavior: "smooth",
                                            block: "center",
                                          });
                                        }
                                      }}
        >
          {"View in Editor 👁️"}
        </button>}
      </li>,
                          )}
    </ol>}
    <div className={"wpaw-edit-plan-actions"}>
      <Button
        isPrimary={true}
        onClick={() => applyEditPlan(plan)}
        disabled={!plan || !isPlanActive}
      >
        {`Apply (${actionCount})`}
      </Button>
      <Button
        isSecondary={true}
        onClick={cancelEditPlan}
        disabled={!isPlanActive}
      >
        {"Cancel"}
      </Button>
    </div>
  </div>;
              }

              if (message.type === "error") {
                const handleRetry = () => {
                  if (message.retryType === "execute") {
                    retryLastExecute();
                    return;
                  }
                  if (message.retryType === "refine") {
                    retryLastRefinement();
                    return;
                  }
                  if (message.retryType === "chat") {
                    retryLastChat();
                    return;
                  }
                  retryLastGeneration();
                };

                // Support structured error objects { title, detail, actionUrl, actionLabel }
                const errContent = message.content;
                const isStructured =
                  errContent &&
                  typeof errContent === "object" &&
                  errContent.title;

                return <div
    key={`error-${index}`}
    className={"wpaw-ai-item wpaw-message wpaw-message-error"}
  >
    {isStructured
                      ? <div>
      <div className={"wpaw-error-title"}>
        {"⚠ "}
        {errContent.title}
      </div>
      {errContent.detail &&
                              <div className={"wpaw-error-detail"}>
        {errContent.detail}
      </div>}
      {errContent.actionUrl &&
                              <a
        href={errContent.actionUrl}
        target={"_blank"}
        rel={"noopener"}
        style={{
                                    display: "inline-block",
                                    marginTop: "8px",
                                    fontSize: "12px",
                                    color: "#fca5a5",
                                    textDecoration: "underline",
                                  }}
      >
        {errContent.actionLabel || "Open Settings"}
      </a>}
    </div>
                      : <div className={"wpaw-message-content"}>
      {renderMessageContent(errContent, true)}
    </div>}
    {message.canRetry &&
                      <Button isSecondary={true} onClick={handleRetry}>
      {"↻ Retry"}
    </Button>}
  </div>;
              }

              return <div key={`response-${index}`} className={"wpaw-ai-item wpaw-response"}>
    <div className={"wpaw-response-content"}>
      {renderMessageContent(message.content, true)}
    </div>
    {isLoading &&
                    isLastGroup &&
                    isLastItem &&
                    <div className={"wpaw-typing-indicator"} aria-label={"Agent is typing"}>
      {streamingLabel}
      <span className={"wpaw-typing-dots"}>
        <span />
        <span />
        <span />
      </span>
    </div>}
    {message.detectedIntent &&
                    renderContextualAction(message.detectedIntent)}
    {message.showResumeActions &&
                    <div className={"wpaw-resume-actions"}>
      <Button
        isPrimary={true}
        onClick={() => {
                              setExecutionStopped(false);
                              executePlanFromCard();
                            }}
        style={{ marginRight: "8px" }}
      >
        {`Resume Writing (${message.pendingCount} pending)`}
      </Button>
      <Button
        isSecondary={true}
        onClick={() => {
                              setExecutionStopped(false);
                              setAgentMode("planning");
                            }}
      >
        {"Review Outline"}
      </Button>
    </div>}
  </div>;
            })}
</div>;
      });
    };

    // Render Config Tab
    // Render Config Tab - Updated for Dark Theme

    const renderConfigTab = () => {
      const isConfigDisabled = isLoading || isConfigLoading || isConfigSaving;

      return <div className={"wpaw-tab-content wpaw-config-tab dark-theme"}>
  {/* Back Header */}
  {
          <div className={"wpaw-tab-header"}>
    <h3>
      {"CONFIGURATION"}
    </h3>
  </div>}
  <div className={"wpaw-config-section"}>
    <label>
      {"AGENT WORKSPACE"}
    </label>
    <p className={"description"}>
      {"The command box now routes chat, outline, writing, refinement, and SEO requests automatically from the current editor context."}
    </p>
  </div>
  <div className={"wpaw-config-section"}>
    <label>
      {"ARTICLE LENGTH"}
    </label>
    <select
      value={postConfig.article_length}
      onChange={(e) =>
                    updatePostConfig("article_length", e.target.value)}
      disabled={isConfigDisabled}
      className={"wpaw-select"}
    >
      <option value={"short"}>
        {"Short (500-800 words)"}
      </option>
      <option value={"medium"}>
        {"Medium (800-1500 words)"}
      </option>
      <option value={"long"}>
        {"Long (1500-2500 words)"}
      </option>
    </select>
  </div>
  <div className={"wpaw-config-section"}>
    <label>
      {"Language"}
    </label>
    <select
      value={postConfig.language}
      onChange={(e) => updatePostConfig("language", e.target.value)}
      disabled={isConfigDisabled}
      className={"wpaw-select"}
    >
      {(() => {
                    const preferredLanguages = settings.preferred_languages || [
                      "auto",
                      "English",
                      "Indonesian",
                    ];
                    const customLanguages = settings.custom_languages || [];
                    const allLanguages = [...preferredLanguages, ...customLanguages];
                    return allLanguages.map((lang) => {
                      const langLower = lang.toLowerCase();
                      const displayName = lang === "auto" ? "Auto-detect" : lang;
                      return <option key={langLower} value={langLower}>
        {displayName}
      </option>;
                    });
                  })()}
    </select>
    <p className={"description"}>
      {"Overrides the detected language when writing or refining."}
    </p>
  </div>
  <div className={"wpaw-config-section"}>
    <TextControl
      label={"Tone"}
      value={postConfig.tone}
      onChange={(value) => updatePostConfig("tone", value)}
      disabled={isConfigDisabled}
      placeholder={"e.g., Friendly, persuasive, professional"}
     />
    <p className={"description"}>
      {"Use this to consistently guide the writing tone."}
    </p>
  </div>
  <div className={"wpaw-config-section"}>
    <label>
      {"Experience Level"}
    </label>
    <select
      value={postConfig.experience_level}
      onChange={(e) =>
                    updatePostConfig("experience_level", e.target.value)}
      disabled={isConfigDisabled}
      className={"wpaw-select"}
    >
      <option value={"general"}>
        {"General audience"}
      </option>
      <option value={"beginner"}>
        {"Beginner"}
      </option>
      <option value={"intermediate"}>
        {"Intermediate"}
      </option>
      <option value={"advanced"}>
        {"Advanced"}
      </option>
    </select>
  </div>
  <div className={"wpaw-config-section"}>
    <CheckboxControl
      label={"Include image suggestions"}
      checked={Boolean(postConfig.include_images)}
      onChange={(value) => updatePostConfig("include_images", value)}
      disabled={isConfigDisabled}
     />
    <p className={"description"}>
      {"When enabled, the agent will add image placeholders."}
    </p>
  </div>
  <div className={"wpaw-config-section"}>
    <CheckboxControl
      label={"Enable web search for outlines"}
      checked={Boolean(postConfig.web_search)}
      onChange={(value) => updatePostConfig("web_search", value)}
      disabled={isConfigDisabled}
     />
    <p className={"description"}>
      {"Uses web search when planning outlines."}
    </p>
  </div>
  {/* SEO Section */}
  {
          <div className={"wpaw-config-divider"}>
    <span>
      {"🔍 SEO OPTIMIZATION"}
    </span>
  </div>}
  <div className={"wpaw-config-section"}>
    <CheckboxControl
      label={"Enable SEO optimization"}
      checked={Boolean(postConfig.seo_enabled)}
      onChange={(value) => updatePostConfig("seo_enabled", value)}
      disabled={isConfigDisabled}
     />
    <p className={"description"}>
      {"Include SEO guidelines in AI prompts for keyword-optimized content."}
    </p>
  </div>
  {postConfig.seo_enabled &&
            <div className={"wpaw-config-section"}>
    <TextControl
      label={"Focus Keyword"}
      value={postConfig.seo_focus_keyword}
      onChange={(value) => updatePostConfig("seo_focus_keyword", value)}
      disabled={isConfigDisabled}
      placeholder={"e.g., wordpress seo plugin"}
     />
    <p className={"description"}>
      {"Primary keyword to optimize content for. Will be included in title, headings, and body."}
    </p>
  </div>}
  {postConfig.seo_enabled &&
            <div className={"wpaw-config-section"}>
    <TextControl
      label={"Secondary Keywords"}
      value={postConfig.seo_secondary_keywords}
      onChange={(value) =>
                    updatePostConfig("seo_secondary_keywords", value)}
      disabled={isConfigDisabled}
      placeholder={"e.g., content optimization, search ranking"}
     />
    <p className={"description"}>
      {"Comma-separated related keywords to sprinkle throughout content."}
    </p>
  </div>}
  {postConfig.seo_enabled &&
            <div className={"wpaw-config-section"}>
    <TextareaControl
      label={"Meta Description"}
      value={postConfig.seo_meta_description}
      onChange={(value) =>
                    updatePostConfig("seo_meta_description", value)}
      disabled={isConfigDisabled}
      placeholder={"Enter meta description (120-160 chars recommended)"}
      rows={3}
     />
    <div className={"wpaw-meta-info"}>
      <span
        className={(postConfig.seo_meta_description?.length || 0) >= 120 &&
                          (postConfig.seo_meta_description?.length || 0) <= 160
                            ? "good"
                            : "warning"}
      >
        {`${postConfig.seo_meta_description?.length || 0}/160 chars`}
      </span>
      <Button
        isSecondary={true}
        isSmall={true}
        onClick={() => generateMetaDescription()}
        disabled={isConfigDisabled || isGeneratingMeta}
      >
        {isGeneratingMeta
                          ? <span
          style={{
                                  display: "flex",
                                  alignItems: "center",
                                  gap: "5px",
                                }}
        >
          <span
            className={"wpaw-spinning-icon"}
            dangerouslySetInnerHTML={{
                                    __html:
                                      '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>',
                                  }}
           />
          {" Generating..."}
        </span>
                          : <span
          style={{
                                  display: "flex",
                                  alignItems: "center",
                                  gap: "5px",
                                }}
        >
          <span
            className={"wpaw-svg-wrapper"}
            dangerouslySetInnerHTML={{
                                    __html:
                                      '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="#fbbf24" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594zM20 2v4m2-2h-4"/><circle cx="4" cy="20" r="2"/></g></svg>',
                                  }}
           />
          {" Generate"}
        </span>}
      </Button>
    </div>
  </div>}
  {/* SEO Audit Section */}
  {
          postConfig.seo_enabled &&
            <div className={"wpaw-config-section wpaw-seo-audit"}>
    <div className={"wpaw-seo-audit-header"}>
      <label>
        {"SEO Audit"}
      </label>
      <Button
        isSecondary={true}
        isSmall={true}
        onClick={() => runSeoAudit()}
        disabled={isConfigDisabled || isSeoAuditing}
      >
        {isSeoAuditing
                          ? <span
          style={{
                                  display: "flex",
                                  alignItems: "center",
                                  gap: "5px",
                                }}
        >
          <span
            className={"wpaw-spinning-icon"}
            style={{ display: "inline-flex", lineHeight: "0" }}
            dangerouslySetInnerHTML={{
                                    // Icon Loader/Circle-slashed untuk kesan analyzing
                                    __html:
                                      '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#4ade80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>',
                                  }}
           />
          {" Analyzing..."}
        </span>
                          : <span
          style={{
                                  display: "flex",
                                  alignItems: "center",
                                  gap: "5px",
                                }}
        >
          <span
            className={"wpaw-svg-wrapper"}
            style={{ display: "inline-flex", lineHeight: "0" }}
            dangerouslySetInnerHTML={{
                                    // Icon Bar-Chart untuk "Run Audit"
                                    __html:
                                      '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" stroke="#4ade80" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M13 5h8m-8 7h8m-8 7h8M3 17l2 2l4-4M3 7l2 2l4-4"/></svg>',
                                  }}
           />
          {" Run Audit"}
        </span>}
      </Button>
    </div>
    {seoAudit &&
                  <div className={"wpaw-seo-audit-results"}>
      <div
        className={"wpaw-seo-score " +
                            (seoAudit.score >= 70
                              ? "good"
                              : seoAudit.score >= 40
                                ? "warning"
                                : "poor")}
      >
        <span className={"score-value"}>
          {seoAudit.score}
        </span>
        <span className={"score-label"}>
          {"/100"}
        </span>
      </div>
      <div className={"wpaw-seo-stats"}>
        <div className={"wpaw-seo-stat"}>
          <span className={"stat-label"}>
            {"Words"}
          </span>
          <span className={"stat-value"}>
            {seoAudit.word_count || 0}
          </span>
        </div>
        <div className={"wpaw-seo-stat"}>
          <span className={"stat-label"}>
            {"Keyword Density"}
          </span>
          <span className={"stat-value"}>
            {`${(seoAudit.keyword_density || 0).toFixed(1)}%`}
          </span>
        </div>
      </div>
      {seoAudit.checks &&
                        <div className={"wpaw-seo-checks"}>
        {seoAudit.checks.map((check, idx) => {
                              const isPassed =
                                check.status === "good" || check.status === "ok";
                              const isFixing = activeSeoFixKey === getSeoFixKey(check);
                              return <div
          key={idx}
          className={"wpaw-seo-check " +
                                    (isPassed ? "passed" : "failed")}
        >
          <span className={"check-icon"}>
            {isPassed ? "✓" : "✗"}
          </span>
          <span className={"check-label"}>
            {check.message}
          </span>
          {!isPassed &&
                                    <Button
            isSmall={true}
            isSecondary={true}
            className={"wpaw-seo-fix-button" +
                                          (isFixing ? " is-fixing" : "")}
            onClick={() => handleSeoAuditFix(check)}
            disabled={isLoading ||
                                          isSeoAuditing ||
                                          isGeneratingMeta ||
                                          Boolean(activeSeoFixKey)}
          >
            {isFixing ? "Fixing..." : "Fix"}
          </Button>}
        </div>;
                            })}
      </div>}
    </div>}
    {!seoAudit &&
                  <p className={"description"}>
      {'Click "Run Audit" to analyze your content for SEO optimization.'}
    </p>}
  </div>}
  {(isConfigSaving || configError) &&
            <div className={"wpaw-config-section"}>
    {isConfigSaving &&
                  <p className={"description"}>
      {"Saving post configuration..."}
    </p>}
    {configError &&
                  <p className={"description"}>
      {configError}
    </p>}
  </div>}
  <div className={"wpaw-config-section"}>
    <p className={"description"}>
      {"Configure global settings like API keys, models, and clarification quiz options in "}
      <a
        href={settings.settings_url ||
                        "/wp-admin/options-general.php?page=wp-agentic-writer"}
        target={"_blank"}
      >
        {"Settings → WP Agentic Writer"}
      </a>
    </p>
  </div>
</div>;
    };

    const getAgentStatus = () => {
      const agentBusy = isLoading || isSeoAuditing || isGeneratingMeta;
      const isStoppingOperation = activeOperation.status === "stopping";
      if (isStoppingOperation) return "stopping";
      if (!agentBusy) return "idle";
      const lastMsg = messages.filter((m) => m.type === "timeline").pop();
      if (activeOperation.type === "refinement") return "refining";
      if (activeOperation.type === "seo_audit") return "checking";
      if (lastMsg?.message?.toLowerCase().includes("writing")) return "writing";
      if (lastMsg?.message?.toLowerCase().includes("generating"))
        return "writing";
      return "thinking";
    };

    const renderGlobalStatusBar = () => {
      const agentStatus = getAgentStatus();
      const statusLabels = {
        idle: "Ready",
        thinking: "Thinking...",
        checking: "Checking...",
        refining: "Refining...",
        writing: "Writing...",
        stopping: "Stopping...",
        complete: "Done",
        error: "Error",
      };

      return <div
  className={"wpaw-status-bar"}
  role={"status"}
  aria-live={"polite"}
>
  <div className={"wpaw-status-indicator"}>
    <span className={"wpaw-status-dot " + agentStatus} />
    <span className={"wpaw-status-label"}>
      {statusLabels[agentStatus]}
    </span>
  </div>
  {/* MEMANTO: Restored from memory badge */}
  {
          memantoRestore.restored &&
            <div
    className={"wpaw-memanto-badge"}
    title={"Restored from memory: " +
                  (memantoRestore.summary || "prior session context")}
  >
    {"\uD83E\uDDE0 Restored"}
  </div>}
  <div className={"wpaw-status-actions"}>
    {/* Undo Button */}
    {
              aiUndoStack.length > 0 &&
                <button
      className={"wpaw-status-icon-btn wpaw-undo-btn has-undo"}
      title={`Undo: ${aiUndoStack[aiUndoStack.length - 1]?.label || "Last AI operation"}`}
      onClick={undoLastAiOperation}
      disabled={isLoading}
      dangerouslySetInnerHTML={{
                    __html:
                      '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>',
                  }}
     />}
    {/* Sessions Icon Button (list/archive -> opens welcome screen) */}
    {
              <button
      className={"wpaw-status-icon-btn" +
                  (activeTab === "chat" && showWelcome ? " is-active" : "")}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg>',
                }}
      title={"Sessions"}
      onClick={() => {
                  setActiveTab("chat");
                  setShowWelcome(true);
                }}
      disabled={isLoading}
     />}
    {/* Chat Icon Button (speech bubble -> jumps back to active conversation) */}
    {
              messages.length > 0 &&
                <button
      className={"wpaw-status-icon-btn" +
                    (activeTab === "chat" && !showWelcome ? " is-active" : "")}
      dangerouslySetInnerHTML={{
                    __html:
                      '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></svg>',
                  }}
      title={"Chat"}
      onClick={() => {
                    setActiveTab("chat");
                    setShowWelcome(false);
                  }}
      disabled={isLoading}
     />}
    {/* Agent Workspace Icon Toggle */}
    {
              messages.length > 0 &&
                <button
      className={"wpaw-status-icon-btn wpaw-workspace-toggle-btn"}
      dangerouslySetInnerHTML={{
                    __html:
                      '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M12 5a3 3 0 1 0-5.997.125a4 4 0 0 0-2.526 5.77a4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M9 13a4.5 4.5 0 0 0 3-4M6.003 5.125A3 3 0 0 0 6.401 6.5m-2.924 4.396a4 4 0 0 1 .585-.396M6 18a4 4 0 0 1-1.967-.516M12 13h4m-4 5h6a2 2 0 0 1 2 2v1M12 8h8m-4 0V5a2 2 0 0 1 2-2"/></svg>',
                  }}
      title={isWorkspaceCollapsed
                    ? "Show Agent Workspace"
                    : "Hide Agent Workspace"}
      onClick={toggleAgentWorkspace}
     />}
    {/* Config Icon Button (gear → opens config tab) */}
    {
              <button
      className={"wpaw-status-icon-btn" +
                  (activeTab === "config" ? " is-active" : "")}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M14 17H5M19 7h-9"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></g></svg>',
                }}
      title={"Configuration"}
      onClick={() =>
                  setActiveTab(activeTab === "config" ? "chat" : "config")}
      disabled={isLoading}
     />}
    {/* Cost Icon Button */}
    {
              <button
      className={"wpaw-status-icon-btn" +
                  (activeTab === "cost" ? " is-active" : "")}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"/><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"/></g></svg>',
                }}
      title={"Cost Tracking"}
      onClick={() => setActiveTab(activeTab === "cost" ? "chat" : "cost")}
      disabled={isLoading}
     />}
  </div>
</div>;
    };

    // Render Chat Tab
    const renderChatTab = () => {
      const agentBusy = isLoading || isSeoAuditing || isGeneratingMeta;
      const isStoppingOperation = activeOperation.status === "stopping";

      return <div className={"wpaw-tab-content wpaw-chat-tab dark-theme"}>
  <div className={`wpaw-chat-container ${inClarification ? "is-dimmed" : ""}`}>
    {/* Editor Lock Banner */}
    {
              isEditorLocked &&
                <div className={"wpaw-editor-lock-banner"}>
      {"Writing in progress — please wait until the article finishes."}
    </div>}
    {isRefinementLocked &&
                <div className={"wpaw-refinement-lock-banner"}>
      {`Refining in progress — editing is temporarily locked. You can still scroll and review changes live (${refiningBlockIds.length} target block(s)).`}
    </div>}
    {/* Session lock notice (another tab is editing this session) */}
    {
              sessionLock.lockedByOther &&
                <div className={"wpaw-session-lock-banner"}>
      <span>
        {"\u26A0\uFE0F This session is active in another tab. Changes here won\u2019t be saved."}
      </span>
      <Button
        isSmall={true}
        isSecondary={true}
        className={"wpaw-session-lock-takeover"}
        onClick={takeOverSession}
      >
        {"Take Over"}
      </Button>
    </div>}
    {/* Health Check Warnings */}
    {
              wpAgenticWriter.health &&
                !wpAgenticWriter.health.ok &&
                wpAgenticWriter.health.issues.map((issue, idx) =>
                  <div key={`health-${idx}`} className={"wpaw-health-notice"}>
      {"⚠️ "}
      {issue.message}
      {issue.actionUrl &&
                        <a
        href={issue.actionUrl}
        target={"_blank"}
        rel={"noopener"}
        style={{ marginLeft: "8px" }}
      >
        {issue.actionLabel || "Fix"}
      </a>}
    </div>,
                )}
    {/* Welcome Screen (first time) */}
    {
              showWelcome && !isEditorLocked && renderWelcomeScreen()}
    {/* Writing Mode Empty State */}
    {
              !showWelcome &&
                shouldShowWritingEmptyState() &&
                renderWritingEmptyState()}
    {/* Agent Workspace Context */}
    {
              !showWelcome &&
                !shouldShowWritingEmptyState() &&
                renderContextIndicator()}
    {/* Activity Log */}
    {
              !showWelcome &&
                !shouldShowWritingEmptyState() &&
                <div className={"wpaw-messages wpaw-activity-log"}>
      <div className={"wpaw-messages-inner"} ref={messagesContainerRef}>
        {renderMessages()}
        <div ref={messagesEndRef} />
      </div>
    </div>}
    {/* Command Input Area - hide when showing empty state or welcome */}
    {
              !showWelcome &&
                !shouldShowWritingEmptyState() &&
                <div className={"wpaw-command-area"} style={{ position: "relative" }}>
      {renderClarification()}
      {/* Slash command hint with animation classes */}
      {
                    <div className={`wpaw-input-hint ${input || isLoading ? "is-hidden" : ""}`}>
        {"Type "}
        <kbd>
          {"/"}
        </kbd>
        {" for commands or "}
        <kbd>
          {"@"}
        </kbd>
        {" to mention a block"}
      </div>}
      {/* Removed Toolbar from Top */}
      {
                    <div
        className={"wpaw-command-input-wrapper" +
                          (isTextareaExpanded ? " expanded" : "")}
      >
        <span className={"wpaw-command-prefix"}>
          {">"}
        </span>
        <textarea
          ref={inputRef}
          className={"wpaw-input"}
          value={input}
          disabled={sessionLock.lockedByOther}
          onChange={(e) => {
                            const val = e.target.value;
                            handleInputChange(val);
                            const el = inputRef.current;
                            if (el) {
                              el.style.height = "auto";
                              el.style.height = Math.min(el.scrollHeight, 250) + "px";
                              el.style.overflowY =
                                el.scrollHeight > 250 ? "auto" : "hidden";
                            }
                          }}
          onKeyDown={handleKeyDown}
          rows={2}
          style={{
                            minHeight: "60px",
                            maxHeight: "250px",
                            resize: "none",
                            width: "100%",
                            boxSizing: "border-box",
                            padding: "10px 12px",
                            fontFamily: "inherit",
                            fontSize: "13px",
                            lineHeight: "1.4",
                            border: "1px solid var(--aw-outline-subtle)",
                            borderRadius: "6px",
                            background: "transparent",
                            color: "inherit",
                          }}
          placeholder={"Ask the agent to write, continue, inspect, refine, or use @ to target blocks..."}
         />
      </div>}
      {showMentionAutocomplete &&
                      mentionOptions.length > 0 &&
                      <div
        className={"wpaw-mention-autocomplete"}
        style={{
                            position: "absolute",
                            bottom: "100%",
                            left: 0,
                            right: 0,
                            maxHeight: "200px",
                            overflowY: "auto",
                            background: "#1e1e1e",
                            border: "1px solid #3c3c3c",
                            zIndex: 1000,
                          }}
      >
        {mentionOptions.map((option, index) => {
                            const isSelected = index === mentionCursorIndex;
                            return <div
          key={option.id}
          className={"wpaw-mention-option" +
                                  (isSelected ? " selected" : "")}
          onClick={() => insertMention(option)}
          style={{
                                  padding: "8px 12px",
                                  cursor: "pointer",
                                  background: isSelected ? "#2c2c2c" : "transparent",
                                  borderBottom: "1px solid #3c3c3c",
                                }}
        >
          <strong
            style={{
                                      display: "block",
                                      color: "#fff",
                                      fontSize: "13px",
                                    }}
          >
            {option.label}
          </strong>
          <span
            style={{
                                      display: "block",
                                      color: "#a7aaad",
                                      fontSize: "12px",
                                      marginTop: "2px",
                                    }}
          >
            {option.sublabel}
          </span>
        </div>;
                          })}
      </div>}
      {showSlashAutocomplete &&
                      slashOptions.length > 0 &&
                      <div
        className={"wpaw-mention-autocomplete"}
        style={{
                            position: "absolute",
                            bottom: "100%",
                            left: 0,
                            right: 0,
                            maxHeight: "200px",
                            overflowY: "auto",
                            background: "#1e1e1e",
                            border: "1px solid #3c3c3c",
                            zIndex: 1000,
                          }}
      >
        {slashOptions.map((option, index) => {
                            const isSelected = index === slashCursorIndex;
                            return <div
          key={option.id}
          className={"wpaw-mention-option" +
                                  (isSelected ? " selected" : "")}
          onClick={() => insertSlashCommand(option)}
          style={{
                                  padding: "8px 12px",
                                  cursor: "pointer",
                                  background: isSelected ? "#2c2c2c" : "transparent",
                                  borderBottom: "1px solid #3c3c3c",
                                }}
        >
          <strong
            style={{
                                      display: "block",
                                      color: "#fff",
                                      fontSize: "13px",
                                    }}
          >
            {option.label}
          </strong>
          <span
            style={{
                                      display: "block",
                                      color: "#a7aaad",
                                      fontSize: "12px",
                                      marginTop: "2px",
                                    }}
          >
            {option.sublabel}
          </span>
        </div>;
                          })}
      </div>}
      <div className={"wpaw-command-actions"}>
        <div className={"wpaw-command-actions-group"}>
          {(() => {
                              // Determine if web search is available for the current provider
                              const taskProviders = settings.task_providers || {};
                              const currentProvider =
                                taskProviders[agentMode] || "openrouter";
                              const isNonOpenRouter =
                                currentProvider === "local_backend" ||
                                currentProvider === "codex";
                              const hasBraveKey = Boolean(settings.brave_search_api_key);
                              const searchBlocked = isNonOpenRouter && !hasBraveKey;
                              const tooltipText = searchBlocked
                                ? "Web Search unavailable — Brave API Key required for " +
                                  currentProvider.replace("_", " ") +
                                  ". Configure in Settings > General."
                                : isNonOpenRouter
                                  ? "Web search via Brave Search API (free tier: 2,000 req/mo)"
                                  : "Web search via OpenRouter (~$0.02/search)";

                              return <label
            className={"wpaw-web-search-toggle" +
                                    (searchBlocked ? " wpaw-search-blocked" : "")}
            title={tooltipText}
            onClick={searchBlocked
                                    ? (e) => {
                                        e.preventDefault();
                                        alert(
                                          "Web Search for " +
                                            currentProvider.replace("_", " ") +
                                            " requires a Brave Search API Key.\n\nGet a free key (2,000 requests/month) and configure it in:\nWP Agentic Writer Settings → General → Brave Search API Key",
                                        );
                                      }
                                    : undefined}
          >
            <input
              type={"checkbox"}
              checked={searchBlocked
                                      ? false
                                      : postConfig.web_search || false}
              onChange={searchBlocked
                                      ? () => {}
                                      : (e) => {
                                          updatePostConfig("web_search", e.target.checked);
                                        }}
              disabled={isLoading || searchBlocked}
             />
            <span
              className={"wpaw-web-search-icon"}
              dangerouslySetInnerHTML={{
                                      __html:
                                        '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20a14.5 14.5 0 0 0 0-20M2 12h20"/></g></svg>',
                                    }}
             />
            <span className={"wpaw-web-search-label"}>
              {searchBlocked ? "Search ✕" : "Search"}
            </span>
          </label>;
                            })()}
        </div>
        <div className={"wpaw-command-actions-group"}>
          {/* Stop Button (appears during execution) - Circle with pause icon */}
          {
                            agentBusy &&
                              <button
            className={"wpaw-command-circle-btn wpaw-stop-circle-btn" +
                                  (isStoppingOperation ? " is-stopping" : "")}
            type={"button"}
            onClick={handleStopExecution}
            disabled={isStoppingOperation}
            title={isStoppingOperation
                                  ? "Stopping..."
                                  : "Stop current operation"}
            dangerouslySetInnerHTML={{
                                  __html: isStoppingOperation
                                    ? '<svg class="wpaw-stop-spinner" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path d="M12 3a9 9 0 1 1-8.5 6"/><path d="M12 7v5l3 2"/></g></svg>'
                                    : '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M11.945 5.75c-1.367 0-2.47 0-3.337.117c-.9.12-1.658.38-2.26.981c-.602.602-.86 1.36-.981 2.26c-.117.867-.117 1.97-.117 3.337v.11c0 1.367 0 2.47.117 3.337c.12.9.38 1.658.981 2.26c.602.602 1.36.86 2.26.982c.867.116 1.97.116 3.337.116h.11c1.367 0 2.47 0 3.337-.116c.9-.122 1.658-.38 2.26-.982s.86-1.36.982-2.26c.116-.867.116-1.97.116-3.337v-.11c0-1.367 0-2.47-.116-3.337c-.122-.9-.38-1.658-.982-2.26s-1.36-.86-2.26-.981c-.867-.117-1.97-.117-3.337-.117z"/></svg>',
                                }}
           />}
          {/* Send Button (Bottom Right) - Circle with send icon */}
          {
                            !agentBusy &&
                              <button
            className={"wpaw-command-circle-btn wpaw-send-circle-btn"}
            type={"button"}
            onClick={sendMessage}
            disabled={!input.trim() || sessionLock.lockedByOther}
            title={"Send message"}
            dangerouslySetInnerHTML={{
                                  __html:
                                    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19.5 2.001a3.5 3.5 0 0 1 3.03 5.249l-7.5 12.99a3.5 3.5 0 0 1-6.411-.842l-1.5-5.595l8.77-5.064a1 1 0 0 0-1-1.732L6.12 12.07L2.026 7.975A3.5 3.5 0 0 1 4.5 2z"/></svg>',
                                }}
           />}
        </div>
      </div>
      <div className={"wpaw-keyboard-hints"} aria-hidden={"true"}>
        <span className={"wpaw-kbd"}>
          <kbd>
            {/Mac|iPod|iPhone|iPad/.test(navigator.platform)
                                  ? "⌘"
                                  : "Ctrl"}
          </kbd>
          {"+"}
          <kbd>
            {"↵"}
          </kbd>
          {" Send"}
        </span>
        <span className={"wpaw-kbd"}>
          <kbd>
            {"@"}
          </kbd>
          {" Blocks"}
        </span>
        <span className={"wpaw-kbd"}>
          <kbd>
            {"/"}
          </kbd>
          {" Commands"}
        </span>
      </div>
      {renderRefineAllConfirmModal()}
    </div>}
  </div>
</div>;
    };

    // Refresh cost data from server
    const [costHistory, setCostHistory] = wp.element.useState([]);

    const refreshCostData = async () => {
      if (!postId) return;
      try {
        const response = await fetch(
          `${wpAgenticWriter.apiUrl}/cost-tracking/${postId}`,
          {
            headers: { "X-WP-Nonce": wpAgenticWriter.nonce },
          },
        );
        const data = await response.json();
        if (data && typeof data.session === "number") {
          setCost({
            session: data.session,
            today: data.today?.total?.cost || 0,
            monthlyUsed: data.monthly?.used || 0,
          });
        }
        if (data?.monthly?.budget) {
          setMonthlyBudget(data.monthly.budget);
        }
        if (data?.history) {
          setCostHistory(data.history);
        }
      } catch (e) {
        wpawLog.error("Failed to refresh cost data:", e);
      }
    };

    React.useEffect(() => {
      if (activeTab === "cost") {
        refreshCostData();
      }
    }, [activeTab, postId]);

    // Render Cost Tab
    const renderCostTab = () => {
      const budgetPercent =
        monthlyBudget > 0 ? (cost.monthlyUsed / monthlyBudget) * 100 : 0;
      const budgetStatus =
        budgetPercent > 90 ? "danger" : budgetPercent > 70 ? "warning" : "ok";
      const remaining = Math.max(0, monthlyBudget - cost.monthlyUsed);

      const actionSummary = costHistory.reduce((acc, curr) => {
        const action = curr.action || "unknown";
        const costVal = parseFloat(curr.cost || 0);
        const tokens =
          parseInt(curr.input_tokens || 0) + parseInt(curr.output_tokens || 0);
        if (!acc[action])
          acc[action] = { action, cost: 0, tokens: 0, count: 0 };
        acc[action].cost += costVal;
        acc[action].tokens += tokens;
        acc[action].count += 1;
        return acc;
      }, {});
      const summaryData = Object.values(actionSummary).sort(
        (a, b) => b.cost - a.cost,
      );

      return <div className={"wpaw-tab-content wpaw-cost-tab dark-theme"}>
  <div className={"wpaw-tab-header"}>
    <h3>
      {"OPENROUTER COST"}
    </h3>
    <button
      className={"wpaw-refresh-btn"}
      dangerouslySetInnerHTML={{
                  __html:
                    '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M21 12a9 9 0 0 0-9-9a9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5m-5 4a9 9 0 0 0 9 9a9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></g></svg>',
                }}
      onClick={refreshCostData}
      title={"Refresh cost data"}
     />
  </div>
  <div className={"wpaw-cost-card"}>
    <div className={"wpaw-cost-stat"}>
      <label>
        {"This Post"}
      </label>
      <div className={"wpaw-cost-value"}>
        {"$"}
        {cost.session.toFixed(4)}
      </div>
    </div>
    <div className={"wpaw-cost-stat"}>
      <label>
        {"Month Used"}
      </label>
      <div className={"wpaw-cost-value"}>
        {"$"}
        {cost.monthlyUsed.toFixed(4)}
      </div>
    </div>
    <div className={"wpaw-cost-stat wpaw-cost-remaining"}>
      <label>
        {"Remaining"}
      </label>
      <div className={"wpaw-cost-value " + budgetStatus}>
        {"$"}
        {remaining.toFixed(2)}
      </div>
    </div>
  </div>
  <div className={"wpaw-budget-section"}>
    <div className={"wpaw-budget-label"}>
      <span>
        {"Budget: $"}
        {monthlyBudget.toFixed(2)}
      </span>
      <span>
        {budgetPercent.toFixed(1)}
        {"%"}
      </span>
    </div>
    <div className={"wpaw-budget-bar"}>
      <div
        className={"wpaw-budget-fill " + budgetStatus}
        style={{ width: Math.min(budgetPercent, 100) + "%" }}
       />
    </div>
  </div>
  {budgetPercent > 80 &&
            <div className={"wpaw-budget-warning " + budgetStatus}>
    {budgetPercent >= 100
                  ? "⚠️ Budget exceeded!"
                  : "⚠️ Approaching budget limit"}
  </div>}
  {costHistory.length > 0 &&
            <div className={"wpaw-cost-history"}>
    <h4>
      {"Cost By Action"}
    </h4>
    <div className={"wpaw-cost-table-wrapper"} style={{ marginBottom: "24px" }}>
      <table className={"wpaw-cost-table"}>
        <thead>
          <tr>
            <th>
              {"Action"}
            </th>
            <th>
              {"Calls"}
            </th>
            <th>
              {"Tokens"}
            </th>
            <th>
              {"Cost(US$)"}
            </th>
          </tr>
        </thead>
        <tbody>
          {summaryData.map((stat, idx) =>
                              <tr key={idx}>
            <td>
              {stat.action}
            </td>
            <td>
              {stat.count}
            </td>
            <td>
              {stat.tokens.toLocaleString()}
            </td>
            <td>
              {"$" + stat.cost.toFixed(4)}
            </td>
          </tr>,
                            )}
        </tbody>
      </table>
    </div>
    <h4>
      {"OpenRouter Cost History"}
    </h4>
    <div className={"wpaw-cost-table-wrapper"}>
      <table className={"wpaw-cost-table"}>
        <thead>
          <tr>
            <th>
              {"Time"}
            </th>
            <th>
              {"Action"}
            </th>
            <th>
              {"Model"}
            </th>
            <th>
              {"Tokens"}
            </th>
            <th>
              {"Cost(US$)"}
            </th>
          </tr>
        </thead>
        <tbody>
          {costHistory.map((record, idx) => {
                              const totalTokens =
                                parseInt(record.input_tokens || 0) +
                                parseInt(record.output_tokens || 0);
                              const time = new Date(record.created_at).toLocaleTimeString(
                                "en-US",
                                { hour: "2-digit", minute: "2-digit" },
                              );
                              const modelShort = record.model
                                ? record.model.split("/").pop().substring(0, 20)
                                : "N/A";
                              return <tr key={idx}>
            <td>
              {time}
            </td>
            <td>
              {record.action}
            </td>
            <td title={record.model}>
              {modelShort}
            </td>
            <td>
              {totalTokens.toLocaleString()}
            </td>
            <td>
              {"$" + parseFloat(record.cost).toFixed(4)}
            </td>
          </tr>;
                            })}
        </tbody>
      </table>
    </div>
  </div>}
  <div className={"wpaw-cost-footer"}>
    <a
      href={settings.settings_url ||
                    "/wp-admin/options-general.php?page=wp-agentic-writer"}
      target={"_blank"}
      className={"wpaw-cost-settings-link"}
    >
      <span
        dangerouslySetInnerHTML={{
                      __html:
                        '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="margin-bottom: -7px;"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/></g></svg> Manage Budget Settings',
                    }}
       />
    </a>
  </div>
</div>;
    };

    // Main render.
    return <wp.element.Fragment>
  <PluginSidebarMoreMenuItem target={"wp-agentic-writer"} icon={pluginIcon}>
    {"WP Agentic Writer"}
  </PluginSidebarMoreMenuItem>
  <PluginSidebar
    name={"wp-agentic-writer"}
    title={<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
    <img
      src={wpAgenticWriter.pluginUrl + "/assets/img/icon.svg"}
      alt={"WP Agentic Writer"}
      style={{ width: "24px", height: "24px" }}
     />
    <span>
      {"WP Agentic Writer"}
    </span>
  </div>}
  >
    <Panel>
      <div className={"wpaw-tab-content-wrapper"}>
        {renderGlobalStatusBar()}
        {activeTab === "chat" && renderChatTab()}
        {activeTab === "config" && renderConfigTab()}
        {activeTab === "cost" && renderCostTab()}
      </div>
    </Panel>
  </PluginSidebar>
</wp.element.Fragment>;
  };

  // HOC to get post ID.
  const mapSelectToProps = (select) => ({
    postId: select("core/editor").getCurrentPostId(),
  });

  // Connect sidebar to Redux store.
  const ConnectedSidebar =
    wp.data.withSelect(mapSelectToProps)(AgenticWriterSidebar);

  // Register plugin.
  registerPlugin("wp-agentic-writer", {
    icon: pluginIcon,
    render: ConnectedSidebar,
  });
})(window.wp);
