import { useState } from 'react';
import { Streamdown } from 'streamdown';
import { code } from '@streamdown/code';
import 'streamdown/styles.css';
import { Paperclip, Copy, Check, ExternalLink, Mic, Play, Pause, Laptop, Smartphone, Monitor, Globe, Volume2 } from 'lucide-react';
import AudioBubble from './AudioBubble';
import AuthedImage from './AuthedImage';
import EnvForm, { type EnvGroupData, type EnvField } from './EnvForm';
import BlobyImageCard from './BlobyImageCard';
import BlobyTextCard from './BlobyTextCard';
import NotchCard from './NotchCard';
import MorphyActionCard from './MorphyActionCard';
import type { StoredAttachment } from '../../hooks/useChat';
import type { LightboxImage } from './ImageLightbox';

interface Props {
  role: 'user' | 'assistant';
  content: string;
  timestamp?: string;
  hasAttachments?: boolean;
  audioData?: string;
  attachments?: StoredAttachment[];
  onImageClick?: (images: LightboxImage[], index: number) => void;
  transcribing?: boolean;
}

function formatTime(iso: string): string {
  try {
    const d = new Date(iso);
    return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  } catch {
    return '';
  }
}

/** Parse the leading channel tag from content, e.g. "[Mac]\n..." → { tag: "Mac", body: "..." }.
 *  Messages without an explicit tag default to "Chat" (PWA chat bubble).
 *  Also strips the [voice] prefix that the workspace prepends to voice transcripts,
 *  and trims any "[Mac] " / "[Alexa] " etc. that the agent echoes at the start of replies. */
function parseChannelTag(text: string): { tag: string; body: string } {
  // Channel tag form: "[Name]\n..." (followed by newline) — set on user messages
  const m = text.match(/^\[([^\]]+)\]\n([\s\S]*)$/);
  let body: string;
  let tag = 'Chat';
  if (m) {
    body = m[2];
    const raw = m[1].split('|')[0].trim();
    if (raw === 'PWA') tag = 'Chat';
    else if (raw.toLowerCase() === 'workspace') tag = 'Workspace';
    else tag = raw;
  } else {
    body = text;
  }
  // Strip "[voice] " prefix the workspace prepends to voice transcripts
  if (body.startsWith('[voice] ')) body = body.slice('[voice] '.length);
  // Strip echoed channel name the agent sometimes prefixes to its reply,
  // e.g. "[Mac] Got it" or "[Alexa] sure thing"
  body = body.replace(/^\[(Mac|Alexa|WhatsApp|Chat|PWA|Workspace|workspace)\]\s*/i, '');
  return { tag, body };
}

function ChannelIcon({ tag }: { tag: string }) {
  const cls = 'h-3 w-3 opacity-60';
  if (tag === 'Mac') return <Laptop className={cls} />;
  if (tag === 'WhatsApp') return <Smartphone className={cls} />;
  if (tag === 'Workspace') return <Monitor className={cls} />;
  if (tag === 'Chat') return <Globe className={cls} />;
  if (tag === 'Alexa') return <Volume2 className={cls} />;
  return <Globe className={cls} />;
}

/** Convert channel-pair URLs (any format) into markdown links so the buttons render */
function preprocessContent(text: string): string {
  let out = text;

  const waLink = '[pair-whatsapp](/api/channels/whatsapp/qr-page)';
  if (!out.includes(waLink)) {
    out = out.replace(
      /`?(?:http:\/\/localhost:\d+)?\/api\/channels\/whatsapp\/qr-page`?/g,
      waLink,
    );
  }

  const alexaLink = '[connect-alexa](/api/channels/alexa/pair-page)';
  if (!out.includes(alexaLink)) {
    out = out.replace(
      /`?(?:http:\/\/localhost:\d+)?\/api\/channels\/alexa\/pair-page`?/g,
      alexaLink,
    );
  }

  const telegramLink = '[connect-telegram](/api/channels/telegram/pair-page)';
  if (!out.includes(telegramLink)) {
    out = out.replace(
      /`?(?:http:\/\/localhost:\d+)?\/api\/channels\/telegram\/pair-page`?/g,
      telegramLink,
    );
  }

  return out;
}

/** One entry from a <mac_actions> JSON array (the current Mac action-registry format).
 *  `card` actions carry a preset + data (rendered as a notch card); the screen-acting
 *  verbs (point/spotlight/…) carry coordinates we don't surface. Kept loose so unknown
 *  future verbs still parse. */
