/**
 * ChatMessage.tsx — Layout stile Replit Agent (S440)
 *
 * Flusso agente:
 *   1. AgentNarration   ← status animato live (solo durante streaming)
 *   2. AgentStepCards   ← live ticker | done: chip row + log panel card
 *   3. Divider          ← "Risposta" (se ci sono sia step che contenuto)
 *   4. MarkdownRender   ← risposta finale in streaming
 *   5. SuggestChips     ← prossimi step suggeriti (dopo completamento)
 *   6. Footer           ← copia / riprova
 */

import { useState, memo, useMemo, useCallback, useRef, useEffect } from "react";
import { useIsMobile } from "@/hooks/use-mobile";

import { Message } from "@/lib/storage";
import { Z_INDEX } from "@/lib/zindex";
import { normalizeLlmText } from "@/lib/utils/normalizeLlmOutput";
import { useTaskStore, selectActiveTask } from "@/store/taskStore";
import { DownloadProjectButton } from "./chat/DownloadProjectButton";
import { useAgentSSE } from "@/hooks/useAgentSSE";

import SuggestChips     from "./chat/SuggestChips";
import TypingIndicator  from "./chat/TypingIndicator";
import MarkdownRender, { parseBlocks } from "./chat/MarkdownRender";
import { AgentActionCard } from "./AgentActionCard";
import AgentNarration from "./chat/AgentNarration";
import AgentStepCards from "./chat/AgentStepCards";
import AgentPhaseBar  from "./chat/AgentPhaseBar";
import StreamingPlanBubble from "./chat/StreamingPlanBubble";
import PlanReplayBubble, { type PlanReplayItem } from "./chat/PlanReplayBubble";


// ── U2: QuickWins — estrae il marker <!--QWINS:...→ dal contenuto e renderizza collassabile ──
interface _QW { c: string; s: string; e: string; i: string }
const _QWINS_RE = /\n<!--QWINS:([\s\S]*?)-->/;
function _extractQuickWins(content: string): { text: string; wins: _QW[] } {
  const m = content?.match(_QWINS_RE);
  if (!m) return { text: content ?? "", wins: [] };
  try {
    const wins = JSON.parse(m[1]) as _QW[];
    return { text: content.replace(_QWINS_RE, "").trim(), wins: Array.isArray(wins) ? wins : [] };
  } catch { return { text: content.replace(_QWINS_RE, "").trim(), wins: [] }; }
}

const _QW_ICON: Record<string, string> = {
  performance: "⚡", reliability: "🛡️", ux: "✨", architecture: "🏗️",
};

