'use client';

/**
 * Message bubble with sender info, content, and actions.
 * Discord-styled for the Phase 2 redesign.
 */

import type { MessageWithMeta } from '@/contexts';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { parseFunctionCalls, type FunctionCall } from '@/lib/functionCallParser';
import { formatMessageTime } from '../shared/formatters';
import { cn } from '@/lib/utils';
import { useUser, useRoom, useTheme } from '@/contexts';
import { memo, useMemo, useState, useCallback, useRef } from 'react';
import { ModelIcon } from '../icon-match';
import { FunctionCallBadge } from '../FunctionCallBadge';
import { MessageContent } from './MessageContent';
import { ThreadIndicator } from '../RoomThreadPanel';
import { getSenderDisplayName, getSenderModelId, getAvatarFromUiMessage, getSenderUserUid, isUserSender } from '../sender';
import { MessageReactions } from './MessageReactions';
import { ReactionPicker } from './ReactionPicker';

// Sender colors — AI Studio clean palette
const SENDER_COLORS: Record<string, string> = {
  system: '#9ca3af',
  ai: '#1c7ded',
  tool: '#6b7280',
};
const DEFAULT_SENDER_COLOR = '#6b7280';

function getSenderColor(senderType: string): string {
  return SENDER_COLORS[senderType] ?? DEFAULT_SENDER_COLOR;
}

const TEXT_COLLAPSE_LINE_COUNT = 5;

interface MessageBubbleProps {
  message: MessageWithMeta;
  roomId: string;
  replyMessage?: MessageWithMeta | null;
  grouped?: boolean;
  showDate?: boolean;
  onInlineEdit?: (message: MessageWithMeta, newContent: string) => void;
  onViewHistory?: (message: MessageWithMeta) => void;
  onRevoke?: (message: MessageWithMeta) => void;
  onReply?: (message: MessageWithMeta) => void;
  onMention?: (name: string, type: 'user' | 'ai') => void;
  onOpenUserCard?: (payload: {
    username: string;
    displayName?: string | null;
    avatarUrl?: string | null;
    userId: string;
    point: { x: number; y: number };
  }) => void;
  onOpenThread?: (message: MessageWithMeta) => void;
  onCreateThread?: (message: MessageWithMeta) => void;
}

