import type { MessageWithMeta } from '@/contexts';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { parseFunctionCalls, type FunctionCall } from '@/lib/functionCallParser';
import { cn } from '@/lib/utils';
import { AlertCircle, AlertTriangle, ChevronDown, ChevronUp, Copy, Edit2, Reply as ReplyIcon, Trash2, History, MoreHorizontal, MessageSquare } from 'lucide-react';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { SmilePlus } from 'lucide-react';
import { useUser, useRoom } from '@/contexts';
import { memo, useMemo, useState, useCallback, useRef } from 'react';
import { toast } from 'sonner';
import { ModelIcon } from './icon-match';
import { FunctionCallBadge } from './FunctionCallBadge';
import { MessageContentWithMentions } from './MessageMentions';
import { ThreadIndicator } from './RoomThreadPanel';
import { getSenderDisplayName, getSenderModelId, getAvatarFromUiMessage, getSenderUserUid, isUserSender } from './sender';

const COMMON_EMOJIS = [
  '👍', '👎', '❤️', '😂', '😮', '😢', '🎉', '🚀',
  '✅', '⭐', '🔥', '💯', '👀', '🙏', '💪', '🤔',
];

interface RoomMessageBubbleProps {
  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;
}

const TEXT_COLLAPSE_LINE_COUNT = 5;

function formatMessageTime(iso: string) {
  const d = new Date(iso);
  const now = new Date();
  const isToday = d.toDateString() === now.toDateString();
  const isYesterday = new Date(now.getTime() - 86400000).toDateString() === d.toDateString();

  if (isToday) {
    return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
  }
  if (isYesterday) {
    return `Yesterday ${d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}`;
  }
  return d.toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' });
}

