'use client';

import {Avatar} from '@astryxdesign/core/Avatar';
import {
  ChatMessage as ChatMessageRow,
  ChatMessageBubble,
} from '@astryxdesign/core/Chat';
import {VStack} from '@astryxdesign/core/Layout';
import {MessageContent} from './MessageContent';
import {TypingIndicator} from './TypingIndicator';
import type {ChatMessage} from '@/features/loyaly-ai/types/chat';

/**
 * One turn.
 *
 * User turns are a filled bubble on the right; assistant turns are `ghost` —
 * transparent, full-width, no container. That asymmetry is the single biggest
 * reason ChatGPT and Claude read as conversation rather than as messaging: a
 * long analytical answer inside a chat bubble reads as a quote, while the same
 * text set flush against the panel reads as prose written for you.
 *
 * It also solves a practical problem. Assistant answers contain tables and
 * code blocks, and a bubble with a max-width would force both to scroll
 * horizontally inside a 380px panel.
 *
 * Timestamps are deliberately absent. They are metadata about a chat log, and
 * this is a working session — ChatGPT, Claude and Gemini all omit them for the
 * same reason.
 */
export function MessageBubble({message}: {message: ChatMessage}) {
  const isAssistant = message.role === 'assistant';
  // Nothing has arrived yet: the dots stand in for the answer, and are
  // replaced by it rather than joined to it.
  const isThinking = isAssistant && message.isStreaming && message.parts.length === 0;

  return (
    <ChatMessageRow
      sender={message.role}
      density="compact"
      avatar={
        isAssistant ? (
          <Avatar name="Loyaly AI" size="xsm" tooltip={false} />
        ) : undefined
      }
    >
      <ChatMessageBubble
        variant={isAssistant ? 'ghost' : 'filled'}
        /**
         * The assistant bubble must have a DEFINITE width. This is the fix for
         * report titles rendering one character per line.
         *
         * ChatMessage lays its row out with `display:flex; align-items:
         * flex-start`, so the bubble is a flex item on the main axis and sizes
         * shrink-to-fit — its width is asked of its contents. ResponseRenderer
         * then sets `container-type: inline-size` to drive the report's
         * container queries, and inline-axis size containment means the report
         * contributes ZERO to that intrinsic measurement. The bubble asked how
         * wide its content wanted to be, containment answered "nothing", and
         * the bubble collapsed to its min-content width.
         *
         * Astryx's own `word-break: break-word` on the bubble is what made the
         * collapse land on a single GLYPH rather than a single word: that
         * value is defined as `word-break: normal` plus `overflow-wrap:
         * anywhere`, and `anywhere` is the one wrapping mode that lets a break
         * opportunity count against min-content size. Min-content therefore
         * became the widest character — "S / t / o / r / e".
         *
         * Stating the width breaks the cycle: the bubble no longer measures
         * its contents, so containment has nothing to poison.
         *
         * `max-w-full` is deliberate too. `styles.content` applies
         * `max-width: max(80%, 280px)` to EVERY bubble including `ghost`,
         * so an assistant report in the 440px rail was being capped around
         * 302px — despite this component's contract that ghost turns are
         * full-width. Tailwind's utilities layer is declared after
         * `astryx-base` in globals.css, so these win over StyleX's
         * `:not(#\#)` specificity hack by cascade layer rather than by
         * out-specifying it.
         */
        className={isAssistant ? 'w-full max-w-full min-w-0' : undefined}
      >
        {isThinking ? (
          <TypingIndicator />
        ) : (
          <VStack gap={3}>
            {message.parts.map((part, index) => (
              <MessageContent
                // Index is a stable key here: parts only ever grow at the end,
                // and the streaming text part is replaced in place rather than
                // reordered — see applyStreamedPart.
                key={`${part.type}-${index}`}
                part={part}
                isStreaming={message.isStreaming}
              />
            ))}
          </VStack>
        )}
      </ChatMessageBubble>
    </ChatMessageRow>
  );
}
