"use client";

import {
  Fragment,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ChangeEvent,
  type KeyboardEvent,
  type ReactNode,
} from "react";
import { Icon } from "@/components/ui/Icon";
import { Avatar } from "@/components/ui/Avatar";
import {
  sendMessageAction,
  markChannelReadAction,
  toggleReactionAction,
  pinMessageAction,
  deleteOwnMessageAction,
  type UploadedAttachment,
} from "@/lib/chat/actions";
import { REACTIONS, DELETE_WINDOW_MS, EMOJIS } from "@/lib/chat/constants";
import { confirmDialog } from "@/components/ui/Dialog";
import { VoicePlayer } from "@/components/ui/VoicePlayer";
import { playPing } from "@/lib/ui/sound";
import { usePresence } from "@/lib/ui/usePresence";
import type { PresenceInfo } from "@/lib/presence";
import type {
  ChatMessage,
  ChatAttachment,
  ChannelMemberDTO,
  ReadReceipt,
} from "@/lib/chat/queries";

// ── Zustellstatus eigener Nachrichten ─────────────────────────────────────
type Delivery = "sending" | "sent" | "delivered" | "read";

const DELIVERY_META: Record<Delivery, { icon: string; label: string; color?: string }> = {
  sending: { icon: "clock", label: "Wird gesendet" },
  sent: { icon: "check", label: "Gesendet" },
  delivered: { icon: "checks", label: "Angekommen" },
  read: { icon: "checks", label: "Gelesen", color: "var(--blue)" },
};

/**
 * „Angekommen" heißt: mindestens eine andere Person war nach dem Absenden
 * online (Präsenz-Signal). „Gelesen" heißt: alle anderen haben den Kanal
 * seitdem geöffnet.
 */
function deliveryOf(msg: ChatMessage, receipts: ReadReceipt[]): Delivery {
  if (msg.id.startsWith("tmp-")) return "sending";
  if (receipts.length === 0) return "sent";

  const at = new Date(msg.createdAt).getTime();
  const seenBy = (v: string | null) => v !== null && new Date(v).getTime() >= at;

  if (receipts.every((r) => seenBy(r.lastReadAt))) return "read";
  if (receipts.some((r) => seenBy(r.lastSeenAt) || seenBy(r.lastReadAt))) return "delivered";
  return "sent";
}

function DeliveryMark({ state }: { state: Delivery }) {
  const meta = DELIVERY_META[state];
  return (
    <span className="msg-delivery" title={meta.label} style={meta.color ? { color: meta.color } : undefined}>
      <Icon name={meta.icon} size={13} />
    </span>
  );
}

// ── Helfer ────────────────────────────────────────────────────────────────
function formatTime(iso: string): string {
  return new Date(iso).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
}
function sameDay(a: string, b: string): boolean {
  return new Date(a).toDateString() === new Date(b).toDateString();
}
function dayLabel(iso: string): string {
  const d = new Date(iso);
  const today = new Date();
  const yest = new Date();
  yest.setDate(today.getDate() - 1);
  if (d.toDateString() === today.toDateString()) return "Heute";
  if (d.toDateString() === yest.toDateString()) return "Gestern";
  return d.toLocaleDateString("de-DE", { day: "2-digit", month: "long", year: "numeric" });
}
/** Kurzvorschau einer Nachricht (Text oder Anhang) fürs Zitieren. */
function previewOf(m: ChatMessage): string {
  if (m.body.trim()) return m.body.slice(0, 80);
  const a = m.attachments[0];
  if (a?.kind === "IMAGE") return "📷 Bild";
  if (a?.kind === "AUDIO") return "🎤 Sprachnachricht";
  if (a?.kind === "FILE") return "📎 Datei";
  return "Nachricht";
}
function renderBody(body: string): ReactNode[] {
  const parts: ReactNode[] = [];
  const regex = /@([\p{L}0-9_]+)/gu;
  let last = 0;
  let m: RegExpExecArray | null;
  let i = 0;
  while ((m = regex.exec(body)) !== null) {
    if (m.index > last) parts.push(body.slice(last, m.index));
    parts.push(<span key={`m${i++}`} className="mention">@{m[1]}</span>);
    last = m.index + m[0].length;
  }
  if (last < body.length) parts.push(body.slice(last));
  return parts;
}
function imageDims(file: File): Promise<{ w: number; h: number }> {
  return new Promise((resolve) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      resolve({ w: img.naturalWidth, h: img.naturalHeight });
      URL.revokeObjectURL(url);
    };
    img.onerror = () => resolve({ w: 0, h: 0 });
    img.src = url;
  });
}
export function jumpToMessage(id: string) {
  const el = document.getElementById(`msg-${id}`);
  if (!el) return;
  el.scrollIntoView({ behavior: "smooth", block: "center" });
  el.classList.remove("msg-jump-target");
  // Reflow erzwingen, damit die Animation auch bei erneutem Sprung startet.
  void el.offsetWidth;
  el.classList.add("msg-jump-target");
  setTimeout(() => el.classList.remove("msg-jump-target"), 1600);
}

