import { extractErrorCode } from '@chat-app/shared/i18n';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';

import { ConversationHeader } from '../components/ConversationHeader';
import { GroupInfoPanel } from '../components/GroupInfoPanel';
import { AlertIcon, ArrowRightIcon, PlusIcon, SpinnerIcon, XIcon } from '../components/icons';
import { InCallPanel } from '../components/InCallPanel';
import { MessageBubble } from '../components/MessageBubble';
import { TypingIndicator } from '../components/TypingIndicator';
import { useAuth } from '../context/AuthContext';
import { useConversationsContext } from '../context/ConversationsContext';
import { useConversationMessages } from '../lib/useConversationMessages';
import { useMessageReactions } from '../lib/useMessageReactions';
import { markRead as markMessagesReadRemote, useMessageReads } from '../lib/useMessageReads';
import { usePeerPresence } from '../lib/usePeerPresence';
import { useTypingChannel } from '../lib/useTypingChannel';

const STICK_THRESHOLD = 80;

export function ConversationPage() {
  const { t } = useTranslation(['app', 'errors']);
  const { id } = useParams<{ id: string }>();
  const { session, device } = useAuth();
  const { conversations, setActiveConversation, markRead } = useConversationsContext();

  const conversation = useMemo(
    () => conversations.find((c) => c.id === id) ?? null,
    [conversations, id],
  );
  const peerId = conversation?.peer?.userId;
  const peerPresence = usePeerPresence(peerId);

  const { messages, loading, error, send } = useConversationMessages({
    conversationId: id,
    userId: session?.user.id,
    deviceId: device?.id,
  });
  const messageIds = useMemo(() => messages.map((m) => m.id), [messages]);
  const { byMessage: reactionsByMessage, toggle: toggleReaction } = useMessageReactions(
    messageIds,
    session?.user.id,
  );

  const myId = session?.user.id;

  // Peer read tracking — only for 1:1 DMs.
  const ownMessageIds = useMemo(
    () => messages.filter((m) => m.senderId === myId).map((m) => m.id),
    [messages, myId],
  );
  const { peerReadSet } = useMessageReads(ownMessageIds, peerId);

  const lastSeenMessageId = useMemo(() => {
    for (let i = messages.length - 1; i >= 0; i--) {
      const m = messages[i];
      if (m && m.senderId === myId && peerReadSet.has(m.id)) return m.id;
    }
    return null;
  }, [messages, peerReadSet, myId]);

  // Typing channel.
  const { typingUserIds, notifyTyping, notifyStopTyping } = useTypingChannel(id, myId);

  // Mark incoming messages as read (server-side, visible to peer if both sides
  // have receipts on). Runs whenever new messages arrive or id changes.
  useEffect(() => {
    if (!id || messages.length === 0 || !myId) return;
    const incoming = messages.filter((m) => m.senderId !== myId).map((m) => m.id);
    if (incoming.length === 0) return;
    void markMessagesReadRemote(incoming).catch((err: unknown) => {
      console.error('markMessagesRead failed', err);
    });
  }, [id, messages, myId]);

  const [text, setText] = useState('');
  const [sending, setSending] = useState(false);
  const [sendError, setSendError] = useState<string | null>(null);
  const [stickToBottom, setStickToBottom] = useState(true);
  const [attachments, setAttachments] = useState<File[]>([]);
  const [infoPanelOpen, setInfoPanelOpen] = useState(false);
  const scrollRef = useRef<HTMLDivElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (!id) return;
    setActiveConversation(id);
    return () => {
      setActiveConversation(null);
    };
  }, [id, setActiveConversation]);

  useEffect(() => {
    if (id && messages.length > 0) markRead(id);
  }, [id, messages.length, markRead]);

  useEffect(() => {
    const el = scrollRef.current;
    if (!el || !stickToBottom) return;
    el.scrollTop = el.scrollHeight;
  }, [messages.length, stickToBottom]);

  useEffect(() => {
    setStickToBottom(true);
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [id]);

  const handleScroll = useCallback(() => {
    const el = scrollRef.current;
    if (!el) return;
    const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
    setStickToBottom(distanceFromBottom < STICK_THRESHOLD);
  }, []);

  async function handleSend(e?: React.FormEvent) {
    e?.preventDefault();
    if ((!text.trim() && attachments.length === 0) || sending) return;
    setSending(true);
    setSendError(null);
    try {
      await send(text, attachments);
      setText('');
      setAttachments([]);
      if (fileInputRef.current) fileInputRef.current.value = '';
      setStickToBottom(true);
      notifyStopTyping();
    } catch (err: unknown) {
      const code = extractErrorCode(err);
      setSendError(
        code
          ? t('errors:' + code, { defaultValue: t('errors:generic') })
          : err instanceof Error
            ? err.message
            : t('errors:generic'),
      );
    } finally {
      setSending(false);
    }
  }

  function handleFilesChosen(list: FileList | null) {
    if (!list) return;
    const next: File[] = [];
    for (let i = 0; i < list.length; i++) {
      const f = list[i];
      if (!f) continue;
      if (!f.type.startsWith('image/')) continue;
      if (f.size > 10 * 1024 * 1024) {
        setSendError('Datei zu groß (max 10 MB)');
        continue;
      }
      next.push(f);
    }
    setAttachments((prev) => [...prev, ...next].slice(0, 4));
  }

  const isGroup = conversation?.type === 'group';

  return (
    <div className="relative flex h-full flex-col">
      <ConversationHeader
        conversation={conversation}
        peerPresence={peerPresence}
        {...(isGroup ? { onInfoClick: () => setInfoPanelOpen(true) } : {})}
      />

      {isGroup && conversation && (
        <GroupInfoPanel
          open={infoPanelOpen}
          onClose={() => setInfoPanelOpen(false)}
          conversation={conversation}
        />
      )}

      {conversation && <InCallPanel conversation={conversation} />}

      <div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto px-6 py-4">
        {loading ? (
          <div className="flex items-center gap-2 text-xs text-neutral-500">
            <SpinnerIcon className="h-3.5 w-3.5 text-brand-400" />
          </div>
        ) : error ? (
          <Banner>{error}</Banner>
        ) : messages.length === 0 ? (
          <p className="text-center text-sm text-neutral-500">…</p>
        ) : (
          <ul className="space-y-0.5">
            {messages.map((m, idx) => {
              const prev = messages[idx - 1];
              const grouped = idx > 0 && prev?.senderId === m.senderId;
              return (
                <li key={m.id}>
                  <MessageBubble
                    message={m}
                    mine={m.senderId === myId}
                    groupedWithPrev={grouped}
                    conversationId={id ?? ''}
                    reactions={reactionsByMessage.get(m.id) ?? []}
                    onToggleReaction={(emoji) => toggleReaction(m.id, emoji)}
                    showSeen={m.id === lastSeenMessageId}
                  />
                </li>
              );
            })}
          </ul>
        )}
      </div>

      <TypingIndicator
        typingUserIds={typingUserIds}
        members={conversation?.members ?? []}
      />

      <form onSubmit={handleSend} className="border-t border-white/5 p-4">
        {sendError && (
          <div className="mb-2">
            <Banner>{sendError}</Banner>
          </div>
        )}

        {attachments.length > 0 && (
          <div className="mb-2 flex flex-wrap gap-2">
            {attachments.map((file, idx) => (
              <AttachmentPreview
                key={idx}
                file={file}
                onRemove={() =>
                  setAttachments((prev) => prev.filter((_, i) => i !== idx))
                }
              />
            ))}
          </div>
        )}

        <div className="flex items-end gap-2">
          <input
            ref={fileInputRef}
            type="file"
            accept="image/*"
            multiple
            className="hidden"
            onChange={(e) => handleFilesChosen(e.target.files)}
          />
          <button
            type="button"
            onClick={() => fileInputRef.current?.click()}
            aria-label="Bild anhängen"
            title="Bild anhängen"
            className="inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-lg border border-white/10 bg-white/5 text-neutral-300 transition hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40"
          >
            <PlusIcon className="h-4 w-4" />
          </button>
          <textarea
            value={text}
            onChange={(e) => {
              setText(e.target.value);
              if (e.target.value.length > 0) notifyTyping();
            }}
            onKeyDown={(e) => {
              if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                void handleSend();
              }
            }}
            rows={1}
            placeholder="…"
            className="max-h-40 min-h-[44px] flex-1 resize-none rounded-lg border border-white/10 bg-ink-900/60 px-3 py-2.5 text-sm text-white placeholder-neutral-500 transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-500/40"
          />
          <button
            type="submit"
            disabled={sending || (text.trim().length === 0 && attachments.length === 0)}
            aria-busy={sending}
            className="inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-lg bg-gradient-to-br from-brand-400 to-brand-600 text-white transition hover:from-brand-300 hover:to-brand-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/60 disabled:cursor-not-allowed disabled:opacity-60"
          >
            {sending ? <SpinnerIcon className="h-4 w-4" /> : <ArrowRightIcon className="h-4 w-4" />}
          </button>
        </div>
      </form>
    </div>
  );
}