export const MessageBubble = memo(function MessageBubble({
  roomId,
  message,
  replyMessage,
  grouped = false,
  showDate = true,
  onInlineEdit,
  onRevoke,
  onReply,
  onOpenUserCard,
  onOpenThread,
}: MessageBubbleProps) {
  const [showFullText, setShowFullText] = useState(false);
  const [isEditing, setIsEditing] = useState(false);
  const [editContent, setEditContent] = useState(message.content);
  const [isSavingEdit, setIsSavingEdit] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);

  const isAi = ['ai', 'system', 'tool'].includes(message.sender_type);
  const isSystem = message.sender_type === 'system';
  const displayName = getSenderDisplayName(message);
  const senderModelId = getSenderModelId(message);
  const avatarUrl = getAvatarFromUiMessage(message);
  const initial = (displayName?.charAt(0) ?? '?').toUpperCase();
  const isStreaming = !!message.is_streaming;
  const isEdited = !!message.edited_at;
  useTheme();
  const { user } = useUser();
  const { wsClient, streamingMessages, members } = useRoom();
  const isOwner = user?.uid === getSenderUserUid(message);
  const isRevoked = !!message.revoked;
  const isFailed = message.isOptimisticError === true;
  const isPending = message.isOptimistic === true || message.id.startsWith('temp-') || message.id.startsWith('optimistic-');

  const displayContent = isStreaming && streamingMessages?.has(message.id)
    ? streamingMessages.get(message.id)!
    : message.content;

  const handleMentionClick = useCallback(
    (type: string, id: string, label: string) => {
      if (!onOpenUserCard || type !== 'user') return;
      // Find member by id
      const member = members.find((m) => m.user === id);
      if (!member) return;
      // Open user card near the message
      const rect = containerRef.current?.getBoundingClientRect();
      if (!rect) return;
      onOpenUserCard({
        username: label,
        displayName: member.user_info?.username ?? label,
        avatarUrl: member.user_info?.avatar_url ?? null,
        userId: id,
        point: { x: rect.left + 40, y: rect.top + 20 },
      });
    },
    [onOpenUserCard, members],
  );

  const handleReaction = useCallback(async (emoji: string) => {
    if (!wsClient) return;
    try {
      const existing = message.reactions?.find(r => r.emoji === emoji);
      if (existing?.reacted_by_me) {
        await wsClient.reactionRemove(roomId, message.id, emoji);
      } else {
        await wsClient.reactionAdd(roomId, message.id, emoji);
      }
    } catch (err) {
      console.warn('[RoomMessage] Failed to update reaction:', err);
    }
  }, [roomId, message.id, message.reactions, wsClient]);

  const functionCalls = useMemo<FunctionCall[]>(
    () =>
      message.content_type === 'text' || message.content_type === 'Text'
        ? parseFunctionCalls(displayContent)
        : [],
    [displayContent, message.content_type],
  );

  const textContent = displayContent;
  const estimatedLines = textContent.split(/\r?\n/).reduce((total, line) => {
    return total + Math.max(1, Math.ceil(line.trim().length / 90));
  }, 0);
  const shouldCollapseText =
    (message.content_type === 'text' || message.content_type === 'Text') &&
    estimatedLines > TEXT_COLLAPSE_LINE_COUNT;
  const isTextCollapsed = shouldCollapseText && !showFullText;

  const handleAvatarClick = (event: React.MouseEvent<HTMLSpanElement>) => {
    if (!onOpenUserCard || isAi || !isUserSender(message)) return;
    if (message.sender_id) {
      onOpenUserCard({
        username: displayName,
        avatarUrl: avatarUrl ?? null,
        userId: message.sender_id,
        point: { x: event.clientX, y: event.clientY },
      });
    }
  };

  const handleStartEdit = useCallback(() => {
    setEditContent(message.content);
    setIsEditing(true);
  }, [message.content]);

  const handleCancelEdit = useCallback(() => {
    setIsEditing(false);
    setEditContent(message.content);
  }, [message.content]);

  const handleSaveEdit = useCallback(async () => {
    if (!editContent.trim() || editContent === message.content) {
      handleCancelEdit();
      return;
    }
    setIsSavingEdit(true);
    try {
      if (onInlineEdit) {
        onInlineEdit(message, editContent.trim());
      }
      setIsEditing(false);
    } finally {
      setIsSavingEdit(false);
    }
  }, [editContent, message, onInlineEdit, handleCancelEdit]);

  const senderColor = getSenderColor(message.sender_type);

  return (
    <div
      ref={containerRef}
      className={cn(
        'group relative flex items-start gap-4 px-4 transition-colors py-0.5',
        !grouped && 'pt-2 pb-1',
        isSystem && 'border-l-2 border-amber-500/60 bg-amber-500/5',
        (isPending || isFailed) && 'opacity-60',
      )}
    >
      {/* Avatar — Discord style, circular, 40px */}
      {!grouped ? (
        <button
          className="shrink-0 cursor-pointer rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--room-accent)]"
          onClick={handleAvatarClick}
          title={displayName}
        >
          <Avatar className="size-10">
            {avatarUrl ? <AvatarImage src={avatarUrl} alt={displayName} /> : null}
            <AvatarFallback
              className="text-sm font-semibold"
              style={{ background: `${senderColor}22`, color: senderColor }}
            >
              {isAi ? <ModelIcon modelId={senderModelId} /> : initial}
            </AvatarFallback>
          </Avatar>
        </button>
      ) : (
        /* Timestamp column for grouped messages */
        <div className="w-10 shrink-0 text-center">
          {showDate && (
            <span className="text-[10px]" style={{ color: 'var(--room-text-subtle)' }}>
              {formatMessageTime(message.send_at).split(':').slice(0, 2).join(':')}
            </span>
          )}
        </div>
      )}

      {/* Message body */}
      <div className="min-w-0 flex-1">
        {/* Header: name + time */}
        {!grouped && (
          <div className="mb-0.5 flex items-baseline gap-2 flex-wrap">
            <span
              className="text-[15px] font-semibold cursor-pointer hover:underline"
              style={{ color: senderColor }}
              onClick={handleAvatarClick}
              role="button"
              tabIndex={0}
            >
              {displayName}
            </span>
            <span style={{ color: 'var(--room-text-muted)' }} className="text-[11px]">{formatMessageTime(message.send_at)}</span>
            {(isFailed || isPending) && (
              <span className="flex items-center gap-1 text-[11px]" title={isFailed ? 'Send failed' : 'Sending...'}>
                <span className={cn('size-2 rounded-full', isFailed ? 'bg-red-500' : 'bg-yellow-400 animate-pulse')} />
                {isFailed ? 'Failed' : 'Sending...'}
              </span>
            )}
            {isEdited && !isEditing && (
              <span
                className="text-[10px] hover:underline transition-colors cursor-pointer"
                style={{ color: 'var(--room-text-subtle)' }}
                title="Edited"
              >
                (edited)
              </span>
            )}
          </div>
        )}

        {/* Inline edit mode */}
        {isEditing ? (
          <div className="mt-0.5 space-y-1.5">
            <textarea
              value={editContent}
              onChange={(e) => setEditContent(e.target.value)}
              className="w-full min-h-[52px] resize-none rounded-md border border-primary bg-background text-foreground px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/60"
              autoFocus
              onKeyDown={(e) => {
                if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); handleSaveEdit(); }
                if (e.key === 'Escape') { handleCancelEdit(); }
              }}
            />
            <div className="flex items-center gap-2">
              <span className="text-[11px]" style={{ color: 'var(--room-text-muted)' }}>Ctrl+Enter to save · Esc to cancel</span>
              <Button size="sm" onClick={handleSaveEdit} disabled={isSavingEdit || !editContent.trim()}
                className="ml-auto h-7 px-3 text-xs bg-primary hover:bg-primary/90 text-primary-foreground border-none">
                {isSavingEdit ? 'Saving...' : 'Save'}
              </Button>
              <Button size="sm" variant="ghost" onClick={handleCancelEdit}
                className="h-7 px-3 text-xs"
                style={{ color: 'var(--room-text-muted)' }}>
                Cancel
              </Button>
            </div>
          </div>
        ) : (
          <>
            {/* Revoked message */}
            {isRevoked ? (
              <p className="text-sm italic" style={{ color: 'var(--room-text-subtle)' }}>This message was deleted.</p>
            ) : (
              <>
                {/* Reply indicator */}
                {replyMessage && (
                  <div className="mb-1 flex items-center gap-2 text-[12px]" style={{ color: 'var(--room-text-muted)' }}>
                    <span className="h-4 w-0.5 rounded-full" style={{ background: 'var(--room-border)' }} />
                    <span>
                      <span className="font-medium" style={{ color: 'var(--room-text-secondary)' }}>{getSenderDisplayName(replyMessage)}</span>
                      <span className="ml-1.5">
                        {replyMessage.content_type === 'text' || replyMessage.content_type === 'Text'
                          ? replyMessage.content.length > 80
                            ? `${replyMessage.content.slice(0, 80)}…`
                            : replyMessage.content
                          : `[${replyMessage.content_type}]`}
                      </span>
                    </span>
                  </div>
                )}

                {/* Message content */}
                <div className="text-[15px] leading-[1.4] min-w-0" style={{ color: 'var(--room-text)' }}>
                  {message.content_type === 'text' || message.content_type === 'Text' ? (
                    <div className={cn('relative', isTextCollapsed && 'max-h-[4.5rem] overflow-hidden')}>
                      {functionCalls.length > 0 ? (
                        functionCalls.map((call, index) => (
                          <div key={index} className="my-1 rounded-md border bg-white/5 p-2 max-w-xl" style={{ borderColor: 'var(--room-border)' }}>
                            <FunctionCallBadge functionCall={call} className="w-auto" />
                          </div>
                        ))
                      ) : (
                        <div className="whitespace-pre-wrap break-words">
                          <MessageContent
                            content={displayContent}
                            onMentionClick={handleMentionClick}
                          />
                        </div>
                      )}

                      {/* Streaming cursor */}
                      {isStreaming && <span className="discord-streaming-cursor" />}

                      {/* Collapse gradient */}
                      {isTextCollapsed && (
                        <div
                          className="pointer-events-none absolute inset-x-0 -bottom-2 h-10 bg-gradient-to-t"
                          style={{ background: `linear-gradient(to top, var(--room-bg), transparent)` }}
                        />
                      )}
                    </div>
                  ) : (
                    <span style={{ color: 'var(--room-text-muted)' }}>[{message.content_type}]</span>
                  )}

                  {/* Show more/less */}
                  {shouldCollapseText && (
                    <button
                      onClick={() => setShowFullText(v => !v)}
                      className="mt-0.5 text-[12px] text-primary hover:underline"
                    >
                      {showFullText ? 'Show less' : 'Show more'}
                    </button>
                  )}
                </div>

                {/* Thread indicator */}
                {message.thread_id && onOpenThread && (
                  <ThreadIndicator threadId={message.thread_id} onClick={() => onOpenThread(message)} />
                )}
              </>
            )}
          </>
        )}

        {/* Reactions + action row */}
        {!isRevoked && !isEditing && (
          <div className="flex items-center gap-1 mt-0.5">
            <MessageReactions message={message} onReaction={handleReaction} />
          </div>
        )}
      </div>

      {/* Discord-style hover action buttons */}
      {!isEditing && !isRevoked && !isPending && (
        <div
          className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity absolute -top-3 right-3"
          style={{ background: 'var(--card)', border: '1px solid var(--room-border)', borderRadius: 6 }}
        >
          <ReactionPicker onReact={handleReaction} />
          {onReply && (
            <button
              className="flex h-7 w-7 items-center justify-center rounded-md transition-colors"
              style={{ color: 'var(--room-text-muted)' }}
              onClick={() => onReply(message)}
              title="Reply"
            >
              ↩
            </button>
          )}
          {isOwner && onRevoke && (
            <button
              className="flex h-7 w-7 items-center justify-center rounded-md transition-colors"
              style={{ color: 'var(--room-text-muted)' }}
              onClick={() => onRevoke(message)}
              title="Delete"
            >
              🗑
            </button>
          )}
          {isOwner && message.content_type === 'text' && handleStartEdit && (
            <button
              className="flex h-7 w-7 items-center justify-center rounded-md transition-colors"
              style={{ color: 'var(--room-text-muted)' }}
              onClick={handleStartEdit}
              title="Edit"
            >
              ✏
            </button>
          )}
        </div>
      )}
    </div>
  );
});