const QuickWinsSection = function QuickWinsSection({ wins }: { wins: _QW[] }) {
  const [open, setOpen] = useState(false);
  if (!wins.length) return null;
  return (
    <div style={{ marginTop: 10, borderRadius: 8, overflow: "hidden",
      border: "1px solid rgba(99,102,241,0.18)", background: "rgba(15,15,35,0.4)" }}>
      <button
        onClick={() => setOpen(o => !o)}
        style={{
          all: "unset" as never, cursor: "pointer", width: "100%",
          display: "flex", alignItems: "center", justifyContent: "space-between",
          padding: "6px 10px",
          fontSize: "0.70rem", fontWeight: 600, letterSpacing: "0.04em",
          color: "rgba(160,160,220,0.75)",
          fontFamily: '"JetBrains Mono", ui-monospace, monospace',
        }}
      >
        <span>💡 Quick Wins ({wins.length})</span>
        <span style={{ fontSize: "0.65rem", opacity: 0.55 }}>{open ? "▲" : "▼"}</span>
      </button>
      {open && (
        <div style={{ padding: "0 10px 10px", display: "flex", flexDirection: "column", gap: 5 }}>
          {wins.map((w, i) => (
            <div key={i} style={{
              fontSize: "0.73rem", lineHeight: 1.5,
              color: "rgba(200,200,230,0.80)",
              display: "flex", gap: 6, alignItems: "flex-start",
            }}>
              <span style={{ flexShrink: 0, fontSize: "0.8rem" }}>{_QW_ICON[w.c] ?? "•"}</span>
              <span>{w.s}<span style={{ marginLeft: 6, opacity: 0.4, fontSize: "0.65rem" }}>
                effort:{w.e} · impatto:{w.i}
              </span></span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

interface ChatMessageProps {
  message:            Message;
  isStreaming?:       boolean;
  onRetry?:           () => void;
  onRegenerate?:      () => void;
  onCopyMessage?:     (content: string) => void;
  onExecuteResult?:   (result: string) => void;
  onSuggest?:         (text: string) => void;
  onEdit?:            () => void;
  isEditing?:         boolean;
  editDraft?:         string;
  onEditDraftChange?: (v: string) => void;
  onEditSave?:        () => void;
  onEditCancel?:      () => void;
}

// S462: ResponseCompletionMarker — "punto fermo" visivo a fine risposta, stile premium
const ResponseCompletionMarker = memo(function ResponseCompletionMarker() {
  return (
    <div style={{
      display: "inline-flex",
      alignItems: "center",
      gap: 4,
      marginTop: 10,
      marginBottom: 2,
    }}>
      <svg width={10} height={10} viewBox="0 0 14 14" fill="none">
        <circle cx="7" cy="7" r="5.5"
          stroke="rgba(74,222,128,0.35)"
          strokeWidth="1.3"
          fill="none"
        />
        <polyline
          points="4.5,7 6.5,9 9.5,5"
          stroke="rgba(74,222,128,0.45)"
          strokeWidth="1.5"
          strokeLinecap="round"
          strokeLinejoin="round"
          fill="none"
        />
      </svg>
      <span style={{
        fontSize: "0.60rem",
        color: "rgba(255,255,255,0.22)",
        fontFamily: '"JetBrains Mono", ui-monospace, monospace',
        letterSpacing: "0.04em",
        userSelect: "none",
      }}>
        completato
      </span>
    </div>
  );
});

const ChatMessage = memo(({ message, isStreaming, onRetry, onRegenerate, onCopyMessage, onSuggest }: ChatMessageProps) => {
  const [copied, setCopied]     = useState(false);

  // S-PLAN-REPLAY: carica piano salvato per questo messaggio (da localStorage)
  const [replayItems, setReplayItems] = useState<PlanReplayItem[]>(() => {
    if (typeof localStorage === "undefined" || !message.id) return [];
    try {
      const raw = localStorage.getItem(`planReplay_${message.id}`);
      if (!raw) return [];
      const parsed = JSON.parse(raw) as PlanReplayItem[] | { items: PlanReplayItem[]; savedAt: number };
      return Array.isArray(parsed) ? parsed : (parsed.items ?? []);
    } catch { return []; }
  });

  // Cattura piano durante streaming e lo salva per replay futuro
  useEffect(() => {
    if (!isStreaming || !message.id) return;
    const onPlanComplete = (e: Event) => {
      const { items } = (e as CustomEvent<{ items: PlanReplayItem[] }>).detail ?? {};
      if (!Array.isArray(items) || items.length === 0) return;
      try { localStorage.setItem(`planReplay_${message.id}`, JSON.stringify({ items, savedAt: Date.now() })); } catch { /* quota */ }
      setReplayItems(items);
    };
    window.addEventListener("agente-ai:plan-complete", onPlanComplete);
    return () => window.removeEventListener("agente-ai:plan-complete", onPlanComplete);
  }, [isStreaming, message.id]);
  // ── Visual Proof Badge ─────────────────────────────────────────────────────
const ProofBadgePill = memo(function ProofBadgePill({
  badge,
}: {
  badge: { status: "ok" | "warn"; label: string };
}) {
  const isOk = badge.status === "ok";
  return (
    <div style={{
      display: "inline-flex", alignItems: "center", gap: 5,
      marginTop: 6,
      padding: "2px 10px",
      borderRadius: 999,
      fontSize: "0.6rem",
      fontFamily: '"JetBrains Mono", ui-monospace, monospace',
      letterSpacing: "0.04em",
      background: isOk ? "rgba(52,211,153,0.08)" : "rgba(251,191,36,0.08)",
      border: `1px solid ${isOk ? "rgba(52,211,153,0.3)" : "rgba(251,191,36,0.3)"}`,
      color: isOk ? "#34d399" : "#fbbf24",
      animation: "fadeIn 0.2s ease both",
    }}>
      <span style={{ fontSize: "0.65rem" }}>{isOk ? "⬡" : "⚠"}</span>
      {badge.label}
    </div>
  );
});

// S301: footer buttons sempre visibili — no hover gate

  const isMobile          = useIsMobile();
  const isUser            = message.role === "user";
  const isTyping          = message.content === "\u2026";
  const isError           = message.error === true;
  const agentStatus       = message.agentStatus ?? "";
  const hasThinkingStatus = isStreaming === true && agentStatus.length > 0;
  const hasContent        = message.content.length > 0 && !isTyping;

  // P4.1: step SSE live dall'activeTask (TaskAction[] → AgentStep[])
  const { steps: liveSteps, isLive } = useAgentSSE();
  // Durante streaming attivo mostra step live; altrimenti quelli salvati nel messaggio
  const displaySteps = (isStreaming && isLive && liveSteps.length > 0)
    ? liveSteps
    : (message.steps ?? []);
  const hasSteps = displaySteps.length > 0;

  // S396-NARR: testo narrativo live dall'ultimo step running → tool name per AgentNarration
  // S484: passa tool name (non explanation) → AgentNarration usa ACTION_VERB/TOOL_HUMAN interni
  // explanation è ancora su AgentStep per uso futuro (tooltip, step card)
  const liveNarrativeStatus = useMemo(() => {
    if (!isStreaming) return agentStatus;
    if (!isLive || liveSteps.length === 0) {
      // Fix #4: usa il primo rigo del testo parziale come narrazione live se nessun status
      if (agentStatus) return agentStatus;
      const _cnt = message.content;
      if (_cnt && _cnt.length > 5 && _cnt !== "\u200B") {
        const _line = _cnt.split("\n")[0].slice(0, 80);
        if (_line.length > 5) return _line;
      }
      return "Analisi della richiesta in corso…";
    }
    const lastRunning = [...liveSteps].reverse().find(s => s.status === "running");
    // S-UI4: preferisci explanation se disponibile — più descrittiva del solo tool name
    if (lastRunning) return lastRunning.explanation ?? lastRunning.tool ?? "";
    const lastDone = [...liveSteps].reverse().find(s => s.status === "done");
    if (lastDone) return lastDone.explanation ?? lastDone.tool ?? agentStatus;
    return agentStatus;
  }, [isStreaming, isLive, liveSteps, agentStatus, message.content]);

  // Show suggest chips only when: response done, has content, has steps
  const showSuggest = !isStreaming && hasContent && (message.steps?.length ?? 0) > 0 && !!onSuggest;
  const _activeTaskId   = useTaskStore(s => s.activeTaskId);
  const _proofBadge     = useTaskStore(s => s.tasks.find(t => t.id === _activeTaskId)?.proofBadge);
  const showProofBadge  = !isStreaming && (message.steps?.length ?? 0) > 0 && !!_proofBadge;

  const activeTask = useTaskStore(selectActiveTask);
  // S385: DownloadProjectButton — detect write_file steps, use last task ID as conversation ID
  const lastTaskId = useTaskStore(state => state.tasks[state.tasks.length - 1]?.id ?? "");
  const vfsFileCount = useMemo(
    () => message.steps?.filter(s => s.tool === "write_file").length ?? 0,
    [message.steps],
  );
  // GAP-FIX #4: showActionCard visibile solo quando NON ci sono liveSteps.
  // Evita overlap/discontinuità: AgentActionCard → AgentStepCards.
  // Viene usato solo nella fase iniziale (prima che SSE popoli liveSteps).
  const showActionCard = isStreaming === true
    && activeTask != null
    && (activeTask.status === "RUNNING" || activeTask.status === "QUEUED")
    && liveSteps.length === 0
    && (activeTask.actions.length > 0 || activeTask.status === "RUNNING");
  // Steps chips: visibili post-completamento
  const showStepsChips = displaySteps.length > 0 && hasContent;

  // U2: estrai quickWins dal contenuto (marker <!--QWINS:...-->)
  const { text: _qwCleanContent, wins: _quickWins } = useMemo(
    () => _extractQuickWins(message.content ?? ""),
    [message.content]
  );

  const copyMsg = useCallback(async () => {
    try {
      await navigator.clipboard.writeText(_qwCleanContent);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
      onCopyMessage?.(_qwCleanContent);
    } catch {
      try {
        const ta = document.createElement("textarea");
        ta.value = _qwCleanContent;
        ta.style.cssText = "position:fixed;opacity:0;top:0;left:0";
        document.body.appendChild(ta); ta.select();
        document.execCommand("copy");
        document.body.removeChild(ta);
        setCopied(true);
        setTimeout(() => setCopied(false), 1500);
      } catch { /* silent */ }
    }
  }, [message.content, onCopyMessage]);

  // S301: strip leaked agent XML artifacts + auto-critique meta-commentary before rendering
  const sanitizeContent = (raw: string): string => raw
    .replace(/<action>[\w_]+<\/action>\s*<input>[\s\S]*?<\/input>/gi, "")
    .replace(/^(?:La mia |)risposta(?:\s+precedente)?(?:\s+era|\s+è)?(?:\s+imprecisa|\s+incompleta|\s+sbagliata|\s+errata|\s+troncata)[^.\n]*\.?\s*/gim, "")
    .replace(/^Risposta\s+(?:corretta\s+e\s+)?(?:migliorata|aggiornata)\s*[:：]\s*/gim, "")
    .replace(/^\[REASONING\s+CORE[^\]]*\][^\n]*/gim, "")
    // BUG-FIX risposte-errate: strip meta-commento su tool falliti (specchio di finalTextCleaner)
    .replace(/^Ho capito che[^.\n]{0,220}.?\s*/gim, "")
    .replace(/^Notizie:[^.\n]{0,320}.?\s*/gim, "")
    .replace(/^Calcolo:[^.\n]{0,220}(?:fallito|errore|sintassi)[^.\n]*.?\s*/gim, "")
    .replace(/^In sintesi,\s*finora non[^.\n]{0,220}.?\s*/gim, "")
    .replace(/^Ecco un riassunto[^.\n]{0,120}(?:finora|ottenuto|disponibili)[^.\n]*.?\s*/gim, "")
    .replace(/^Non sono disponibili[^.\n]{0,200}(?:timeout|errore|sintassi)[^.\n]*.?\s*/gim, "")
    .trim();

  // #11: debounce normalizeLlmText durante streaming — evita 21 regex × 250 chunk = 5250 ops/msg
  const _lastBlocksRef = useRef<ReturnType<typeof parseBlocks>>([]);
  const _lastLenRef    = useRef<number>(0);

  const blocks = useMemo(() => {
    if (isUser || isTyping) return [];
    const currentLen = message.content?.length ?? 0;
    // Durante streaming salta la normalizzazione se il contenuto è cresciuto di meno di 150 chars
    const hasNewStep = /```step/.test(message.content ?? "") &&
      !_lastBlocksRef.current.some(b => b.type === "step");
    if (isStreaming && !hasNewStep && currentLen - _lastLenRef.current < 150 && _lastBlocksRef.current.length > 0) {
      return _lastBlocksRef.current;
    }
    const normalized = normalizeLlmText(sanitizeContent(_qwCleanContent));
    const parsed = parseBlocks(normalized);
    _lastBlocksRef.current = parsed;
    _lastLenRef.current = currentLen;
    return parsed;
  }, [message.content, isUser, isTyping, isStreaming]);

  // Meccanismo B live: set dei tool già emessi come ```step inline nel testo streamato
  // Passato ad AgentStepCards per escluderli dal live ticker ed evitare duplicati
  const inlineStepTools = useMemo(() => {
    if (!isStreaming) return undefined;
    const tools = blocks.filter(b => b.type === "step").map(b => b.content.trim());
    return tools.length > 0 ? new Set(tools) : undefined;
  }, [isStreaming, blocks]);

  // S456: layout interlacciato stile Replit — paragrafi di testo alternati con chip step
  // Attivo solo post-completamento (non streaming) quando ci sono ≥2 paragrafi
  type _InterleavedSeg =
    | { type: "text";  blk: ReturnType<typeof parseBlocks> }
    | { type: "chips"; steps: NonNullable<Message["steps"]> }
    | { type: "streaming-tail"; blk: ReturnType<typeof parseBlocks> };

  const interleavedSegments = useMemo<_InterleavedSeg[] | null>(() => {
    // S-LIVE: condizione senza guard su isStreaming — i chip si rendono anche durante lo streaming
    // per effetto "live" identico a Replit/Manus (zero salti al completamento).
    if (!showStepsChips || !hasContent || displaySteps.length === 0) return null;

    // Meccanismo B: step inline → MarkdownRender li gestisce via parseBlocks (no duplicati)
    if (blocks.some(b => b.type === "step")) return null;

    const normalized = normalizeLlmText(sanitizeContent(_qwCleanContent));
    const allParas = normalized
      .split(/\n{2,}/)
      .map(p => p.trim())
      .filter(p => p.length > 0);

    if (allParas.length < 1) return null;

    // S480: meta-tool interni nascosti — corrisponde all'INTERNAL set di AgentStepCards
    const _CHIP_INTERNAL = new Set(["__plan__","__thinking__","__verify__","__belief_revision__","__plan_tracker__"]);
    const visibleSteps = displaySteps.filter(s => !_CHIP_INTERNAL.has(s.tool));
    if (visibleSteps.length === 0) return null;

    const segs: _InterleavedSeg[] = [];

    // S-LIVE2: Distribuzione proporzionale su TUTTI i paragrafi — effetto Replit autentico.
    //
    // Ogni paragrafo spiega il processo che stava accadendo mentre veniva scritto.
    // Ogni processo ha le sue card sotto il paragrafo che lo descrive.
    //
    // Formula:  paragrafo[i] → steps[ stepIdx .. round(N*(i+1)/tot) ]
    //
    // Esempio: 4 paragrafi, 10 step:
    //   para[0] → steps[0..2]   (3 step — "Ho analizzato il codebase")
    //   para[1] → steps[3..5]   (2 step — "Ho scritto i file")
    //   para[2] → steps[6..8]   (2 step — "Ho fatto il push")
    //   para[3] → steps[9]      (1 step — "Completato")
    //
    // Durante streaming: para[last] = streaming-tail (testo live) + step in arrivo sotto di esso.
    // Zero salti: il layout finale è identico a quello durante lo streaming.
    let _stepIdx = 0;

    allParas.forEach((para, i) => {
      const isLast = i === allParas.length - 1;
      const _paraHasInlineSteps = parseBlocks(para).some(b => b.type === "step");

      // L'ultimo paragrafo durante streaming → streaming-tail (aggiornamento live del testo)
      segs.push(
        isStreaming && isLast
          ? { type: "streaming-tail", blk: parseBlocks(para) } as _InterleavedSeg
          : { type: "text",           blk: parseBlocks(para) } as _InterleavedSeg
      );

      if (_paraHasInlineSteps) return; // salta chip: il paragrafo ha già step inline nel testo

      // Quota proporzionale di step per questo paragrafo
      const targetIdx = isLast
        ? visibleSteps.length
        : Math.round(visibleSteps.length * (i + 1) / allParas.length);

      const paraSteps = visibleSteps.slice(_stepIdx, targetIdx);
      if (paraSteps.length > 0) {
        segs.push({ type: "chips", steps: paraSteps } as _InterleavedSeg);
      }
      _stepIdx = targetIdx;
    });

    return segs.length > 0 ? segs : null;
  }, [showStepsChips, hasContent, message.content, message.steps, displaySteps, blocks, isStreaming]);

  /* ─────────────────────────────────────────────────────────────── */
  /* ── User bubble ────────────────────────────────────────────── */
  /* ─────────────────────────────────────────────────────────────── */
  if (isUser) {
    return (
      <div data-testid="message-user" style={{ display: "flex", justifyContent: "flex-end" }}>
        <div style={{
          maxWidth: "min(78%, 520px)",
          minWidth: 0,
          background: "var(--user-bubble-bg)",
          borderRadius: "18px 18px 4px 18px",
          padding: isMobile ? "8px 12px" : "10px 16px",
          color: "var(--user-bubble-text)",
          fontSize: isMobile ? "0.88rem" : "0.92rem",
          lineHeight: isMobile ? 1.5 : 1.65,
          overflowWrap: "anywhere",
          wordBreak: "break-word",
          boxShadow: "0 2px 14px rgba(0,0,0,0.18)",
          zIndex: Z_INDEX.BASE,
        }}>
          <span style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere", wordBreak: "break-word" }}>
            {message.content}
          </span>
        </div>
      </div>
    );
  }

  /* ─────────────────────────────────────────────────────────────── */
  /* ── AI card ─────────────────────────────────────────────────── */
  /* ─────────────────────────────────────────────────────────────── */
  // S300: background con fallback esplicito — evita sfondo bianco su iOS
  // rgba(13,13,31,...) è il fallback neutro per browser senza var() support
  const cardBg = isError
    ? "var(--card-bg-error, rgba(30,8,8,0.90))"
    : isStreaming
    ? "var(--card-bg-streaming, rgba(16,18,40,0.48))"
    : "transparent";

  const cardBorder = isError
    ? "1px solid var(--card-border-error)"
    : isStreaming
    ? "1px solid rgba(99,102,241,0.28)"
    : "none";

  const cardShadow = isError || isStreaming
    ? "var(--card-shadow-streaming)"
    : "none";

  return (
    <div
      className={isStreaming ? "chat-card-streaming" : (!isError ? "chat-card-enter" : undefined)}
      style={{
        width: "100%",
        minWidth: 0,
        background: cardBg,
        border: cardBorder,
        borderRadius: 16,
        padding: isMobile ? "10px 12px" : "14px 16px",
        paddingTop: isStreaming && !isError ? (isMobile ? "12px" : "16px") : (isMobile ? "10px" : "14px"),
        color: "var(--text)",
        fontSize: "0.92rem",
        lineHeight: isMobile ? 1.5 : 1.65,
        position: "relative",
        zIndex: Z_INDEX.BASE,
        boxShadow: cardShadow,
        transition: "border-color 0.35s ease, box-shadow 0.35s ease",
        overflowWrap: "anywhere",
        wordBreak: "break-word",
        overflow: "hidden",
      }}
    >
      {/* ── S443: barra progresso 1px indeterminate in cima durante streaming ── */}
      {isStreaming && !isError && (
        <div style={{
          position: "absolute",
          top: 0,
          left: 0,
          right: 0,
          height: 2,
          overflow: "hidden",
          borderRadius: "16px 16px 0 0",
        }}>
          <div className="stream-progress-bar" />
        </div>
      )}

      {/* ── Header rimosso S432-UI: avatar/label ridondante ── */}

      {/* ── GAP-4: Abort button — ferma il task durante streaming ── */}
      {isStreaming && !isError && (
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 4 }}>
          <button
            onClick={() => window.dispatchEvent(new CustomEvent("agente-ai:abort"))}
            title="Ferma il task corrente"
            style={{
              all: "unset",
              cursor: "pointer",
              fontSize: "0.60rem",
              fontFamily: '"JetBrains Mono", ui-monospace, monospace',
              fontWeight: 600,
              letterSpacing: "0.04em",
              color: "rgba(248,113,113,0.55)",
              padding: "2px 9px",
              borderRadius: 6,
              background: "rgba(239,68,68,0.06)",
              border: "1px solid rgba(239,68,68,0.16)",
              display: "inline-flex",
              alignItems: "center",
              gap: 4,
              transition: "all 0.15s",
            }}
            onMouseEnter={e => {
              e.currentTarget.style.color = "#f87171";
              e.currentTarget.style.background = "rgba(239,68,68,0.12)";
              e.currentTarget.style.borderColor = "rgba(239,68,68,0.30)";
            }}
            onMouseLeave={e => {
              e.currentTarget.style.color = "rgba(248,113,113,0.55)";
              e.currentTarget.style.background = "rgba(239,68,68,0.06)";
              e.currentTarget.style.borderColor = "rgba(239,68,68,0.16)";
            }}
          >
            ✕ Stop
          </button>
        </div>
      )}

      {/* ── S396-NARR: AgentNarration — barra narrativa live (solo durante streaming) ── */}
      {isStreaming && !isError && liveNarrativeStatus && (
        <AgentNarration status={liveNarrativeStatus} />
      )}

      {/* ── GAP-5: AgentPhaseBar — stepper macro-fasi (Piano→Ricerca→Azione→Verifica) ── */}
      {isStreaming && !isError && displaySteps.length > 0 && (
        <AgentPhaseBar steps={displaySteps} isLive={true} />
      )}

      {/* ── S-UI-PLAN: StreamingPlanBubble — piano subtask rivelati uno per uno prima dell'esecuzione ── */}
      {/* Nascosto non appena AgentStepCards prende il controllo (liveSteps.length > 0) */}
      {isStreaming && !isError && (
        <StreamingPlanBubble visible={true} />
      )}

      {/* ── S487: AgentStepCards LIVE — fallback finché nessun paragrafo completato (no interleave) ── */}
      {isStreaming && !isError && liveSteps.length > 0 && !interleavedSegments && (
        <AgentStepCards steps={liveSteps} isLive={true} inlineStepTools={inlineStepTools} />
      )}

      {/* ── Pure typing (no steps, no status) — #9: skeleton invece di "…" ── */}
      {isTyping && !agentStatus && !hasSteps ? (
        <div style={{ padding: "4px 0", display: "flex", flexDirection: "column", gap: 8 }}>
          <div className="animate-pulse" style={{ height: 11, width: "62%", borderRadius: 6, background: "rgba(96,165,250,0.12)" }} />
          <div className="animate-pulse" style={{ height: 11, width: "41%", borderRadius: 6, background: "rgba(96,165,250,0.07)", animationDelay: "0.25s" }} />
        </div>

      ) : isError ? (
        /* ── Error state ── */
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
            <span style={{ fontSize: "0.8rem", flexShrink: 0, marginTop: 1, color: "#f87171", fontWeight: 700 }}>!</span>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: "0.82rem", fontWeight: 600, color: "#f87171" }}>
                Qualcosa è andato storto
              </div>
              <div style={{ fontSize: "0.78rem", color: "rgba(248,113,113,0.55)", marginTop: 3, overflowWrap: "anywhere" }}>
                {message.content || "Errore sconosciuto — riprova o cambia provider."}
              </div>
            </div>
          </div>
          {onRetry && (
            <button
              onClick={onRetry}
              onMouseEnter={e => { e.currentTarget.style.background = "rgba(239,68,68,0.14)"; }}
              onMouseLeave={e => { e.currentTarget.style.background = "rgba(239,68,68,0.07)"; }}
              style={{
                all: "unset", cursor: "pointer",
                fontSize: "0.74rem", fontWeight: 600,
                padding: "5px 13px", borderRadius: 8,
                background: "rgba(239,68,68,0.07)",
                border: "1px solid rgba(239,68,68,0.18)",
                color: "#f87171",
                display: "inline-flex", alignItems: "center", gap: 5,
                alignSelf: "flex-start", transition: "background 0.18s",
              }}
            >
              ↻ Riprova
            </button>
          )}
        </div>

      ) : (
        /* ── Normal AI content — Claude/Replit/Manus layout ── */
        <>
          {/* AgentActionCard — fallback chip-based per fase pending iniziale (no steps, no SSE) */}
          {showActionCard && activeTask && (
            <AgentActionCard task={activeTask} isStreaming={isStreaming ?? false} liveStatus={agentStatus} />
          )}

          {/* Streaming "thinking" placeholder (no steps, no status, has nothing yet) */}
          {!isTyping && isStreaming && !hasContent && !agentStatus && !showActionCard && !hasSteps && (
            <TypingIndicator />
          )}

          {/* 6+7. S456: layout interlacciato stile Replit — paragrafo → chip → paragrafo → chip … */}
          {/* Fix #5: separatore visivo narrazione → risposta quando il primo segmento è chips */}
          {interleavedSegments && interleavedSegments[0]?.type === "chips" && (
            <div style={{ height: 1, background: "rgba(255,255,255,0.05)", margin: "8px 0 10px" }} />
          )}
          {interleavedSegments ? (
            interleavedSegments.map((seg, i) => {
              const isLast = i === interleavedSegments.length - 1;
              // S487: streaming-tail = ultimo paragrafo in scrittura durante live
              if (seg.type === "streaming-tail") {
                return (
                  <MarkdownRender
                    key={i}
                    blocks={seg.blk}
                    isStreaming={true}
                    hasThinkingStatus={hasThinkingStatus}
                    steps={displaySteps}
                  />
                );
              }
              if (seg.type === "text") {
                return (
                  <MarkdownRender
                    key={i}
                    blocks={seg.blk}
                    isStreaming={isLast ? (isStreaming ?? false) : false}
                    hasThinkingStatus={isLast ? hasThinkingStatus : false}
                    steps={displaySteps}
                  />
                );
              }
              return (
                <div key={i} style={{ margin: "12px 0 14px" }}>
                  <AgentStepCards
                    steps={seg.steps}
                    isLive={isLast && !!isStreaming}
                    inlineStepTools={isStreaming ? inlineStepTools : undefined}
                  />
                </div>
              );
            })
          ) : (
            /* Layout classico — chip sopra + divider + testo (testo breve / 1 paragrafo) */
            <>
              {/* S-PLAN-REPLAY: piano salvato — visibile per messaggi completati, collassato di default */}
              {!isStreaming && replayItems.length > 0 && (
                <PlanReplayBubble items={replayItems} />
              )}
              {showStepsChips && (
                <AgentStepCards
                  steps={message.steps!}
                  isLive={false}
                  inlineStepTools={blocks.some(b => b.type === "step")
                    ? new Set(blocks.filter(b => b.type === "step").map(b => b.content.trim()))
                    : undefined}
                />
              )}
              {showStepsChips && hasContent && (
                <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "4px 0" }}>
                  <div style={{ flex: 1, height: 1, background: "rgba(255,255,255,0.06)" }} />
                  <span style={{ fontSize: 11, color: "rgba(122,122,122,0.40)", letterSpacing: "0.05em", userSelect: "none" }}>
                    Risposta
                  </span>
                  <div style={{ flex: 1, height: 1, background: "rgba(255,255,255,0.06)" }} />
                </div>
              )}
              {!isTyping && hasContent && (
                <MarkdownRender
                  blocks={blocks}
                  isStreaming={isStreaming}
                  hasThinkingStatus={hasThinkingStatus}
                  steps={displaySteps}
                />
              )}
            </>
          )}

          {/* S462: ResponseCompletionMarker — punto fermo visivo a fine risposta */}
          {!isStreaming && hasContent && (message.steps?.length ?? 0) > 0 && (
            <ResponseCompletionMarker />
          )}

          {/* Visual Proof Badge — ⬡ verde verificato / ⚠ giallo discrepanza */}
          {showProofBadge && _proofBadge && (
            <ProofBadgePill badge={_proofBadge} />
          )}

          {/* 8. SuggestChips — prossimo step stile Replit/Manus */}
          {showSuggest && (
            <SuggestChips
              steps={message.steps!}
              onSuggest={onSuggest}
            />
          )}
        </>
      )}

      {/* S-LIVE: completion marker gestito da ResponseCompletionMarker dentro interleavedSegments */}

      {/* U2: QuickWins — insights collassabili post-completamento */}
      {!isStreaming && !isError && !isTyping && _quickWins.length > 0 && (
        <QuickWinsSection wins={_quickWins} />
      )}

      {/* ── Footer actions — copia / rigenera / riprova ── */}
      {!isTyping && !isError && !isStreaming && (
        <div style={{
          display: "flex",
          gap: 4,
          marginTop: showSuggest ? 6 : 10,
          paddingTop: 8,
          borderTop: showSuggest ? "none" : "1px solid var(--border)",
          flexWrap: "wrap",
          animation: "fadeIn 0.15s ease both",
        }}>
          <button
            onClick={copyMsg}
            style={{
              all: "unset", cursor: "pointer",
              fontSize: "0.64rem",
              fontFamily: '"JetBrains Mono", ui-monospace, monospace',
              letterSpacing: "0.04em",
              color: copied ? "#60a5fa" : "var(--footer-btn-color)",
              padding: isMobile ? "3px 6px" : "3px 10px", borderRadius: 6,
              background: copied ? "rgba(59,130,246,0.08)" : "var(--footer-btn-bg)",
              border: `1px solid ${copied ? "rgba(59,130,246,0.22)" : "var(--footer-btn-border)"}`,
              transition: "all 0.18s ease",
              display: "inline-flex", alignItems: "center", gap: 4,
            }}
            onMouseEnter={e => { if (!copied) { e.currentTarget.style.color = "var(--primary)"; e.currentTarget.style.borderColor = "rgba(99,102,241,0.35)"; } }}
            onMouseLeave={e => { if (!copied) { e.currentTarget.style.color = "var(--footer-btn-color)"; e.currentTarget.style.borderColor = "var(--footer-btn-border)"; } }}
          >
            {copied ? "copiato" : "copia"}
          </button>

          {/* S393 Priority 3: Preview Runtime — rileva HTML nel contenuto e offre inline preview */}
          {message.role === "assistant" && hasContent && (() => {
            const _rawContent = message.content ?? "";
            const _hasHtml = /<!DOCTYPE\s+html|<html[\s>]/i.test(_rawContent)
              || /<(div|section|article|main|body)\s[^>]*style=/i.test(_rawContent);
            if (!_hasHtml) return null;
            const _extractHtml = () => {
              // Estrae blocco HTML dal markdown (```html ... ```) o raw HTML
              const m = _rawContent.match(/```(?:html)?\s*(<!DOCTYPE[\s\S]*?)<\/html>\s*```/i)
                      || _rawContent.match(/(<!DOCTYPE[\s\S]*?<\/html>)/i);
              return m ? m[1] : _rawContent;
            };
            return (
              <button
                onClick={() => {
                  const _html = _extractHtml();
                  window.dispatchEvent(new CustomEvent("agente:preview_open", {
                    detail: { html: _html, name: "preview.html" },
                  }));
                }}
                title="Apri anteprima HTML nel pannello preview"
                style={{
                  all: "unset", cursor: "pointer",
                  fontSize: "0.64rem",
                  fontFamily: '"JetBrains Mono", ui-monospace, monospace',
                  letterSpacing: "0.04em",
                  color: "#34d399",
                  padding: isMobile ? "3px 6px" : "3px 10px", borderRadius: 6,
                  background: "rgba(52,211,153,0.07)",
                  border: "1px solid rgba(52,211,153,0.22)",
                  transition: "all 0.18s ease",
                  display: "inline-flex", alignItems: "center", gap: 4,
                }}
                onMouseEnter={e => { e.currentTarget.style.background = "rgba(52,211,153,0.12)"; e.currentTarget.style.borderColor = "rgba(52,211,153,0.40)"; }}
                onMouseLeave={e => { e.currentTarget.style.background = "rgba(52,211,153,0.07)"; e.currentTarget.style.borderColor = "rgba(52,211,153,0.22)"; }}
              >
                <span style={{ fontSize: "0.8rem", lineHeight: 1 }}>▷</span>
                preview
              </button>
            );
          })()}

          {/* S385: DownloadProjectButton — scarica tutti i file VFS come ZIP (solo assistant) */}
          {message.role === "assistant" && vfsFileCount > 0 && lastTaskId && (
            <DownloadProjectButton conversationId={lastTaskId} fileCount={vfsFileCount} />
          )}

          {onRetry && (
            <button
              onClick={onRetry}
              title="Auto-revisione — rilegge e critica la risposta precedente"
              style={{
                all: "unset", cursor: "pointer",
                fontSize: "0.64rem",
                fontFamily: '"JetBrains Mono", ui-monospace, monospace',
                letterSpacing: "0.04em",
                color: "var(--footer-btn-color)",
                padding: isMobile ? "3px 6px" : "3px 10px", borderRadius: 6,
                background: "var(--footer-btn-bg)",
                border: "1px solid var(--footer-btn-border)",
                transition: "all 0.18s ease",
                display: "inline-flex", alignItems: "center", gap: 4,
              }}
              onMouseEnter={e => { e.currentTarget.style.color = "#a78bfa"; e.currentTarget.style.borderColor = "rgba(167,139,250,0.3)"; e.currentTarget.style.background = "rgba(167,139,250,0.06)"; }}
              onMouseLeave={e => { e.currentTarget.style.color = "var(--footer-btn-color)"; e.currentTarget.style.borderColor = "var(--footer-btn-border)"; e.currentTarget.style.background = "var(--footer-btn-bg)"; }}
            >
              <span style={{ fontSize: "0.8rem", lineHeight: 1 }}>↺</span>
              riprova
            </button>
          )}
        </div>
      )}
    </div>
  );
});

ChatMessage.displayName = "ChatMessage";
export default ChatMessage;
