"use client";

import { useEffect, useState, useRef, useCallback } from "react";
import { useParams, useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import Link from "next/link";
import Image from "next/image";
import { avatarUrl } from "@/lib/media";
import TypingIndicator from "@/components/shared/typing-indicator";
import VoiceRecorder from "@/components/shared/voice-recorder";
import LastSeen from "@/components/shared/last-seen";
import ChatHeaderMenu from "./ChatHeaderMenu";
import ChatExtras from "@/components/messaging/chat-extras";

interface Message {
  id: number;
  body: string | null;
  created_at: string;
  // R14 D.1: real read receipts — set when the recipient opens the room.
  read_at?: string | null;
  // R15 E.8: image attachment — same-origin URL stored on the message.
  attachment_url?: string | null;
  is_whisper?: boolean;
  user: {
    id: number;
    username: string | null;
    profile_photo: string | null;
    lastonline_at?: string | null;
  };
}

/* ── Date helpers ─────────────────────────────────────────────────── */

function isSameDay(a: Date, b: Date) {
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}

function formatDateSeparator(dateStr: string): string {
  const date = new Date(dateStr);
  const now = new Date();
  const yesterday = new Date(now);
  yesterday.setDate(yesterday.getDate() - 1);

  if (isSameDay(date, now)) return "Today";
  if (isSameDay(date, yesterday)) return "Yesterday";
  return date.toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric" });
}

function formatMessageTime(dateStr: string): string {
  return new Date(dateStr).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}

/* ── Loading skeleton ─────────────────────────────────────────────── */

function ChatSkeleton() {
  return (
    <div className="max-w-3xl mx-auto flex flex-col h-[calc(100dvh-200px)]">
      {/* Header skeleton */}
      <div className="flex items-center gap-3 p-4 bg-surface rounded-xl mb-4 animate-pulse border border-white/5">
        <div className="w-10 h-10 rounded-full bg-surface-light" />
        <div className="space-y-2">
          <div className="h-4 w-28 bg-surface-light rounded" />
          <div className="h-3 w-16 bg-surface-light rounded" />
        </div>
      </div>
      {/* Message bubbles skeleton */}
      <div className="flex-1 overflow-hidden bg-surface rounded-xl p-4 space-y-4 border border-white/5">
        {/* Left bubble */}
        <div className="flex gap-3 animate-pulse">
          <div className="w-8 h-8 rounded-full bg-surface-light shrink-0" />
          <div className="space-y-1.5">
            <div className="h-10 w-52 bg-surface-light rounded-2xl rounded-tl-sm" />
            <div className="h-3 w-12 bg-surface-light/50 rounded" />
          </div>
        </div>
        {/* Right bubble */}
        <div className="flex gap-3 flex-row-reverse animate-pulse">
          <div className="w-8 h-8 rounded-full bg-surface-light shrink-0" />
          <div className="space-y-1.5 flex flex-col items-end">
            <div className="h-10 w-64 bg-primary/20 rounded-2xl rounded-tr-sm" />
            <div className="h-3 w-12 bg-surface-light/50 rounded" />
          </div>
        </div>
        {/* Left bubble */}
        <div className="flex gap-3 animate-pulse">
          <div className="w-8 h-8 rounded-full bg-surface-light shrink-0" />
          <div className="space-y-1.5">
            <div className="h-16 w-72 bg-surface-light rounded-2xl rounded-tl-sm" />
            <div className="h-3 w-12 bg-surface-light/50 rounded" />
          </div>
        </div>
        {/* Right bubble */}
        <div className="flex gap-3 flex-row-reverse animate-pulse">
          <div className="w-8 h-8 rounded-full bg-surface-light shrink-0" />
          <div className="space-y-1.5 flex flex-col items-end">
            <div className="h-10 w-40 bg-primary/20 rounded-2xl rounded-tr-sm" />
            <div className="h-3 w-12 bg-surface-light/50 rounded" />
          </div>
        </div>
      </div>
      {/* Input skeleton */}
      <div className="flex gap-2 mt-4 animate-pulse">
        <div className="flex-1 h-12 bg-surface rounded-xl" />
        <div className="w-20 h-12 bg-surface-light rounded-xl" />
      </div>
    </div>
  );
}

/* ── Main component ───────────────────────────────────────────────── */

export default function ChatPage() {
  const { roomId } = useParams();
  const router = useRouter();
  const { data: session, status } = useSession();
  const [messages, setMessages] = useState<Message[]>([]);
  const [newMessage, setNewMessage] = useState("");
  // R15 E.8: image attachment staged before send. Cleared on send / cancel.
  const [pendingAttachment, setPendingAttachment] = useState<string | null>(null);
  const [sending, setSending] = useState(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [isTyping, setIsTyping] = useState(false);
  const [creditError, setCreditError] = useState<string | null>(null);
  const [messageCost, setMessageCost] = useState<number | null>(null);
  const [isWhisper, setIsWhisper] = useState(false);
  const [whisperSeen, setWhisperSeen] = useState<Record<number, boolean>>({});
  const [whisperFading, setWhisperFading] = useState<Record<number, boolean>>({});
  const [translations, setTranslations] = useState<Record<number, string>>({});
  const [translating, setTranslating] = useState<Record<number, boolean>>({});
  const typingTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messagesContainerRef = useRef<HTMLDivElement>(null);
  const pollRef = useRef<ReturnType<typeof setInterval>>(undefined);
  const prevMessageCountRef = useRef(0);

  const currentUserId = session?.user?.id ? Number(session.user.id) : 0;

  // Derive the other participant from messages
  const otherUser = messages.find((m) => m.user.id !== currentUserId)?.user ?? null;

  const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
    messagesEndRef.current?.scrollIntoView({ behavior });
  }, []);

  // Track initial-load via a ref so we don't have to put `loading` in the
  // useCallback deps — earlier code did, which made the callback identity
  // change on every fetch, tearing down and recreating the setInterval.
  // Result was duplicate in-flight requests every cycle.
  const isInitialLoadRef = useRef(true);
  // R15 C.4: track the last-seen message id so polls only request new
  // messages with `?since=<id>`. The server returns just the delta which
  // we append to the local state instead of replacing the whole array.
  const lastIdRef = useRef<number | null>(null);
  const loadMessages = useCallback(async () => {
    try {
      const url = lastIdRef.current
        ? `/api/messages/${roomId}?since=${lastIdRef.current}`
        : `/api/messages/${roomId}`;
      const res = await fetch(url);
      if (res.status === 401) {
        router.push("/login");
        return;
      }
      if (!res.ok) {
        throw new Error(`Failed to load messages (${res.status})`);
      }
      const data = await res.json();
      const incoming = Array.isArray(data?.data) ? data.data : [];
      if (incoming.length > 0) {
        if (lastIdRef.current === null) {
          // First load — replace the whole array.
          setMessages(incoming);
        } else {
          // Incremental — append. Filter out any rows the client already
          // has (in case of overlapping deliveries) by id.
          setMessages((prev) => {
            const seen = new Set(prev.map((m) => m.id));
            const fresh = incoming.filter((m: { id: number }) => !seen.has(m.id));
            return fresh.length > 0 ? prev.concat(fresh) : prev;
          });
        }
        const maxId = incoming.reduce(
          (acc: number, m: { id: number }) => (m.id > acc ? m.id : acc),
          lastIdRef.current ?? 0,
        );
        lastIdRef.current = maxId;
      } else if (lastIdRef.current === null) {
        // Empty initial load — still publish the empty state.
        setMessages([]);
      }
      setError(null);
    } catch (err) {
      if (isInitialLoadRef.current) {
        setError(err instanceof Error ? err.message : "Failed to load messages");
      }
    } finally {
      isInitialLoadRef.current = false;
      setLoading(false);
    }
  }, [roomId, router]);

  useEffect(() => {
    isInitialLoadRef.current = true;
    lastIdRef.current = null;

    // C.8: pause polling while the tab is hidden. Earlier setInterval fired
    // unconditionally, so 100 users with chat in a background tab generated
    // ~2,000 wasted /api/messages calls per minute and drained mobile battery.
    const startPoll = () => {
      if (pollRef.current) return;
      pollRef.current = setInterval(loadMessages, 3000);
    };
    const stopPoll = () => {
      if (pollRef.current) {
        clearInterval(pollRef.current);
        pollRef.current = undefined;
      }
    };

    loadMessages();
    if (typeof document !== "undefined" && !document.hidden) startPoll();

    const onVisibility = () => {
      if (document.hidden) {
        stopPoll();
      } else {
        loadMessages();
        startPoll();
      }
    };
    if (typeof document !== "undefined") {
      document.addEventListener("visibilitychange", onVisibility);
    }

    return () => {
      stopPoll();
      if (typeof document !== "undefined") {
        document.removeEventListener("visibilitychange", onVisibility);
      }
    };
  }, [roomId, loadMessages]);

  // Fetch recipient messaging cost once we know who they are
  useEffect(() => {
    if (!otherUser) return;
    fetch(`/api/messaging/settings/check?user_id=${otherUser.id}`)
      .then((r) => r.json())
      .then((data) => {
        if (data.data?.enabled && data.data?.per_message_price > 0) {
          setMessageCost(data.data.per_message_price);
        }
      })
      .catch(() => {});
  }, [otherUser?.id]);

  // Auto-scroll when new messages arrive
  useEffect(() => {
    if (messages.length > prevMessageCountRef.current) {
      // Instant scroll on initial load, smooth on new messages
      scrollToBottom(prevMessageCountRef.current === 0 ? "instant" : "smooth");
    }
    prevMessageCountRef.current = messages.length;
  }, [messages.length, scrollToBottom]);

  // Redirect to login if not authenticated
  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/login");
    }
  }, [status, router]);

  function handleTyping() {
    setIsTyping(true);
    if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
    typingTimeoutRef.current = setTimeout(() => setIsTyping(false), 2000);
  }

  async function handleSend(e: React.FormEvent) {
    e.preventDefault();
    // R15 E.8: allow sending an image-only message with empty body.
    if ((!newMessage.trim() && !pendingAttachment) || sending) return;

    setSending(true);
    try {
      const recipientId = otherUser?.id ?? messages[0]?.user?.id;
      const res = await fetch("/api/messages", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          recipient_id: recipientId,
          body: newMessage,
          room_id: roomId,
          is_whisper: isWhisper,
          attachment_url: pendingAttachment ?? undefined,
        }),
      });
      if (res.status === 401) {
        router.push("/login");
        return;
      }
      if (res.ok) {
        setNewMessage("");
        setPendingAttachment(null);
        setIsWhisper(false);
        setCreditError(null);
        await loadMessages();
      } else if (res.status === 402) {
        const data = await res.json();
        setCreditError(`Insufficient credits. Messages cost ${data.cost} credits each.`);
      }
    } finally {
      setSending(false);
    }
  }

  async function handleTranslate(msgId: number, text: string) {
    if (translations[msgId] || translating[msgId]) return;
    setTranslating((prev) => ({ ...prev, [msgId]: true }));
    try {
      const res = await fetch("/api/ai/translate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ text, target_lang: "English" }),
      });
      if (res.ok) {
        const data = await res.json();
        if (data.translated) {
          setTranslations((prev) => ({ ...prev, [msgId]: data.translated }));
        }
      }
    } catch {
      // Silently fail
    } finally {
      setTranslating((prev) => ({ ...prev, [msgId]: false }));
    }
  }

  async function handleVoiceReady(audioUrl: string) {
    setSending(true);
    try {
      const recipientId = otherUser?.id ?? messages[0]?.user?.id;
      const voiceBody = `🎤 Voice message: ${audioUrl}`;
      const res = await fetch("/api/messages", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ recipient_id: recipientId, body: voiceBody, room_id: roomId }),
      });
      if (res.ok) {
        await loadMessages();
      } else if (res.status === 402) {
        const data = await res.json();
        setCreditError(`Insufficient credits. Messages cost ${data.cost} credits each.`);
      }
    } finally {
      setSending(false);
    }
  }

  if (status === "loading" || loading) {
    return <ChatSkeleton />;
  }

  /* ── Error state ──────────────────────────────────────────────── */
  if (error) {
    return (
      <div className="max-w-3xl mx-auto flex flex-col items-center justify-center h-[calc(100dvh-200px)]">
        <div className="bg-surface rounded-xl p-12 text-center border border-white/5 w-full max-w-md">
          <div className="mx-auto mb-5 w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center">
            <svg className="w-8 h-8 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
                d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
              />
            </svg>
          </div>
          <h2 className="text-lg font-semibold text-text mb-2">Something went wrong</h2>
          <p className="text-text-muted text-sm mb-6">{error}</p>
          <button
            onClick={() => { setError(null); setLoading(true); loadMessages(); }}
            className="inline-flex items-center gap-2 px-5 py-2.5 gradient-gold text-background font-semibold rounded-lg hover:opacity-90 transition-opacity"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
              />
            </svg>
            Try again
          </button>
        </div>
      </div>
    );
  }

  /* ── Group messages by date ─────────────────────────────────────── */
  const groupedMessages: { date: string; label: string; messages: Message[] }[] = [];
  for (const msg of messages) {
    const dateKey = new Date(msg.created_at).toDateString();
    const last = groupedMessages[groupedMessages.length - 1];
    if (last && last.date === dateKey) {
      last.messages.push(msg);
    } else {
      groupedMessages.push({ date: dateKey, label: formatDateSeparator(msg.created_at), messages: [msg] });
    }
  }

  return (
    <div className="max-w-3xl mx-auto flex flex-col h-[calc(100dvh-200px)]">

      {/* ── Header bar ──────────────────────────────────────────── */}
      <div className="flex items-center gap-3 p-4 bg-surface rounded-xl mb-4 border border-white/5">
        <Link href="/messages" className="mr-1 p-1 rounded-lg hover:bg-surface-light transition-colors text-text-muted hover:text-text">
          <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
          </svg>
        </Link>
        {otherUser ? (
          <>
            <img
              src={avatarUrl(otherUser.profile_photo)}
              alt=""
              className="w-10 h-10 rounded-full object-cover ring-2 ring-white/10"
            />
            <div className="min-w-0 flex-1">
              <p className="font-medium text-text truncate leading-tight">
                {otherUser.username || "User"}
              </p>
              {/* R14 B.1: real last-seen subtitle (was static "Conversation") */}
              <LastSeen lastonlineAt={otherUser.lastonline_at ?? null} />
            </div>
            {/* R14 B.1: kebab menu — Report + Block from inside the chat. */}
            <ChatHeaderMenu otherUserId={otherUser.id} otherUsername={otherUser.username || "User"} />
          </>
        ) : (
          <div className="min-w-0">
            <p className="font-medium text-text truncate leading-tight">Chat</p>
            <p className="text-xs text-text-muted">Conversation</p>
          </div>
        )}
      </div>

      {/* ── Messages area ───────────────────────────────────────── */}
      <div
        ref={messagesContainerRef}
        className="flex-1 overflow-y-auto mb-4 bg-surface rounded-xl p-4 border border-white/5"
      >
        {messages.length === 0 ? (
          <div className="flex flex-col items-center justify-center h-full text-center py-16">
            <div className="w-16 h-16 rounded-full bg-surface-light flex items-center justify-center mb-4">
              <svg className="w-8 h-8 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
                  d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
                />
              </svg>
            </div>
            <p className="text-text font-medium mb-1">No messages yet</p>
            <p className="text-text-muted text-sm max-w-xs">
              Send your first message to start the conversation.
            </p>
          </div>
        ) : (
          <div className="space-y-4">
            {groupedMessages.map((group) => (
              <div key={group.date}>
                {/* Date separator */}
                <div className="flex items-center gap-3 my-4">
                  <div className="flex-1 h-px bg-white/5" />
                  <span className="text-[11px] font-medium text-text-muted uppercase tracking-wider px-2">
                    {group.label}
                  </span>
                  <div className="flex-1 h-px bg-white/5" />
                </div>

                {/* Messages in this date group */}
                <div className="space-y-3">
                  {group.messages.map((msg) => {
                    const isMe = msg.user.id === currentUserId;
                    const isWhisperMsg = msg.is_whisper === true;
                    const isFading = whisperFading[msg.id];
                    const isGone = whisperSeen[msg.id] && isFading;

                    // For received whisper messages, start fade timer when first seen
                    if (isWhisperMsg && !isMe && !whisperSeen[msg.id]) {
                      setTimeout(() => {
                        setWhisperSeen((prev) => ({ ...prev, [msg.id]: true }));
                        setTimeout(() => {
                          setWhisperFading((prev) => ({ ...prev, [msg.id]: true }));
                        }, 10000);
                      }, 500);
                    }

                    return (
                      <div
                        key={msg.id}
                        className={`flex gap-2.5 ${isMe ? "flex-row-reverse" : ""} transition-opacity duration-1000 ${isGone ? "opacity-0" : "opacity-100"}`}
                      >
                        <Image
                          src={avatarUrl(msg.user.profile_photo)}
                          alt=""
                          width={32}
                          height={32}
                          className="w-8 h-8 rounded-full object-cover shrink-0 ring-1 ring-white/10 self-end"
                        />
                        <div className="max-w-[70%]">
                          <div
                            className={`rounded-2xl px-4 py-2.5 ${
                              isWhisperMsg
                                ? "backdrop-blur-md bg-white/5 border border-white/10 text-text"
                                : isMe
                                ? "bg-primary/90 text-white rounded-tr-sm"
                                : "bg-surface-light text-text rounded-tl-sm"
                            }`}
                          >
                            {isWhisperMsg && (
                              <p className="text-[10px] text-purple-400 font-medium mb-1 flex items-center gap-1">
                                <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
                                </svg>
                                Self-destructs after reading
                              </p>
                            )}
                            {msg.body?.startsWith("🎤 Voice message:") ? (
                              <audio
                                controls
                                src={msg.body.replace("🎤 Voice message: ", "")}
                                className="max-w-full h-8"
                                onError={(e) => {
                                  // Replace the audio element with an inline error
                                  // so a deleted/expired upload doesn't leave the
                                  // user staring at a silent broken player.
                                  const el = e.currentTarget;
                                  const note = document.createElement("p");
                                  note.className = "text-xs italic text-text-muted";
                                  note.textContent = "Voice message unavailable.";
                                  el.replaceWith(note);
                                }}
                              />
                            ) : (
                              <>
                                {msg.attachment_url && (
                                  // R15 E.8: image attachment renders inline
                                  // above the body. Same-origin enforced
                                  // server-side.
                                  // eslint-disable-next-line @next/next/no-img-element
                                  <img
                                    src={msg.attachment_url}
                                    alt=""
                                    className="max-w-full max-h-72 rounded mb-1"
                                  />
                                )}
                                {msg.body && (
                                  <p className="text-sm leading-relaxed whitespace-pre-wrap break-words">{msg.body}</p>
                                )}
                              </>
                            )}
                            <div className={`flex items-center gap-1.5 mt-1 ${isMe ? "justify-end" : ""}`}>
                              <p className={`text-[11px] ${isMe ? "text-white/50" : "text-text-muted"}`}>
                                {formatMessageTime(msg.created_at)}
                              </p>
                              {/* R14 D.1: real read receipts. Single check
                                  = sent (read_at null); double check = read
                                  (recipient opened the room). Renders only
                                  on the sender's own bubbles. */}
                              {isMe && (
                                msg.read_at ? (
                                  <svg className="w-3.5 h-3.5 text-blue-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} aria-label="Read">
                                    <path strokeLinecap="round" strokeLinejoin="round" d="M2 12l5 5L18 6" />
                                    <path strokeLinecap="round" strokeLinejoin="round" d="M7 12l5 5L23 6" />
                                  </svg>
                                ) : (
                                  <svg className="w-3.5 h-3.5 text-white/40 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} aria-label="Sent">
                                    <path strokeLinecap="round" strokeLinejoin="round" d="M5 12l5 5L20 7" />
                                  </svg>
                                )
                              )}
                            </div>
                          </div>
                          {/* Translate link for received messages */}
                          {!isMe && msg.body && !msg.body.startsWith("🎤") && (
                            <div className="mt-1 ml-1">
                              {translations[msg.id] ? (
                                <p className="text-xs text-text-muted italic">{translations[msg.id]}</p>
                              ) : (
                                <button
                                  type="button"
                                  onClick={() => handleTranslate(msg.id, msg.body || "")}
                                  disabled={translating[msg.id]}
                                  className="text-[11px] text-primary/70 hover:text-primary transition-colors disabled:opacity-50"
                                >
                                  {translating[msg.id] ? "Translating..." : "Translate"}
                                </button>
                              )}
                            </div>
                          )}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))}

            <TypingIndicator isTyping={isTyping} />
            <div ref={messagesEndRef} />
          </div>
        )}
      </div>

      {/* ── Credit cost info bar ────────────────────────────────── */}
      {messageCost !== null && messageCost > 0 && (
        <div className="rounded-xl px-4 py-2.5 mb-3 flex items-center gap-2.5 bg-primary/10 border border-primary/20">
          <div className="w-7 h-7 rounded-full bg-primary/20 flex items-center justify-center shrink-0">
            <svg className="w-3.5 h-3.5 text-gold" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"
              />
            </svg>
          </div>
          <span className="text-sm text-text-muted">
            Messages to this escort cost <span className="text-gold font-semibold">{messageCost} credits</span> each
          </span>
        </div>
      )}

      {/* ── Credit error ────────────────────────────────────────── */}
      {creditError && (
        <div className="bg-red-500/10 border border-red-500/20 rounded-xl px-4 py-2.5 mb-3 flex items-center gap-2.5">
          <svg className="w-4 h-4 text-red-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
              d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
            />
          </svg>
          <span className="text-sm text-red-400">{creditError}</span>
          <Link href="/credits" className="ml-auto text-xs font-medium text-gold hover:text-gold-light transition-colors">
            Buy credits
          </Link>
        </div>
      )}

      {/* R15 E.8: pending image attachment preview. Click ✕ to drop it. */}
      {pendingAttachment && (
        <div className="mb-2 flex items-center gap-2 bg-surface border border-surface-light rounded-lg p-2">
          <Image
            src={pendingAttachment}
            alt="Attachment preview"
            width={48}
            height={48}
            className="rounded object-cover"
          />
          <span className="text-sm text-text-muted truncate flex-1">Image ready to send</span>
          <button
            type="button"
            onClick={() => setPendingAttachment(null)}
            className="text-text-muted hover:text-red-400 px-2"
            aria-label="Remove attachment"
          >
            ×
          </button>
        </div>
      )}

      {/* ── Input bar ───────────────────────────────────────────── */}
      {/* R17 A.2: pb-safe so iOS home-indicator doesn't sit on the
          Send button. The chat container above uses h-[calc(100dvh-200px)]
          which collapses with the on-screen keyboard via dvh, so the
          input naturally stays above the keyboard on Safari/Chrome
          mobile. */}
      <form
        onSubmit={handleSend}
        className="flex gap-2 items-end pb-[env(safe-area-inset-bottom)]"
        style={{ paddingBottom: "max(env(safe-area-inset-bottom), 0.25rem)" }}
      >
        <VoiceRecorder onVoiceReady={handleVoiceReady} disabled={sending} />
        <ChatExtras
          onInsertReply={(text) => setNewMessage((prev) => (prev ? `${prev} ${text}` : text))}
          onAttachmentReady={setPendingAttachment}
          disabled={sending}
        />
        <input
          type="text"
          value={newMessage}
          onChange={(e) => { setNewMessage(e.target.value); handleTyping(); }}
          placeholder={isWhisper ? "Whisper a secret..." : "Type a message..."}
          className={`flex-1 rounded-xl border bg-surface px-4 py-3 text-text placeholder-text-muted focus:ring-1 focus:outline-none transition-all ${
            isWhisper
              ? "border-purple-500/30 focus:border-purple-500 focus:ring-purple-500/30"
              : "border-white/10 focus:border-primary focus:ring-primary/30"
          }`}
        />
        <button
          type="button"
          onClick={() => setIsWhisper(!isWhisper)}
          title={isWhisper ? "Whisper mode ON" : "Whisper mode OFF"}
          className={`p-3 rounded-xl border transition-all ${
            isWhisper
              ? "bg-purple-500/20 border-purple-500/30 text-purple-400"
              : "bg-surface border-white/10 text-text-muted hover:text-text"
          }`}
        >
          <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
              d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
              d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
          </svg>
        </button>
        <button
          type="submit"
          disabled={(!newMessage.trim() && !pendingAttachment) || sending}
          className={`px-5 py-3 rounded-xl font-semibold transition-all disabled:opacity-40 disabled:cursor-not-allowed hover:opacity-90 active:scale-[0.97] ${
            isWhisper ? "bg-purple-600 text-white" : "gradient-gold text-background"
          }`}
        >
          {sending ? (
            <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
            </svg>
          ) : (
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
                d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
              />
            </svg>
          )}
        </button>
      </form>
    </div>
  );
}