type MacAction = { type?: string; preset?: string; data?: unknown; screen?: number; [key: string]: unknown };

type ContentSegment = { type: 'text'; value: string } | { type: 'env'; group: EnvGroupData } | { type: 'bloby-image'; src: string; alt: string } | { type: 'bloby-text'; title: string; content: string } | { type: 'notch'; format: 'html' | 'card'; content: string; cardType?: string } | { type: 'morphy-action'; actionType: string; content: string } | { type: 'mac-actions'; actions: MacAction[] };

/** Extract <EnvGroup> and <BlobyImage> tags from content, splitting into typed segments */
function extractContentSegments(text: string): ContentSegment[] {
  // Collect all custom tag matches with their positions
  const matches: { start: number; end: number; segment: ContentSegment }[] = [];

  // Find <EnvGroup> blocks
  const envRegex = /<EnvGroup\s+title="([^"]+)">([\s\S]*?)<\/EnvGroup>/g;
  let match: RegExpExecArray | null;
  while ((match = envRegex.exec(text)) !== null) {
    const title = match[1];
    const inner = match[2];
    const fields: EnvField[] = [];
    const inputRegex = /<EnvInput\s+name="([^"]+)"\s+label="([^"]+)"\s+placeholder="([^"]+)"\s*\/>/g;
    let inputMatch: RegExpExecArray | null;
    while ((inputMatch = inputRegex.exec(inner)) !== null) {
      fields.push({ name: inputMatch[1], label: inputMatch[2], placeholder: inputMatch[3] });
    }
    if (fields.length > 0) {
      matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'env', group: { title, fields } } });
    }
  }

  // Find <BlobyImage> tags
  const imgRegex = /<BlobyImage\s+src="([^"]+)"(?:\s+alt="([^"]*)")?\s*\/>/g;
  while ((match = imgRegex.exec(text)) !== null) {
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'bloby-image', src: match[1], alt: match[2] || '' } });
  }

  // Find <BlobyText> blocks
  const textRegex = /<BlobyText\s+title="([^"]+)">([\s\S]*?)<\/BlobyText>/g;
  while ((match = textRegex.exec(text)) !== null) {
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'bloby-text', title: match[1], content: match[2].trim() } });
  }

  // Find <notch_html> blocks — custom Mac-notch cards (rendered as a live preview)
  const notchHtmlRegex = /<notch_html>([\s\S]*?)<\/notch_html>/gi;
  while ((match = notchHtmlRegex.exec(text)) !== null) {
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'notch', format: 'html', content: match[1].trim() } });
  }

  // Find <notch_card type="..."> blocks — preset Mac-notch cards (shown as JSON)
  const notchCardRegex = /<notch_card\s+type="([^"]+)">([\s\S]*?)<\/notch_card>/gi;
  while ((match = notchCardRegex.exec(text)) !== null) {
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'notch', format: 'card', content: match[2].trim(), cardType: match[1] } });
  }

  // Find <morphy_action type="..."> blocks — legacy single Mac screen action (spotlight/
  // point/…), shown as a compact "Agent actions" chip rather than the raw tag + coordinates.
  const morphyActionRegex = /<morphy_action\s+type="([^"]+)">([\s\S]*?)<\/morphy_action>/gi;
  while ((match = morphyActionRegex.exec(text)) !== null) {
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'morphy-action', actionType: match[1], content: match[2].trim() } });
  }

  // Find <mac_actions> blocks — the current action-registry format: a JSON array of
  // actions. Each entry is fanned out at render time — `card` → a notch card, every
  // screen-acting verb (point/spotlight/…) → an "Agent actions" chip. A malformed/
  // non-array payload yields no actions (nothing rendered) rather than raw JSON.
  const macActionsRegex = /<mac_actions>([\s\S]*?)<\/mac_actions>/gi;
  while ((match = macActionsRegex.exec(text)) !== null) {
    let actions: MacAction[] = [];
    try {
      const parsed = JSON.parse(match[1].trim());
      if (Array.isArray(parsed)) actions = parsed;
    } catch { /* malformed JSON — render nothing for this block */ }
    matches.push({ start: match.index, end: match.index + match[0].length, segment: { type: 'mac-actions', actions } });
  }

  // Sort by position in text
  matches.sort((a, b) => a.start - b.start);

  // Build segments with text between tags
  const segments: ContentSegment[] = [];
  let lastIndex = 0;
  for (const m of matches) {
    if (m.start > lastIndex) {
      const before = text.slice(lastIndex, m.start).trim();
      if (before) segments.push({ type: 'text', value: before });
    }
    segments.push(m.segment);
    lastIndex = m.end;
  }
  if (lastIndex < text.length) {
    const after = text.slice(lastIndex).trim();
    if (after) segments.push({ type: 'text', value: after });
  }

  return segments;
}