function Banner({ children }: { children: React.ReactNode }) {
  return (
    <div
      role="alert"
      className="flex items-start gap-3 rounded-lg border border-rose-500/20 bg-rose-500/10 p-3 text-sm text-rose-100"
    >
      <AlertIcon className="mt-0.5 h-5 w-5 shrink-0 text-rose-400" />
      <p className="min-w-0 flex-1 break-words">{children}</p>
    </div>
  );
}

function AttachmentPreview({ file, onRemove }: { file: File; onRemove: () => void }) {
  const [url, setUrl] = useState<string | null>(null);
  useEffect(() => {
    const u = URL.createObjectURL(file);
    setUrl(u);
    return () => URL.revokeObjectURL(u);
  }, [file]);
  return (
    <div className="relative overflow-hidden rounded-lg border border-white/10 bg-ink-900/60">
      {url ? (
        <img src={url} alt={file.name} className="block h-20 w-20 object-cover" />
      ) : (
        <div className="h-20 w-20" />
      )}
      <button
        type="button"
        onClick={onRemove}
        aria-label="Entfernen"
        className="absolute right-1 top-1 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-ink-950/80 text-neutral-200 transition hover:bg-rose-500/70 hover:text-white"
      >
        <XIcon className="h-3 w-3" />
      </button>
    </div>
  );
}