type Pending = UploadedAttachment & { previewUrl?: string };
type ReplyTarget = { id: string; author: string; preview: string };

function AttachmentView({ att, onZoom }: { att: ChatAttachment; onZoom: (url: string) => void }) {
  if (att.kind === "IMAGE") {
    return (
      // eslint-disable-next-line @next/next/no-img-element
      <img className="att-image" src={att.url} alt={att.name} onClick={() => onZoom(att.url)} />
    );
  }
  if (att.kind === "AUDIO") {
    return <VoicePlayer src={att.url} durationMs={att.durationMs} />;
  }
  return (
    <a className="att-file" href={att.url} target="_blank" rel="noreferrer">
      <Icon name="pin" size={18} />
      <span className="min-w-0 flex-1 truncate font-bold">{att.name}</span>
    </a>
  );
}

// ── Hauptkomponente ─────────────────────────────────────────────────────────
export function MessageThread({
  channelId,
  initialMessages,
  currentUserId,
  canSend,
  members,
  initialPresence,
  initialReceipts,
}: {
  channelId: string;
  initialMessages: ChatMessage[];
  currentUserId: string;
  canSend: boolean;
  members: ChannelMemberDTO[];
  initialPresence: Record<string, PresenceInfo>;
  initialReceipts: ReadReceipt[];
}) {
  const presence = usePresence(
    members.map((m) => m.id),
    initialPresence,
  );
  const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);
  const [receipts, setReceipts] = useState<ReadReceipt[]>(initialReceipts);
  const [text, setText] = useState("");
  const [pending, setPending] = useState<Pending[]>([]);
  const [replyTo, setReplyTo] = useState<ReplyTarget | null>(null);
  const [sending, setSending] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [zoom, setZoom] = useState<string | null>(null);
  const [recording, setRecording] = useState(false);
  const [pickerFor, setPickerFor] = useState<string | null>(null);
  const [copiedId, setCopiedId] = useState<string | null>(null);
  const [emojiOpen, setEmojiOpen] = useState(false);
  const [mentionQuery, setMentionQuery] = useState<{ q: string; start: number } | null>(null);

  const listRef = useRef<HTMLDivElement>(null);
  const taRef = useRef<HTMLTextAreaElement>(null);
  const fileRef = useRef<HTMLInputElement>(null);
  const lastIdRef = useRef<string | null>(initialMessages[initialMessages.length - 1]?.id ?? null);
  const recorderRef = useRef<MediaRecorder | null>(null);
  const chunksRef = useRef<Blob[]>([]);
  const recordStartRef = useRef<number>(0);

  const mentionCandidates = useMemo(
    () => members.filter((m) => m.id !== currentUserId),
    [members, currentUserId],
  );

  const isNearBottom = () => {
    const el = listRef.current;
    if (!el) return true;
    return el.scrollHeight - el.scrollTop - el.clientHeight < 160;
  };
  const scrollToBottom = useCallback(() => {
    const el = listRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, []);

  useEffect(() => {
    scrollToBottom();
    markChannelReadAction(channelId);
  }, [channelId, scrollToBottom]);

  useEffect(() => {
    let alive = true;
    const poll = async () => {
      try {
        const res = await fetch(`/api/chat/${channelId}/messages`, { cache: "no-store" });
        if (!res.ok || !alive) return;
        const data = await res.json();
        setMessages(data.messages as ChatMessage[]);
        if (data.receipts) setReceipts(data.receipts as ReadReceipt[]);
      } catch {
        /* still */
      }
    };
    const id = setInterval(poll, 4000);
    return () => {
      alive = false;
      clearInterval(id);
    };
  }, [channelId]);

  useEffect(() => {
    const newest = messages[messages.length - 1];
    if (!newest || newest.id === lastIdRef.current) return;
    const isIncoming = newest.authorId !== currentUserId && !newest.id.startsWith("tmp-");
    const stick = isNearBottom() || newest.authorId === currentUserId;
    lastIdRef.current = newest.id;
    if (stick) requestAnimationFrame(scrollToBottom);
    if (isIncoming) {
      markChannelReadAction(channelId);
      void playPing(); // Benachrichtigungston bei neuer eingehender Nachricht
    }
  }, [messages, channelId, currentUserId, scrollToBottom]);

  // ── Datei-Upload ──
  const uploadFile = async (file: File, extra: Record<string, number | null> = {}) => {
    const fd = new FormData();
    fd.append("file", file);
    for (const [k, v] of Object.entries(extra)) if (v) fd.append(k, String(v));
    const res = await fetch(`/api/chat/${channelId}/upload`, { method: "POST", body: fd });
    if (!res.ok) {
      const d = await res.json().catch(() => ({}));
      throw new Error(d.error ?? "Upload fehlgeschlagen.");
    }
    return (await res.json()) as UploadedAttachment;
  };

  const onFiles = async (e: ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files ?? []);
    e.target.value = "";
    if (files.length === 0) return;
    setBusy(true);
    setError(null);
    try {
      for (const file of files) {
        const extra: Record<string, number | null> = {};
        let previewUrl: string | undefined;
        if (file.type.startsWith("image/")) {
          const { w, h } = await imageDims(file);
          extra.width = w;
          extra.height = h;
          previewUrl = URL.createObjectURL(file);
        }
        const up = await uploadFile(file, extra);
        setPending((p) => [...p, { ...up, previewUrl }]);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Upload fehlgeschlagen.");
    } finally {
      setBusy(false);
    }
  };

  // ── Sprachaufnahme ──
  const startRecording = async () => {
    if (!navigator.mediaDevices?.getUserMedia) {
      setError("Aufnahme wird vom Browser nicht unterstützt.");
      return;
    }
    try {
      // Hochwertige Mono-Aufnahme mit Rauschunterdrückung & Echo-Cancelling.
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true,
          channelCount: 1,
          sampleRate: 48000,
        },
      });
      // Vom Browser tatsächlich unterstütztes Format wählen (Chrome=webm, Safari=mp4).
      const candidates = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg"];
      const chosen =
        typeof MediaRecorder.isTypeSupported === "function"
          ? candidates.find((t) => MediaRecorder.isTypeSupported(t))
          : undefined;
      const rec = new MediaRecorder(stream, {
        ...(chosen ? { mimeType: chosen } : {}),
        audioBitsPerSecond: 128000,
      });
      chunksRef.current = [];
      rec.ondataavailable = (ev) => ev.data.size > 0 && chunksRef.current.push(ev.data);
      rec.onstop = async () => {
        stream.getTracks().forEach((t) => t.stop());
        const durationMs = Date.now() - recordStartRef.current;
        // Reinen MIME-Typ (ohne codecs=…) verwenden, damit Upload & Wiedergabe passen.
        const rawMime = rec.mimeType || chosen || "audio/webm";
        const mime = rawMime.split(";")[0];
        const ext = mime.includes("mp4") ? "m4a" : mime.includes("ogg") ? "ogg" : "webm";
        const blob = new Blob(chunksRef.current, { type: mime });
        const file = new File([blob], `sprachnachricht.${ext}`, { type: mime });
        setBusy(true);
        try {
          const up = await uploadFile(file, { durationMs });
          setPending((p) => [...p, { ...up, previewUrl: URL.createObjectURL(blob) }]);
        } catch (err) {
          setError(err instanceof Error ? err.message : "Upload fehlgeschlagen.");
        } finally {
          setBusy(false);
        }
      };
      recorderRef.current = rec;
      recordStartRef.current = Date.now();
      rec.start();
      setRecording(true);
    } catch (err) {
      const name = err instanceof DOMException ? err.name : "";
      setError(
        name === "NotAllowedError"
          ? "Mikrofon-Zugriff wurde abgelehnt."
          : "Mikrofon nicht verfügbar.",
      );
    }
  };
  const stopRecording = () => {
    recorderRef.current?.stop();
    setRecording(false);
  };

  // ── @-Mention ──
  const onTextChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
    const v = e.target.value;
    setText(v);
    const el = e.target;
    el.style.height = "auto";
    el.style.height = `${Math.min(el.scrollHeight, 140)}px`;
    const caret = el.selectionStart ?? v.length;
    const m = v.slice(0, caret).match(/(?:^|\s)@([\p{L}0-9_]*)$/u);
    setMentionQuery(m ? { q: m[1].toLowerCase(), start: caret - m[1].length - 1 } : null);
  };
  const applyMention = (member: ChannelMemberDTO) => {
    if (!mentionQuery) return;
    const firstName = member.name.split(/\s+/)[0];
    const caret = taRef.current?.selectionStart ?? text.length;
    setText(`${text.slice(0, mentionQuery.start)}@${firstName} ${text.slice(caret)}`);
    setMentionQuery(null);
    requestAnimationFrame(() => taRef.current?.focus());
  };
  const mentionMatches = mentionQuery
    ? mentionCandidates.filter((m) => m.name.toLowerCase().includes(mentionQuery.q)).slice(0, 6)
    : [];

  // ── Nachrichten-Aktionen ──
  const toggleReaction = (mid: string, emoji: string) => {
    setPickerFor(null);
    setMessages((prev) =>
      prev.map((m) => {
        if (m.id !== mid) return m;
        const reactions = [...m.reactions];
        const idx = reactions.findIndex((r) => r.emoji === emoji);
        if (idx >= 0) {
          const r = reactions[idx];
          if (r.mine) {
            if (r.count <= 1) reactions.splice(idx, 1);
            else reactions[idx] = { ...r, count: r.count - 1, mine: false };
          } else {
            reactions[idx] = { ...r, count: r.count + 1, mine: true };
          }
        } else {
          reactions.push({ emoji, count: 1, mine: true });
        }
        return { ...m, reactions };
      }),
    );
    void toggleReactionAction(mid, emoji);
  };
  const togglePin = (m: ChatMessage) => {
    setMessages((prev) => prev.map((x) => (x.id === m.id ? { ...x, pinned: !m.pinned } : x)));
    void pinMessageAction(m.id, !m.pinned);
  };
  const insertEmoji = (emoji: string) => {
    const el = taRef.current;
    const start = el?.selectionStart ?? text.length;
    const end = el?.selectionEnd ?? start;
    const next = text.slice(0, start) + emoji + text.slice(end);
    setText(next);
    requestAnimationFrame(() => {
      el?.focus();
      const pos = start + emoji.length;
      el?.setSelectionRange(pos, pos);
    });
  };

  const removeOwn = async (mid: string) => {
    if (
      !(await confirmDialog({
        title: "Nachricht löschen?",
        message: "Diese Nachricht wird für alle im Kanal entfernt.",
        confirmLabel: "Löschen",
        danger: true,
      }))
    )
      return;
    setMessages((prev) =>
      prev.map((m) =>
        m.id === mid ? { ...m, deleted: true, body: "", attachments: [], reactions: [] } : m,
      ),
    );
    const res = await deleteOwnMessageAction(mid);
    if (res?.error) setError(res.error);
  };
  const copyText = (m: ChatMessage) => {
    navigator.clipboard?.writeText(m.body).then(() => {
      setCopiedId(m.id);
      setTimeout(() => setCopiedId((c) => (c === m.id ? null : c)), 1200);
    });
  };
  const canDelete = (m: ChatMessage) =>
    m.authorId === currentUserId &&
    !m.deleted &&
    Date.now() - new Date(m.createdAt).getTime() < DELETE_WINDOW_MS;

  // ── Senden ──
  const send = async () => {
    const body = text.trim();
    if ((!body && pending.length === 0) || sending) return;
    setSending(true);
    setError(null);
    const replyId = replyTo?.id ?? null;

    const optimistic: ChatMessage = {
      id: `tmp-${Date.now()}`,
      body,
      authorId: currentUserId,
      authorName: "Du",
      authorAvatar: null,
      createdAt: new Date().toISOString(),
      editedAt: null,
      deleted: false,
      pinned: false,
      replyTo: replyTo ? { id: replyTo.id, author: replyTo.author, preview: replyTo.preview } : null,
      reactions: [],
      attachments: pending.map((p, i) => ({
        id: `tmp-att-${i}`,
        kind: p.kind,
        mime: p.mime,
        name: p.originalName,
        url: p.previewUrl ?? "#",
        width: p.width ?? null,
        height: p.height ?? null,
        durationMs: p.durationMs ?? null,
      })),
    };
    const toSend: UploadedAttachment[] = pending.map((p) => ({
      storedName: p.storedName,
      kind: p.kind,
      mime: p.mime,
      size: p.size,
      originalName: p.originalName,
      width: p.width ?? null,
      height: p.height ?? null,
      durationMs: p.durationMs ?? null,
    }));
    setMessages((prev) => [...prev, optimistic]);
    setText("");
    setPending([]);
    setReplyTo(null);
    if (taRef.current) taRef.current.style.height = "auto";

    const res = await sendMessageAction(channelId, body, toSend, replyId);
    setSending(false);
    if (res?.error) {
      setError(res.error);
      setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
      return;
    }
    try {
      const r = await fetch(`/api/chat/${channelId}/messages`, { cache: "no-store" });
      if (r.ok) {
        const data = await r.json();
        setMessages(data.messages as ChatMessage[]);
        if (data.receipts) setReceipts(data.receipts as ReadReceipt[]);
      }
    } catch {
      /* poll holt es nach */
    }
  };

  const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === "Escape") {
      setMentionQuery(null);
      setReplyTo(null);
    }
    if (e.key === "Enter" && !e.shiftKey && !mentionQuery) {
      e.preventDefault();
      void send();
    }
  };

  return (
    <>
      <div ref={listRef} className="msg-list">
        <div className="msg-inner">
          {messages.length === 0 ? (
            <div className="m-auto text-center">
              <p className="text-muted text-sm">Noch keine Nachrichten. Schreib die erste!</p>
            </div>
          ) : (
            messages.map((m, idx) => {
              const me = m.authorId === currentUserId;
              const prev = messages[idx - 1];
              const newDay = !prev || !sameDay(prev.createdAt, m.createdAt);
              const startGroup = newDay || !prev || prev.authorId !== m.authorId || Boolean(m.replyTo);
              const isTmp = m.id.startsWith("tmp-");
              return (
                <Fragment key={m.id}>
                  {newDay ? <div className="day-divider">{dayLabel(m.createdAt)}</div> : null}
                  <div id={`msg-${m.id}`} className={`msg-group ${me ? "is-me" : ""}`}>
                    {!me ? (
                      startGroup ? (
                        <Avatar name={m.authorName} src={m.authorAvatar} size={34} status={presence[m.authorId]?.status ?? null} />
                      ) : (
                        <div style={{ width: 34, flexShrink: 0 }} />
                      )
                    ) : null}

                    <div className="msg-stack">
                      {m.pinned ? (
                        <span className="pin-flag">
                          <Icon name="pin" size={11} style={{ display: "inline" }} /> fixiert
                        </span>
                      ) : null}
                      {!me && startGroup ? <span className="msg-author">{m.authorName}</span> : null}

                      {m.deleted ? (
                        <div className="bubble deleted-msg">Nachricht gelöscht</div>
                      ) : (
                        <>
                          {m.replyTo ? (
                            <div className="reply-quote" onClick={() => jumpToMessage(m.replyTo!.id)}>
                              <b>{m.replyTo.author}</b>: {m.replyTo.preview}
                            </div>
                          ) : null}
                          <div className="msg-content">
                          {!isTmp ? (
                            <div className="msg-actions">
                              {pickerFor === m.id ? (
                                REACTIONS.map((e) => (
                                  <button
                                    key={e}
                                    className="msg-action"
                                    onClick={() => toggleReaction(m.id, e)}
                                  >
                                    {e}
                                  </button>
                                ))
                              ) : (
                                <>
                                  <button
                                    className="msg-action"
                                    onClick={() => setPickerFor(m.id)}
                                    title="Reagieren"
                                  >
                                    <Icon name="smile" size={16} />
                                  </button>
                                  <button
                                    className="msg-action"
                                    onClick={() =>
                                      setReplyTo({
                                        id: m.id,
                                        author: m.authorName,
                                        preview: previewOf(m),
                                      })
                                    }
                                    title="Antworten"
                                  >
                                    <Icon name="reply" size={16} />
                                  </button>
                                  <button
                                    className="msg-action"
                                    onClick={() => copyText(m)}
                                    title="Kopieren"
                                  >
                                    <Icon name={copiedId === m.id ? "check" : "copy"} size={16} />
                                  </button>
                                  <button
                                    className="msg-action"
                                    onClick={() => togglePin(m)}
                                    title={m.pinned ? "Lösen" : "Fixieren"}
                                  >
                                    <Icon name="pin" size={16} />
                                  </button>
                                  {canDelete(m) ? (
                                    <button
                                      className="msg-action"
                                      onClick={() => removeOwn(m.id)}
                                      title="Löschen"
                                    >
                                      <Icon name="trash" size={16} />
                                    </button>
                                  ) : null}
                                </>
                              )}
                            </div>
                          ) : null}

                          {m.body.trim() ? (
                            <div className={`bubble ${me ? "is-me" : ""}`}>
                              <div style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
                                {renderBody(m.body)}
                              </div>
                            </div>
                          ) : null}
                          {m.attachments.map((att) =>
                            att.kind === "AUDIO" ? (
                              <VoicePlayer key={att.id} src={att.url} durationMs={att.durationMs} />
                            ) : (
                              <div key={att.id} className={`bubble ${me ? "is-me" : ""}`} style={{ padding: 6 }}>
                                <AttachmentView att={att} onZoom={setZoom} />
                              </div>
                            ),
                          )}

                          {m.reactions.length > 0 ? (
                            <div className="reaction-row">
                              {m.reactions.map((r) => (
                                <button
                                  key={r.emoji}
                                  className="reaction-chip"
                                  data-mine={r.mine}
                                  onClick={() => toggleReaction(m.id, r.emoji)}
                                >
                                  <span>{r.emoji}</span>
                                  <span>{r.count}</span>
                                </button>
                              ))}
                            </div>
                          ) : null}
                          </div>
                        </>
                      )}

                      <span className="bubble-time">
                        {formatTime(m.createdAt)}
                        {me && !m.deleted ? (
                          <DeliveryMark state={deliveryOf(m, receipts)} />
                        ) : null}
                      </span>
                    </div>
                  </div>
                </Fragment>
              );
            })
          )}
        </div>
      </div>

      <div className="composer">
        <div className="composer-inner">
          {error ? <p className="field-error mb-2 px-1">{error}</p> : null}

          {replyTo ? (
            <div className="reply-bar">
              <Icon name="reply" size={15} />
              <span className="min-w-0 flex-1 truncate">
                Antwort an <b>{replyTo.author}</b>: {replyTo.preview}
              </span>
              <button className="text-muted hover:text-[color:var(--pink)]" onClick={() => setReplyTo(null)}>
                <Icon name="close" size={15} />
              </button>
            </div>
          ) : null}

          {pending.length > 0 ? (
            <div className="mb-2 flex flex-wrap gap-2">
              {pending.map((p, i) => {
                const remove = () => setPending((arr) => arr.filter((_, j) => j !== i));
                if (p.kind === "AUDIO" && p.previewUrl) {
                  return (
                    <div key={i} className="voice-pending">
                      <VoicePlayer src={p.previewUrl} durationMs={p.durationMs} />
                      <button className="att-preview-x" onClick={remove} aria-label="Entfernen">
                        <Icon name="close" size={14} />
                      </button>
                    </div>
                  );
                }
                if (p.kind === "IMAGE" && p.previewUrl) {
                  return (
                    <div key={i} className="img-preview">
                      {/* eslint-disable-next-line @next/next/no-img-element */}
                      <img src={p.previewUrl} alt={p.originalName} />
                      <button className="att-preview-x" onClick={remove} aria-label="Entfernen">
                        <Icon name="close" size={13} />
                      </button>
                    </div>
                  );
                }
                return (
                  <div key={i} className="att-preview">
                    <Icon name={p.kind === "AUDIO" ? "mic" : "pin"} size={15} />
                    <span className="max-w-[140px] truncate">{p.originalName}</span>
                    <button className="att-preview-x" onClick={remove} aria-label="Entfernen">
                      <Icon name="close" size={14} />
                    </button>
                  </div>
                );
              })}
            </div>
          ) : null}

          {canSend ? (
            <div className="relative">
              {mentionQuery && mentionMatches.length > 0 ? (
                <div className="card-sm pop-solid mention-pop">
                  {mentionMatches.map((mem) => (
                    <button key={mem.id} className="channel-item" onClick={() => applyMention(mem)} type="button">
                      <Avatar name={mem.name} src={mem.avatarUrl} size={28} />
                      <span className="font-bold">{mem.name}</span>
                    </button>
                  ))}
                </div>
              ) : null}

              {emojiOpen ? (
                <>
                  <div className="emoji-backdrop" onClick={() => setEmojiOpen(false)} />
                  <div className="card-sm pop-solid emoji-pop">
                    {EMOJIS.map((e) => (
                      <button
                        key={e}
                        type="button"
                        className="emoji-cell"
                        onClick={() => insertEmoji(e)}
                      >
                        {e}
                      </button>
                    ))}
                  </div>
                </>
              ) : null}

              <div className="composer-box">
                <button
                  className="icon-btn"
                  onClick={() => fileRef.current?.click()}
                  disabled={busy}
                  aria-label="Datei anhängen"
                  title="Bild oder Datei anhängen"
                >
                  <Icon name="plus" size={20} />
                </button>
                <input
                  type="file"
                  hidden
                  multiple
                  ref={fileRef}
                  accept="image/*,audio/*,application/pdf,text/plain,application/zip"
                  onChange={onFiles}
                />

                <textarea
                  ref={taRef}
                  className="composer-input"
                  placeholder="Nachricht schreiben …  (@ für Erwähnung)"
                  value={text}
                  onChange={onTextChange}
                  onKeyDown={onKeyDown}
                  rows={1}
                />

                <button
                  className="icon-btn"
                  onClick={() => setEmojiOpen((v) => !v)}
                  data-active={emojiOpen}
                  aria-label="Emoji einfügen"
                  title="Emoji einfügen"
                >
                  <Icon name="smile" size={20} />
                </button>

                <button
                  className={`icon-btn ${recording ? "is-recording" : ""}`}
                  onClick={recording ? stopRecording : startRecording}
                  aria-label={recording ? "Aufnahme beenden" : "Sprachnachricht aufnehmen"}
                  title={recording ? "Aufnahme beenden" : "Sprachnachricht aufnehmen"}
                >
                  <Icon name={recording ? "stop" : "mic"} size={19} />
                </button>

                <button
                  className="send-fab"
                  onClick={() => void send()}
                  disabled={sending || (!text.trim() && pending.length === 0)}
                  aria-label="Senden"
                >
                  <Icon name="send" size={18} />
                </button>
              </div>
              {recording ? (
                <p className="mt-1 px-1 text-xs" style={{ color: "var(--pink)" }}>
                  ● Aufnahme läuft … Stopp-Symbol zum Beenden
                </p>
              ) : null}
            </div>
          ) : (
            <p className="text-muted px-1 text-sm">
              Du hast keine Berechtigung, in diesem Kanal zu schreiben.
            </p>
          )}
        </div>
      </div>

      {zoom ? (
        <div className="lightbox" onClick={() => setZoom(null)}>
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img src={zoom} alt="Vorschau" />
        </div>
      ) : null}
    </>
  );
}