/** Check if content has any custom tags that need special rendering */
function hasCustomTags(text: string): boolean {
  return /<EnvGroup\s+title="[^"]+">/i.test(text) || /<BlobyImage\s+src="/i.test(text) || /<BlobyText\s+title="/i.test(text) || /<notch_html>/i.test(text) || /<notch_card\s+type="/i.test(text) || /<morphy_action\s+type="/i.test(text) || /<mac_actions>/i.test(text);
}

function CopyButton({ text }: { text: string }) {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    } catch { /* clipboard not available */ }
  };
  return (
    <button
      onClick={handleCopy}
      className="absolute -top-2.5 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded-md bg-background/80 border border-border/40 shadow-sm hover:bg-muted text-muted-foreground/60 hover:text-muted-foreground"
      title="Copy message"
    >
      {copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
    </button>
  );
}

/** Opens a table element in a new browser tab with matching dark styling */
function openTableInNewTab(tableEl: HTMLElement) {
  const html = tableEl.outerHTML;
  const win = window.open('', '_blank');
  if (!win) return;
  win.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Table</title>
<style>
  body{background:#1a1a1a;color:#f5f5f5;font-family:system-ui,-apple-system,sans-serif;padding:2rem;margin:0}
  table{border-collapse:collapse;width:100%}
  th,td{border:1px solid rgba(255,255,255,.12);padding:.6rem .85rem;text-align:left;font-size:.875rem}
  th{background:rgba(255,255,255,.05);font-weight:600}
  tr:hover td{background:rgba(255,255,255,.03)}
</style></head><body>${html}</body></html>`);
  win.document.close();
}

/**
 * Intercept Streamdown's fullscreen button click and open table in a new tab instead.
 * Uses capture phase so it fires before Streamdown's handler.
 */
function handleStreamdownClick(e: React.MouseEvent<HTMLDivElement>) {
  const target = e.target as HTMLElement;
  const fullscreenBtn = target.closest('[title="View fullscreen"]');
  if (fullscreenBtn) {
    e.stopPropagation();
    e.preventDefault();
    // Walk up from the button to find the table wrapper, then the table inside it
    const wrapper = fullscreenBtn.closest('[data-streamdown]');
    const tableEl = wrapper?.querySelector('table') ?? fullscreenBtn.closest('.group')?.querySelector('table');
    if (tableEl) openTableInNewTab(tableEl);
  }
}

export default function MessageBubble({ role, content, timestamp, hasAttachments, audioData, attachments, onImageClick, transcribing }: Props) {
  const isUser = role === 'user';
  const time = timestamp ? formatTime(timestamp) : '';

  // Strip channel tag (and [voice] prefix / echoed tags in assistant replies) from BOTH sides
  const { tag: channelTag, body: displayContent } = parseChannelTag(content);

  // Separate image and document attachments. An attachment counts as an image whenever
  // its mediaType says so, its type is 'image', OR its name/path has an image extension —
  // Mac/channel attachments often arrive with a missing or odd mediaType.
  const isImageAtt = (a: StoredAttachment) =>
    a.mediaType?.startsWith('image/') ||
    a.type === 'image' ||
    /\.(png|jpe?g|gif|webp|avif|bmp|svg|heic|heif)$/i.test(a.name || a.filePath || '');
  const imageAtts = attachments?.filter(isImageAtt) || [];
  const docAtts = attachments?.filter((a) => !isImageAtt(a)) || [];

  // Resolve image URLs (keeping them aligned 1:1 with imageAtts so names thread through)
  const imageAttsWithUrl = imageAtts.filter((a) => a.filePath);
  const imageUrls = imageAttsWithUrl.map((a) =>
    a.filePath.startsWith('data:') ? a.filePath : `/api/files/${a.filePath}`
  );
  // Lightbox data model: thread the human filename alongside each URL so downloads/alt
  // text use the real name rather than a data:-URL or random stamp.
  const imageItems: LightboxImage[] = imageUrls.map((url, i) => ({
    url,
    name: imageAttsWithUrl[i]?.name,
  }));

  if (isUser) {
    return (
      <div className="flex flex-col items-end gap-0.5">
        <div className="group relative max-w-[85%]">
          <CopyButton text={displayContent} />
          <div className="rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap bg-primary text-primary-foreground break-words">
            {/* Image thumbnails */}
            {imageUrls.length > 0 && (
              <div className="flex gap-1.5 flex-wrap mb-2">
                {imageUrls.map((url, i) => (
                  <AuthedImage
                    key={i}
                    src={url}
                    alt={imageAttsWithUrl[i]?.name || 'attachment'}
                    className="w-28 h-28 rounded-lg object-cover cursor-pointer border border-white/10 hover:opacity-80 transition-opacity"
                    onClick={() => onImageClick?.(imageItems, i)}
                  />
                ))}
              </div>
            )}
            {/* Document attachments — one row per file with paperclip + filename */}
            {docAtts.length > 0 && (
              <div className="flex flex-col gap-1 mb-2">
                {docAtts.map((a, i) => {
                  const label = a.name || a.filePath?.split('/').pop() || 'file';
                  return (
                    <span key={i} className="inline-flex items-center gap-1 text-primary-foreground/80 text-xs" title={label}>
                      <Paperclip className="h-3 w-3 shrink-0" />
                      <span className="truncate max-w-[14rem]">{label}</span>
                    </span>
                  );
                })}
              </div>
            )}
            {/* Fallback paperclip for legacy messages with no parsed attachments */}
            {!attachments?.length && hasAttachments && (
              <span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
                <Paperclip className="h-3 w-3" />
              </span>
            )}
            {/* Transcribing shimmer */}
            {transcribing && (
              <div className="flex flex-col gap-1.5 mb-1">
                <div className="h-3 w-48 max-w-full rounded bg-white/15 animate-pulse" />
                <div className="h-3 w-32 max-w-full rounded bg-white/10 animate-pulse" />
              </div>
            )}
            {/* Text content */}
            {displayContent}
            {/* Inline audio bar */}
            {audioData && (
              <div className="mt-2 pt-2 border-t border-white/10">
                <AudioBubble audioData={audioData} />
              </div>
            )}
          </div>
        </div>
        {/* Timestamp + channel icon (icon only on user messages — it represents the sender) */}
        <div className="flex items-center gap-1 px-1 self-end">
          <span className="flex items-center gap-0.5 text-[10px] text-muted-foreground/40">
            <ChannelIcon tag={channelTag} />
            {channelTag}
          </span>
          {time && <span className="text-[10px] text-muted-foreground/50">{time}</span>}
        </div>
      </div>
    );
  }

  // Shared Streamdown renderer for text segments
  const renderStreamdown = (text: string) => (
    <Streamdown
      mode="static"
      plugins={{ code }}
      controls={{
        code: { copy: true, download: false },
        table: { copy: true, download: true, fullscreen: true },
      }}
      shikiTheme={['github-dark', 'github-dark']}
      lineNumbers={false}
      components={{
        a({ href, children }) {
          if (href?.includes('/api/channels/whatsapp/qr-page')) {
            return (
              <a
                href={href}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-3 my-2 px-3.5 py-2.5 rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 hover:bg-[#25D366]/15 transition-colors no-underline"
              >
                <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-[#25D366] shrink-0">
                  <svg viewBox="0 0 24 24" className="w-4 h-4 fill-white">
                    <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" />
                  </svg>
                </div>
                <div className="flex flex-col min-w-0">
                  <span className="text-[#25D366] font-medium text-sm">Click here to pair your WhatsApp</span>
                  <span className="text-muted-foreground text-xs">Opens QR code pairing page</span>
                </div>
                <ExternalLink className="w-3.5 h-3.5 text-muted-foreground/50 ml-auto shrink-0" />
              </a>
            );
          }
          if (href?.includes('/api/channels/alexa/pair-page')) {
            return (
              <a
                href={href}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-3 my-2 px-3.5 py-2.5 rounded-xl bg-[#00CAFF]/10 border border-[#00CAFF]/20 hover:bg-[#00CAFF]/15 transition-colors no-underline"
              >
                <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-[#00CAFF] shrink-0">
                  <svg viewBox="0 0 24 24" className="w-4 h-4 fill-white">
                    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>
                  </svg>
                </div>
                <div className="flex flex-col min-w-0">
                  <span className="text-[#00CAFF] font-medium text-sm">Click here to connect Alexa</span>
                  <span className="text-muted-foreground text-xs">Generates a one-time linking code</span>
                </div>
                <ExternalLink className="w-3.5 h-3.5 text-muted-foreground/50 ml-auto shrink-0" />
              </a>
            );
          }
          if (href?.includes('/api/channels/telegram/pair-page')) {
            return (
              <a
                href={href}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-3 my-2 px-3.5 py-2.5 rounded-xl bg-[#229ED9]/10 border border-[#229ED9]/20 hover:bg-[#229ED9]/15 transition-colors no-underline"
              >
                <div className="flex items-center justify-center w-8 h-8 rounded-lg bg-[#229ED9] shrink-0">
                  <svg viewBox="0 0 24 24" className="w-4 h-4 fill-white">
                    <path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19L7.74 13.3 3.64 12c-.88-.25-.89-.86.2-1.3l15.97-6.16c.73-.33 1.43.18 1.15 1.3l-2.72 12.81c-.19.91-.74 1.13-1.5.71l-4.13-3.05-1.99 1.93c-.23.23-.42.42-.83.42z"/>
                  </svg>
                </div>
                <div className="flex flex-col min-w-0">
                  <span className="text-[#229ED9] font-medium text-sm">Click here to connect Telegram</span>
                  <span className="text-muted-foreground text-xs">Opens the Telegram pairing page</span>
                </div>
                <ExternalLink className="w-3.5 h-3.5 text-muted-foreground/50 ml-auto shrink-0" />
              </a>
            );
          }
          return (
            <a
              href={href}
              target="_blank"
              rel="noopener noreferrer"
              className="text-[#0069FE] underline decoration-[#0069FE]/30 underline-offset-2 hover:decoration-[#0069FE] transition-colors"
            >
              {children}
            </a>
          );
        },
      }}
    >
      {preprocessContent(text)}
    </Streamdown>
  );

  // Check if content has custom tags — if so, split into segments
  if (hasCustomTags(displayContent)) {
    const segments = extractContentSegments(displayContent);

    return (
      <div className="flex flex-col items-start gap-0.5">
        <div className="group relative w-full">
          <CopyButton text={displayContent} />
          <div
            className="rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words"
            onClickCapture={handleStreamdownClick}
          >
            {segments.map((seg, i) => {
              if (seg.type === 'text') return <div key={i}>{renderStreamdown(seg.value)}</div>;
              if (seg.type === 'env') return <EnvForm key={i} group={seg.group} />;
              if (seg.type === 'bloby-image') return <BlobyImageCard key={i} src={seg.src} alt={seg.alt} />;
              if (seg.type === 'bloby-text') return <BlobyTextCard key={i} title={seg.title} content={seg.content} />;
              if (seg.type === 'notch') return <NotchCard key={i} format={seg.format} content={seg.content} cardType={seg.cardType} />;
              if (seg.type === 'morphy-action') return <MorphyActionCard key={i} actionType={seg.actionType} content={seg.content} />;
              if (seg.type === 'mac-actions') {
                // Fan each action out: `card` → notch card (preset+data, same as a legacy
                // <notch_card>); every screen-acting verb → an "Agent actions" chip.
                return seg.actions.map((action, j) =>
                  action.type === 'card' ? (
                    <NotchCard key={`${i}-${j}`} format="card" content={JSON.stringify(action.data ?? {})} cardType={action.preset} />
                  ) : (
                    <MorphyActionCard key={`${i}-${j}`} actionType={action.type ?? 'action'} content={JSON.stringify(action)} />
                  ),
                );
              }
              return null;
            })}
          </div>
        </div>
        {time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
      </div>
    );
  }

  // Default: no env groups, render normally
  return (
    <div className="flex flex-col items-start gap-0.5">
      <div className="group relative w-full">
        <CopyButton text={displayContent} />
        <div
          className="rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words"
          onClickCapture={handleStreamdownClick}
        >
          {renderStreamdown(displayContent)}
        </div>
      </div>
      {time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
    </div>
  );
}