export const RoomMessageBubble = memo(function RoomMessageBubble({
  roomId,
  message,
  replyMessage,
  grouped = false,
  showDate = true,
  onInlineEdit,
  onViewHistory,
  onRevoke,
  onReply,
  onMention,
  onOpenUserCard,
  onOpenThread,
  onCreateThread,
}: RoomMessageBubbleProps) {
  const [showFullText, setShowFullText] = useState(false);
  const [isEditing, setIsEditing] = useState(false);
  const [editContent, setEditContent] = useState(message.content);
  const [isSavingEdit, setIsSavingEdit] = useState(false);
  const [showReactionPicker, setShowReactionPicker] = 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;
  const { user } = useUser();
  const { wsClient, streamingMessages } = useRoom();
  const isOwner = user?.uid === getSenderUserUid(message);
  const isRevoked = !!message.revoked;
  const isFailed = message.isOptimisticError === true;
  // True for messages that haven't been confirmed by the server yet.
  // Handles both the old 'temp-' prefix and the new isOptimistic flag.
  const isPending = message.isOptimistic === true || message.id.startsWith('temp-') || message.id.startsWith('optimistic-');

  // Get streaming content if available
  const displayContent = isStreaming && streamingMessages?.has(message.id)
    ? streamingMessages.get(message.id)!
    : message.content;

  // Detect narrow container width using CSS container query instead of ResizeObserver
  // The .group/narrow class on the container enables CSS container query support

  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);
    }
    setShowReactionPicker(false);
  }, [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)) {
      onMention?.(displayName, isAi ? 'ai' : 'user');
      return;
    }
    if (message.sender_id) {
      onOpenUserCard({
        username: displayName,
        avatarUrl: avatarUrl ?? null,
        userId: message.sender_id,
        point: { x: event.clientX, y: event.clientY },
      });
    }
  };

  // Inline edit handlers
  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]);

  return (
    <div
      ref={containerRef}
      className={cn(
        'group relative flex gap-3 px-4 transition-colors',
        grouped ? 'py-0.5' : 'py-2',
        !isSystem && 'hover:bg-muted/30',
        isSystem && 'border-l-2 border-amber-500/60 bg-amber-500/5',
        (isPending || isFailed) && 'opacity-60',
      )}
    >
      {/* Avatar */}
      {!grouped ? (
        <Avatar
          className={cn('size-8 shrink-0', onMention && 'cursor-pointer')}
          onClick={handleAvatarClick}
        >
          {avatarUrl ? <AvatarImage src={avatarUrl} alt={displayName} /> : null}
          <AvatarFallback className="bg-muted text-xs text-foreground">
            {isAi ? <ModelIcon modelId={senderModelId} /> : initial}
          </AvatarFallback>
        </Avatar>
      ) : (
        <div className="w-8 shrink-0" />
      )}

      {/* Message Content */}
      <div className="min-w-0 flex-1">
        {/* Header */}
        {!grouped && (
          <div className="mb-1 flex flex-wrap items-center gap-2">
            <span className="text-sm font-semibold text-foreground">{displayName}</span>
            {isSystem && (
              <Badge variant="outline" className="gap-1 border-amber-500/40 text-[10px] text-amber-700">
                <AlertTriangle className="size-3" />
                System
              </Badge>
            )}
            {showDate && (
              <span className="text-xs text-muted-foreground">{formatMessageTime(message.send_at)}</span>
            )}
            {(isFailed || isPending) && (
              <span className="flex items-center gap-0.5 text-[10px] text-muted-foreground/60" title={isFailed ? 'Send failed' : 'Sending...'}>
                <AlertCircle className={cn('size-2.5', isFailed ? 'text-destructive' : 'animate-pulse')} />
                {isFailed ? 'Failed' : 'Sending...'}
              </span>
            )}
          </div>
        )}

        {/* Inline edit mode */}
        {isEditing ? (
          <div className="space-y-2">
            <textarea
              value={editContent}
              onChange={(e) => setEditContent(e.target.value)}
              className="w-full min-h-[60px] resize-none rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
              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-xs text-muted-foreground">
                Press 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"
              >
                {isSavingEdit ? 'Saving...' : 'Save'}
              </Button>
              <Button
                size="sm"
                variant="outline"
                onClick={handleCancelEdit}
                className="h-7 px-3 text-xs"
              >
                Cancel
              </Button>
            </div>
          </div>
        ) : (
          <>
            {/* Revoked message indicator */}
            {isRevoked ? (
              <div className="flex items-center gap-2 rounded-lg border border-border/50 bg-muted/30 px-3 py-2 text-sm italic text-muted-foreground">
                <Trash2 className="size-3.5" />
                <span>This message has been deleted</span>
              </div>
            ) : (
              <>
                {/* Reply indicator */}
                {replyMessage && (
                  <div className="mb-2 flex items-start gap-1.5 border-l-2 border-border pl-2 text-xs text-muted-foreground">
                    <ReplyIcon className="mt-0.5 size-3 shrink-0" />
                    <span className="truncate">
                      <span className="font-medium">{getSenderDisplayName(replyMessage)}</span>
                      <span className="ml-1">
                        {replyMessage.content_type === 'text' || replyMessage.content_type === 'Text'
                          ? replyMessage.content.length > 64
                            ? `${replyMessage.content.slice(0, 64)}...`
                            : replyMessage.content
                          : `[${replyMessage.content_type}]`}
                      </span>
                    </span>
                  </div>
                )}

                {/* Message content */}
                <div
                  className="min-w-0 break-words text-sm text-foreground"
                  title={grouped ? formatMessageTime(message.send_at) : undefined}
                >
                  {message.content_type === 'text' || message.content_type === 'Text' ? (
                    <>
                      <div className={cn('relative', isTextCollapsed && 'max-h-[5.25rem] overflow-hidden')}>
                        {functionCalls.length > 0 ? (
                          functionCalls.map((call, index) => (
                            <div key={index} className="my-1 rounded-md border border-border/70 bg-muted/20 p-2">
                              <FunctionCallBadge functionCall={call} className="w-auto" />
                            </div>
                          ))
                        ) : (
                          <div className="max-w-full min-w-0 overflow-hidden whitespace-pre-wrap break-words">
                            <MessageContentWithMentions content={displayContent} />
                          </div>
                        )}

                        {isStreaming && (
                          <span className="ml-0.5 inline-block size-1 animate-pulse align-middle rounded-full bg-primary" />
                        )}

                        {isTextCollapsed && (
                          <div className="pointer-events-none absolute inset-x-0 -bottom-1 h-14 bg-gradient-to-b from-transparent via-background/35 to-background/95" />
                        )}
                      </div>

                      {shouldCollapseText && (
                        <Button
                          variant="ghost"
                          size="sm"
                          onClick={() => setShowFullText((v) => !v)}
                          className="mt-1 h-auto gap-1 p-0 text-xs text-muted-foreground hover:text-foreground"
                        >
                          {showFullText ? (
                            <><ChevronUp className="size-3" /> Show less</>
                          ) : (
                            <><ChevronDown className="size-3" /> Show more</>
                          )}
                        </Button>
                      )}
                    </>
                  ) : (
                    <span className="text-muted-foreground">[{message.content_type}]</span>
                  )}
                </div>

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

                {/* Edited indicator - bottom right corner */}
                {isEdited && (
                  <div className="mt-1 flex items-center justify-end gap-0.5 text-[10px] text-muted-foreground/60">
                    <Edit2 className="size-2.5" />
                    <span>Edited</span>
                  </div>
                )}
              </>
            )}
          </>
        )}
      </div>

      {/* Reaction badges - OUTSIDE message content div so they align with action toolbar */}
      {(message.reactions?.length ?? 0) > 0 && (
        <div className="flex flex-wrap items-center gap-1">
          {message.reactions!.map((r) => (
            <Button
              key={r.emoji}
              variant="ghost"
              size="sm"
              onClick={() => handleReaction(r.emoji)}
              className={cn(
                'h-6 gap-1 rounded-full px-2 text-xs transition-colors',
                r.reacted_by_me
                  ? 'border border-primary/50 bg-primary/10 text-primary hover:bg-primary/20'
                  : 'border border-border bg-muted/20 text-muted-foreground hover:bg-muted/40'
              )}
            >
              <span>{r.emoji}</span>
              <span className="font-medium">{r.count}</span>
            </Button>
          ))}
        </div>
      )}

      {/* Action toolbar - inline icon buttons */}
      {!isEditing && !isRevoked && !isPending && (
        <div className="flex items-start gap-0.5 opacity-0 transition-opacity group-hover:opacity-100">
          {/* Add reaction */}
          <Popover open={showReactionPicker} onOpenChange={setShowReactionPicker}>
            <PopoverTrigger
              render={
                <Button
                  variant="ghost"
                  size="sm"
                  className="size-7 p-0 text-muted-foreground hover:bg-accent hover:text-foreground"
                  title="Add reaction"
                >
                  <SmilePlus className="size-3.5" />
                </Button>
              }
            />
            <PopoverContent className="w-auto p-2" align="start" sideOffset={4}>
              <p className="mb-2 text-xs font-medium text-muted-foreground">Select emoji</p>
              <div className="grid grid-cols-8 gap-1">
                {COMMON_EMOJIS.map((emoji) => (
                  <Button
                    key={emoji}
                    variant="ghost"
                    size="sm"
                    onClick={() => handleReaction(emoji)}
                    className="size-7 p-0 text-base hover:bg-accent"
                    title={emoji}
                  >
                    {emoji}
                  </Button>
                ))}
              </div>
            </PopoverContent>
          </Popover>
          {/* Reply */}
          {onReply && (
            <Button
              variant="ghost"
              size="sm"
              className="size-7 p-0 text-muted-foreground hover:bg-accent hover:text-foreground"
              onClick={() => onReply(message)}
              title="Reply"
            >
              <ReplyIcon className="size-3.5" />
            </Button>
          )}
          {/* Copy */}
          {message.content_type === 'text' && (
            <Button
              variant="ghost"
              size="sm"
              className="size-7 p-0 text-muted-foreground hover:bg-accent hover:text-foreground"
              onClick={async () => {
                try {
                  await navigator.clipboard.writeText(message.content);
                  toast.success('Message copied');
                } catch {
                  toast.error('Failed to copy');
                }
              }}
              title="Copy"
            >
              <Copy className="size-3.5" />
            </Button>
          )}
          {/* More menu */}
          <DropdownMenu>
            <DropdownMenuTrigger
              render={
                <Button
                  variant="ghost"
                  size="sm"
                  className="size-7 p-0 text-muted-foreground hover:bg-accent hover:text-foreground"
                  title="More"
                >
                  <MoreHorizontal className="size-3.5" />
                </Button>
              }
            />
            <DropdownMenuContent align="end">
              {onCreateThread && !message.thread_id && (
                <DropdownMenuItem onClick={() => onCreateThread(message)}>
                  <MessageSquare className="mr-2 size-4" />
                  Create thread
                </DropdownMenuItem>
              )}
              {message.edited_at && onViewHistory && (
                <DropdownMenuItem onClick={() => onViewHistory(message)}>
                  <History className="mr-2 size-4" />
                  View edit history
                </DropdownMenuItem>
              )}
              {isOwner && message.content_type === 'text' && (
                <DropdownMenuItem onClick={handleStartEdit}>
                  <Edit2 className="mr-2 size-4" />
                  Edit
                </DropdownMenuItem>
              )}
              {isOwner && onRevoke && (
                <DropdownMenuItem onClick={() => onRevoke(message)} className="text-destructive focus:text-destructive">
                  <Trash2 className="mr-2 size-4" />
                  Delete
                </DropdownMenuItem>
              )}
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
      )}

    </div>
  );
});
