'use client';

import { memo, type ReactNode } from 'react';

import { Avatar, AvatarFallback, AvatarImage } from '@djangocfg/ui-core/components';
import { cn } from '@djangocfg/ui-core/lib';

import { MarkdownMessage } from '../../../components/markdown';
import type {
  ChatAssistantContext,
  ChatAttachment,
  ChatMessage,
  ChatSource,
  ChatToolCall,
  ChatUserContext,
} from '../types';
import { resolvePersona, deriveInitials } from '../core/persona';
import { useChatContextOptional } from '../context';
import { StreamingIndicator } from './StreamingIndicator';
import { Sources } from './Sources';
import { ToolCalls } from './ToolCalls';
import {
  AttachmentsGrid,
  AttachmentsList,
  type AttachmentRendererMap,
} from './Attachments';
import { MessageActions } from './MessageActions';
import type { ToolCallsProps } from './ToolCalls';

export interface MessageBubbleProps {
  message: ChatMessage;
  isUser?: boolean;
  showAvatar?: boolean;
  /** Override avatar URL (skips persona resolution). */
  avatarSrc?: string;
  /** Override avatar fallback (skips persona resolution). */
  avatarFallback?: ReactNode;
  /** Personas — when provided, take precedence over context. */
  user?: ChatUserContext;
  assistant?: ChatAssistantContext;
  showTimestamp?: boolean;
  showActions?: boolean;
  isCompact?: boolean;
  className?: string;
  beforeContent?: ReactNode;
  afterContent?: ReactNode;
  toolCallsRenderer?: (calls: ChatToolCall[]) => ReactNode;
  /** Forwarded to the default `<ToolCalls>` when `toolCallsRenderer` is not set. */
  toolCallsProps?: Omit<ToolCallsProps, 'calls'>;
  sourcesRenderer?: (sources: ChatSource[]) => ReactNode;
  attachmentsRenderer?: (atts: ChatAttachment[]) => ReactNode;
  /** Per-type attachment renderers forwarded to default `<Attachments>`. */
  attachmentRenderers?: AttachmentRendererMap;
  /** Click handler for attachment tiles (e.g. open lightbox). */
  onAttachmentOpen?: (a: ChatAttachment) => void;
  onCopy?: () => void;
  onRegenerate?: () => void;
  onEdit?: () => void;
  onDelete?: () => void;
}

const MessageBubbleInner = ({
  message,
  isUser: isUserProp,
  showAvatar = true,
  avatarSrc,
  avatarFallback,
  user,
  assistant,
  showTimestamp = false,
  showActions = true,
  isCompact = false,
  className,
  beforeContent,
  afterContent,
  toolCallsRenderer,
  toolCallsProps,
  sourcesRenderer,
  attachmentsRenderer,
  attachmentRenderers,
  onAttachmentOpen,
  onCopy,
  onRegenerate,
  onEdit,
  onDelete,
}: MessageBubbleProps) => {
  const isUser = isUserProp ?? message.role === 'user';
  const isStreaming = !!message.isStreaming;
  const isErr = !!message.isError;

  const ctx = useChatContextOptional();
  const persona = resolvePersona(
    message,
    user ?? ctx?.config.user,
    assistant ?? ctx?.config.assistant,
  );
  const initials = deriveInitials(persona, message.role);
  const personaName = persona.name ?? (isUser ? 'You' : 'Assistant');

  return (
    <div
      role="article"
      aria-label={`${personaName} said: ${message.content.slice(0, 80)}`}
      aria-busy={isStreaming || undefined}
      data-role={message.role}
      className={cn(
        'group/msg flex gap-2.5 px-2.5 py-2',
        isUser ? 'flex-row-reverse' : 'flex-row',
        className,
      )}
    >
      {showAvatar ? (
        <Avatar
          className="size-7 shrink-0"
          title={persona.description ?? personaName}
        >
          {avatarSrc || persona.avatarUrl ? (
            <AvatarImage src={avatarSrc ?? persona.avatarUrl} alt={personaName} />
          ) : null}
          <AvatarFallback className="text-[10px]">
            {avatarFallback ?? initials}
          </AvatarFallback>
        </Avatar>
      ) : null}

      <div className={cn('min-w-0 flex-1', isUser && 'flex flex-col items-end')}>
        {beforeContent}
        {message.attachments?.length
          ? attachmentsRenderer
            ? attachmentsRenderer(message.attachments)
            : (
                <div className="mb-1.5 w-full">
                  {attachmentRenderers ? (
                    <AttachmentsList
                      attachments={message.attachments}
                      renderers={attachmentRenderers}
                      onClick={onAttachmentOpen}
                      className={isUser ? 'items-end' : undefined}
                    />
                  ) : (
                    <AttachmentsGrid
                      attachments={message.attachments}
                      onClick={onAttachmentOpen}
                      className={isUser ? 'justify-end' : undefined}
                    />
                  )}
                </div>
              )
          : null}

        <div
          className={cn(
            'inline-block max-w-full rounded-2xl px-3.5 py-2 text-sm',
            isUser
              ? 'bg-primary text-primary-foreground rounded-tr-md'
              : isErr
                ? 'bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30'
                : 'bg-muted text-foreground rounded-tl-md',
          )}
        >
          {isStreaming && message.toolActivity ? (
            <div className="mb-1.5">
              <StreamingIndicator label={message.toolActivity} />
            </div>
          ) : null}

          {message.content || !isStreaming ? (
            <MarkdownMessage
              content={message.content || (isErr ? '*Failed to generate a response.*' : '')}
              isUser={isUser}
              isCompact={isCompact}
              plainText={isStreaming}
            />
          ) : (
            <StreamingIndicator />
          )}
        </div>

        {message.toolCalls?.length
          ? toolCallsRenderer
            ? toolCallsRenderer(message.toolCalls)
            : <ToolCalls calls={message.toolCalls} {...toolCallsProps} />
          : null}

        {message.sources?.length && !isStreaming
          ? sourcesRenderer
            ? sourcesRenderer(message.sources)
            : <Sources sources={message.sources} />
          : null}

        {showActions && !isStreaming ? (
          <MessageActions
            role={message.role}
            onCopy={onCopy}
            onRegenerate={onRegenerate}
            onEdit={onEdit}
            onDelete={onDelete}
          />
        ) : null}

        {showTimestamp ? (
          <div className="mt-1 text-[10px] text-muted-foreground">
            {new Date(message.createdAt).toLocaleTimeString()}
          </div>
        ) : null}

        {afterContent}
      </div>
    </div>
  );
};

export const MessageBubble = memo(MessageBubbleInner, (prev, next) => {
  const a = prev.message;
  const b = next.message;
  return (
    a.id === b.id &&
    a.content === b.content &&
    a.isStreaming === b.isStreaming &&
    a.isError === b.isError &&
    (a.version ?? 0) === (b.version ?? 0) &&
    a.toolActivity === b.toolActivity &&
    a.toolCalls === b.toolCalls &&
    a.sources === b.sources &&
    a.attachments === b.attachments
  );
});
MessageBubble.displayName = 'MessageBubble';
