import { useState, useRef, useEffect, useCallback } from 'react';
import { usePage } from '@inertiajs/react';
import { getEcho } from '@/lib/echo';

/* ── Types ───────────────────────────────────────────────────────────── */

export type ChatAttachment = {
  id: number;
  name: string;
  mime: string | null;
  size: number | null;
  isImage: boolean;
  downloadUrl: string;
  previewUrl: string | null;
};

export type ChatMessage = {
  id: number;
  body: string;
  fromMe: boolean;
  read: boolean;
  createdAt: string;
  sortAt?: number;
  attachments: ChatAttachment[];
  // Client-only fields for optimistic UI:
  tempId?: string;
  status?: 'sending' | 'sent' | 'failed';
  localFiles?: { name: string; url: string | null; isImage: boolean }[];
};

export type Conversation = {
  id: number;
  subject: string;
  unread: number;
  lastBody: string;
  lastAt: string;
  lastIsOwn: boolean;
  sortAt?: number;
  messages: ChatMessage[];
};

type Student = { id: number; firstName: string; userId: number | null };

type Props = { student: Student; conversations: Conversation[] };

/* ── Helpers ─────────────────────────────────────────────────────────── */

function csrf(): string {
  return (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '';
}

function humanSize(bytes: number | null): string {
  if (bytes === null) return '';
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
  return `${(bytes / 1048576).toFixed(1)} MB`;
}

let tempSeq = 0;
function nextTempId(): string {
  tempSeq += 1;
  return `tmp-${Date.now()}-${tempSeq}`;
}

/* A pending message we still need to persist. Held per temp id so a failed
   send can be retried with the very same body + files. */
type PendingSend = {
  threadId: number | null; // null → new conversation
  subject: string;
  body: string;
  files: File[];
};

/* ── Component ───────────────────────────────────────────────────────── */

export function StudentChat({ student, conversations: initial }: Props) {
  const { props: pageProps } = usePage<{ auth?: { user?: { id: number } | null }; realtime?: { key?: string } }>();
  const myId = pageProps.auth?.user?.id ?? null;

  const [conversations, setConversations] = useState<Conversation[]>(initial);
  const [activeId, setActiveId] = useState<number | null>(initial[0]?.id ?? null);
  const [live, setLive] = useState(false);
  const [composing, setComposing] = useState(initial.length === 0);

  // composer state
  const [body, setBody] = useState('');
  const [files, setFiles] = useState<File[]>([]);
  const [subject, setSubject] = useState('');
  const [sending, setSending] = useState(false);

  const pendingRef = useRef<Map<string, PendingSend>>(new Map());
  const chatEndRef = useRef<HTMLDivElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const bodyRef = useRef<HTMLTextAreaElement>(null);

  const activeThread = conversations.find(c => c.id === activeId) ?? null;

  useEffect(() => {
    chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [activeId, activeThread?.messages.length]);

  // Props stay the source of truth on Inertia reloads.
  useEffect(() => {
    setConversations(initial);
    setActiveId(cur => (cur !== null && initial.some(c => c.id === cur) ? cur : initial[0]?.id ?? null));
  }, [initial]);

  /* Append/replace a message inside a thread, ignoring server duplicates. */
  const upsertMessage = useCallback((threadId: number, msg: ChatMessage, bumpUnread: boolean) => {
    setConversations(prev => prev.map(c => {
      if (c.id !== threadId) return c;
      if (msg.id && c.messages.some(m => m.id === msg.id && !m.tempId)) return c;
      return {
        ...c,
        messages: [...c.messages, msg],
        lastBody: msg.body || '📎 پیوست',
        lastAt: msg.createdAt,
        lastIsOwn: msg.fromMe,
        unread: bumpUnread ? c.unread + 1 : c.unread,
      };
    }));
  }, []);

  /* ── Live delivery over Reverb, polling fallback ── */
  useEffect(() => {
    if (!myId) return;
    const echo = getEcho(pageProps.realtime?.key);

    if (echo) {
      const channel = echo.private(`chat.${myId}`);
      channel.listen('.message.sent', (payload: { threadId: number; message: ChatMessage }) => {
        setLive(true);
        // Only messages that belong to a conversation we already track (this
        // student's threads). A new thread from the student is picked up by the
        // periodic reload below.
        setConversations(prev => {
          if (!prev.some(c => c.id === payload.threadId)) return prev;
          return prev;
        });
        setActiveId(cur => {
          upsertMessage(payload.threadId, { ...payload.message, status: 'sent' }, payload.threadId !== cur);
          return cur;
        });
      });

      const pusher = (echo.connector as unknown as { pusher?: { connection?: { bind: (e: string, cb: () => void) => void } } }).pusher;
      pusher?.connection?.bind('connected', () => setLive(true));
      pusher?.connection?.bind('unavailable', () => setLive(false));
      pusher?.connection?.bind('failed', () => setLive(false));

      return () => { echo.leave(`chat.${myId}`); };
    }

    // No socket → poll the chat endpoint.
    const timer = window.setInterval(() => { void refresh(); }, 5000);
    return () => window.clearInterval(timer);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [myId, upsertMessage, pageProps.realtime?.key]);

  /* Pull the full conversation list fresh (used by the polling fallback and
     after creating a new thread). Preserves any still-pending local bubbles. */
  const refresh = useCallback(async () => {
    try {
      const res = await fetch(`/teacher/students/${student.id}/chats`, {
        headers: { Accept: 'application/json' },
      });
      if (!res.ok) return;
      const data = await res.json() as { conversations: Conversation[] };
      setConversations(prev => {
        // keep pending/failed bubbles that the server doesn't know about yet
        return data.conversations.map(server => {
          const local = prev.find(c => c.id === server.id);
          const pendings = local?.messages.filter(m => m.status && m.status !== 'sent') ?? [];
          return pendings.length ? { ...server, messages: [...server.messages, ...pendings] } : server;
        });
      });
    } catch {
      /* ignore — next tick retries */
    }
  }, [student.id]);

  /* ── Sending ── */

  const doSend = useCallback(async (pending: PendingSend, tempId: string, targetThreadId: number | null) => {
    const fd = new FormData();
    if (pending.body) fd.append('body', pending.body);
    if (pending.subject) fd.append('subject', pending.subject);
    pending.files.forEach(f => fd.append('files[]', f));

    const url = targetThreadId
      ? `/teacher/students/${student.id}/chats/${targetThreadId}/messages`
      : `/teacher/students/${student.id}/chats`;

    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: { 'X-CSRF-TOKEN': csrf(), Accept: 'application/json' },
        body: fd,
      });
      if (!res.ok) throw new Error(String(res.status));
      const data = await res.json() as { threadId: number; message: ChatMessage; subject?: string };
      pendingRef.current.delete(tempId);

      if (targetThreadId) {
        // Swap the optimistic bubble for the server copy.
        setConversations(prev => prev.map(c => c.id === targetThreadId ? {
          ...c,
          messages: c.messages.map(m => m.tempId === tempId ? { ...data.message, status: 'sent' } : m),
        } : c));
      } else {
        // New conversation created: drop the placeholder thread, add the real one.
        setConversations(prev => {
          const withoutTemp = prev.filter(c => c.id !== -1);
          const newConv: Conversation = {
            id: data.threadId,
            subject: data.subject ?? '(بدون موضوع)',
            unread: 0,
            lastBody: data.message.body || '📎 پیوست',
            lastAt: data.message.createdAt,
            lastIsOwn: true,
            messages: [{ ...data.message, status: 'sent' }],
          };
          return [newConv, ...withoutTemp];
        });
        setActiveId(data.threadId);
      }
    } catch {
      // Mark the optimistic bubble failed so the user can resend.
      setConversations(prev => prev.map(c => ({
        ...c,
        messages: c.messages.map(m => m.tempId === tempId ? { ...m, status: 'failed' } : m),
      })));
    }
  }, [student.id]);

  /* Send from the composer of an existing conversation. */
  const send = useCallback(async () => {
    if (sending) return;
    if (!body.trim() && files.length === 0) return;
    const tempId = nextTempId();
    const pending: PendingSend = { threadId: activeId, subject: '', body: body.trim(), files: [...files] };
    pendingRef.current.set(tempId, pending);

    const optimistic: ChatMessage = {
      id: 0,
      body: pending.body,
      fromMe: true,
      read: false,
      createdAt: 'در حال ارسال…',
      attachments: [],
      localFiles: files.map(f => ({
        name: f.name,
        url: f.type.startsWith('image/') ? URL.createObjectURL(f) : null,
        isImage: f.type.startsWith('image/'),
      })),
      tempId,
      status: 'sending',
    };

    if (activeId) {
      upsertMessage(activeId, optimistic, false);
    }
    setBody('');
    setFiles([]);
    setSending(true);
    await doSend(pending, tempId, activeId);
    setSending(false);
    setTimeout(() => bodyRef.current?.focus(), 50);
  }, [sending, body, files, activeId, upsertMessage, doSend]);

  /* Start a brand-new conversation. */
  const startConversation = useCallback(async () => {
    if (sending) return;
    if (!body.trim() && files.length === 0) return;
    const tempId = nextTempId();
    const pending: PendingSend = { threadId: null, subject: subject.trim(), body: body.trim(), files: [...files] };
    pendingRef.current.set(tempId, pending);

    // Placeholder thread (id -1) so the user sees their message immediately.
    const optimistic: ChatMessage = {
      id: 0, body: pending.body, fromMe: true, read: false, createdAt: 'در حال ارسال…',
      attachments: [],
      localFiles: files.map(f => ({ name: f.name, url: f.type.startsWith('image/') ? URL.createObjectURL(f) : null, isImage: f.type.startsWith('image/') })),
      tempId, status: 'sending',
    };
    setConversations(prev => [{
      id: -1, subject: subject.trim() || '(بدون موضوع)', unread: 0,
      lastBody: pending.body || '📎 پیوست', lastAt: 'در حال ارسال…', lastIsOwn: true,
      messages: [optimistic],
    }, ...prev]);
    setActiveId(-1);
    setComposing(false);
    setBody(''); setFiles([]); setSubject('');
    setSending(true);
    await doSend(pending, tempId, null);
    setSending(false);
  }, [sending, body, files, subject, doSend]);

  /* Retry a failed bubble. */
  const resend = useCallback(async (msg: ChatMessage) => {
    if (!msg.tempId) return;
    const pending = pendingRef.current.get(msg.tempId);
    if (!pending) return;
    setConversations(prev => prev.map(c => ({
      ...c,
      messages: c.messages.map(m => m.tempId === msg.tempId ? { ...m, status: 'sending', createdAt: 'در حال ارسال…' } : m),
    })));
    await doSend(pending, msg.tempId, pending.threadId === -1 ? null : pending.threadId);
  }, [doSend]);

  /* Select a thread + mark read. */
  const selectThread = useCallback((c: Conversation) => {
    setActiveId(c.id);
    setComposing(false);
    if (c.unread > 0 && c.id > 0) {
      fetch(`/teacher/students/${student.id}/chats/${c.id}/read`, {
        method: 'PATCH', headers: { 'X-CSRF-TOKEN': csrf(), Accept: 'application/json' },
      });
      setConversations(prev => prev.map(x => x.id === c.id ? { ...x, unread: 0 } : x));
    }
    setTimeout(() => bodyRef.current?.focus(), 80);
  }, [student.id]);

  const deleteThread = useCallback(async (c: Conversation) => {
    if (c.id < 0) { // just a local placeholder
      setConversations(prev => prev.filter(x => x.id !== c.id));
      return;
    }
    if (!window.confirm(`حذف گفتگوی «${c.subject}»؟ همه پیام‌ها و پیوست‌های آن پاک می‌شود.`)) return;
    setConversations(prev => prev.filter(x => x.id !== c.id));
    if (activeId === c.id) setActiveId(null);
    try {
      await fetch(`/teacher/students/${student.id}/chats/${c.id}`, {
        method: 'DELETE', headers: { 'X-CSRF-TOKEN': csrf(), Accept: 'application/json' },
      });
    } catch {
      void refresh();
    }
  }, [activeId, student.id, refresh]);

  const onPickFiles = (e: React.ChangeEvent<HTMLInputElement>) => {
    const picked = Array.from(e.target.files ?? []);
    setFiles(prev => [...prev, ...picked].slice(0, 5));
    e.target.value = '';
  };

  const handleKey = (e: React.KeyboardEvent, fn: () => void) => {
    if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); fn(); }
  };

  if (!student.userId) {
    return (
      <div className="panel-card" style={{ textAlign: 'center', padding: '2rem', color: 'var(--color-text-muted)' }}>
        <i className="bi bi-chat-dots" style={{ fontSize: '2rem', display: 'block', marginBottom: '0.5rem' }} />
        این دانش‌آموز حساب کاربری ندارد؛ امکان گفتگو نیست.
      </div>
    );
  }

  /* ── Render ── */

  return (
    <div style={{ display: 'flex', height: 'calc(100vh - 260px)', minHeight: 460, borderRadius: 'var(--radius-lg)', overflow: 'hidden', border: '1px solid var(--color-border)', background: 'var(--color-surface)' }}>

      {/* ── Right: conversation cards ── */}
      <div style={{ width: 280, minWidth: 240, borderLeft: '1px solid var(--color-border)', display: 'flex', flexDirection: 'column', background: 'var(--color-bg-soft)' }}>
        <div style={{ padding: '0.75rem 0.9rem', borderBottom: '1px solid var(--color-border)', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
          <span style={{ fontWeight: 700, fontSize: '0.92rem', flex: 1 }}>
            <i className="bi bi-chat-dots-fill" style={{ color: 'var(--color-primary)', marginLeft: '0.4rem' }} />
            گفتگوها
          </span>
          <button className="btn btn-primary btn-sm" title="گفتگوی جدید" onClick={() => { setComposing(true); setActiveId(null); }}>
            <i className="bi bi-pencil-square" />
          </button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto' }}>
          {conversations.length === 0 ? (
            <div style={{ padding: '2rem 1rem', textAlign: 'center', color: 'var(--color-text-muted)', fontSize: '0.85rem' }}>
              <i className="bi bi-inbox" style={{ fontSize: '2rem', display: 'block', marginBottom: '0.5rem', opacity: 0.4 }} />
              هنوز گفتگویی نیست
            </div>
          ) : conversations.map(c => (
            <div
              key={c.id}
              onClick={() => selectThread(c)}
              style={{
                position: 'relative', width: '100%', textAlign: 'right', padding: '0.7rem 0.9rem',
                background: activeId === c.id ? 'var(--color-primary-soft)' : 'transparent',
                cursor: 'pointer', borderBottom: '1px solid var(--color-border)',
                borderRight: activeId === c.id ? '3px solid var(--color-primary)' : '3px solid transparent',
              }}
            >
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '0.3rem' }}>
                <span style={{ fontWeight: c.unread > 0 ? 700 : 500, fontSize: '0.85rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1, color: 'var(--color-text)' }}>
                  {c.subject}
                </span>
                <span style={{ fontSize: '0.66rem', color: 'var(--color-text-muted)', flexShrink: 0, marginTop: '0.1rem' }}>{c.lastAt}</span>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: '0.3rem', marginTop: '0.18rem' }}>
                {c.lastIsOwn && <i className="bi bi-arrow-return-left" style={{ fontSize: '0.68rem', color: 'var(--color-text-muted)', flexShrink: 0 }} />}
                <span style={{ fontSize: '0.76rem', color: 'var(--color-text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
                  {c.lastBody}
                </span>
                {c.unread > 0 && (
                  <span style={{ background: 'var(--color-primary)', color: '#fff', borderRadius: 999, fontSize: '0.62rem', padding: '0.05rem 0.36rem', flexShrink: 0 }}>{c.unread}</span>
                )}
                <button
                  onClick={(e) => { e.stopPropagation(); deleteThread(c); }}
                  title="حذف گفتگو"
                  style={{ background: 'none', border: 'none', color: 'var(--color-text-muted)', cursor: 'pointer', padding: '0 0.1rem', flexShrink: 0 }}
                >
                  <i className="bi bi-trash" style={{ fontSize: '0.75rem' }} />
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* ── Left: chat view / composer ── */}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        {composing ? (
          <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
            <div style={{ padding: '0.8rem 1.1rem', borderBottom: '1px solid var(--color-border)', display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
              <button className="btn btn-ghost btn-sm" onClick={() => setComposing(false)}><i className="bi bi-arrow-right" /></button>
              <strong style={{ fontSize: '0.95rem' }}>گفتگوی جدید با {student.firstName}</strong>
            </div>
            <div style={{ flex: 1, padding: '1.1rem', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '0.9rem' }}>
              <div>
                <label style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--color-text-muted)', display: 'block', marginBottom: '0.3rem' }}>موضوع (اختیاری)</label>
                <input className="form-input" value={subject} onChange={e => setSubject(e.target.value)} placeholder="موضوع گفتگو…" />
              </div>
              <div>
                <label style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--color-text-muted)', display: 'block', marginBottom: '0.3rem' }}>پیام</label>
                <textarea
                  className="form-input" rows={4} value={body}
                  onChange={e => setBody(e.target.value)}
                  onKeyDown={e => handleKey(e, startConversation)}
                  placeholder="متن پیام… (Ctrl+Enter برای ارسال)"
                />
              </div>
              <AttachmentTray files={files} onRemove={i => setFiles(prev => prev.filter((_, idx) => idx !== i))} />
            </div>
            <div style={{ padding: '0.8rem 1.1rem', borderTop: '1px solid var(--color-border)', display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
              <input ref={fileInputRef} type="file" multiple hidden onChange={onPickFiles} />
              <button className="btn btn-ghost" type="button" onClick={() => fileInputRef.current?.click()} title="پیوست فایل">
                <i className="bi bi-paperclip" />
              </button>
              <button className="btn btn-primary" onClick={startConversation} disabled={sending || (!body.trim() && files.length === 0)}>
                {sending ? <i className="bi bi-hourglass-split" /> : <i className="bi bi-send-fill" />} ارسال
              </button>
            </div>
          </div>
        ) : activeThread ? (
          <>
            <div style={{ padding: '0.8rem 1.1rem', borderBottom: '1px solid var(--color-border)', display: 'flex', alignItems: 'center', gap: '0.7rem' }}>
              <i className="bi bi-chat-text" style={{ color: 'var(--color-primary)', fontSize: '1.05rem' }} />
              <div style={{ flex: 1 }}>
                <div style={{ fontWeight: 700, fontSize: '0.94rem' }}>{activeThread.subject}</div>
                <div style={{ fontSize: '0.73rem', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                  <span>{activeThread.messages.length} پیام</span>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}>
                    <span style={{ width: 6, height: 6, borderRadius: '50%', background: live ? 'var(--color-success)' : 'var(--color-text-soft)' }} />
                    {live ? 'آنلاین' : 'بررسی دوره‌ای'}
                  </span>
                </div>
              </div>
              <button className="btn btn-ghost btn-sm" title="حذف گفتگو" onClick={() => deleteThread(activeThread)}>
                <i className="bi bi-trash" />
              </button>
            </div>

            <div style={{ flex: 1, overflowY: 'auto', padding: '1rem 1.1rem', display: 'flex', flexDirection: 'column', gap: '0.55rem' }}>
              {activeThread.messages.map((m, idx) => (
                <MessageBubble key={m.tempId ?? m.id ?? idx} msg={m} onResend={() => resend(m)} />
              ))}
              <div ref={chatEndRef} />
            </div>

            <div style={{ padding: '0.7rem 1.1rem', borderTop: '1px solid var(--color-border)', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
              {files.length > 0 && <AttachmentTray files={files} onRemove={i => setFiles(prev => prev.filter((_, idx) => idx !== i))} />}
              <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'flex-end' }}>
                <input ref={fileInputRef} type="file" multiple hidden onChange={onPickFiles} />
                <button className="btn btn-ghost" type="button" onClick={() => fileInputRef.current?.click()} title="پیوست فایل" style={{ height: 44 }}>
                  <i className="bi bi-paperclip" />
                </button>
                <textarea
                  ref={bodyRef}
                  value={body}
                  onChange={e => setBody(e.target.value)}
                  onKeyDown={e => handleKey(e, send)}
                  placeholder="پیام بنویسید… (Ctrl+Enter برای ارسال)"
                  rows={1}
                  style={{
                    flex: 1, resize: 'none', border: '1px solid var(--color-border)', borderRadius: 12,
                    padding: '0.55rem 0.8rem', fontFamily: 'inherit', fontSize: '0.875rem',
                    background: 'var(--color-bg-soft)', color: 'var(--color-text)', outline: 'none', lineHeight: 1.55, minHeight: 44,
                  }}
                />
                <button className="btn btn-primary" onClick={send} disabled={sending || (!body.trim() && files.length === 0)} style={{ borderRadius: 10, height: 44, minWidth: 44, padding: '0 0.85rem' }} title="ارسال (Ctrl+Enter)">
                  {sending ? <i className="bi bi-hourglass-split" /> : <i className="bi bi-send-fill" />}
                </button>
              </div>
            </div>
          </>
        ) : (
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: 'var(--color-text-muted)', gap: '0.75rem' }}>
            <i className="bi bi-chat-dots" style={{ fontSize: '3rem', opacity: 0.25 }} />
            <span style={{ fontSize: '0.9rem' }}>یک گفتگو انتخاب کنید یا گفتگوی جدید شروع کنید</span>
            <button className="btn btn-primary btn-sm" onClick={() => setComposing(true)}>
              <i className="bi bi-pencil-square" /> گفتگوی جدید
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

/* ── Sub-components ──────────────────────────────────────────────────── */

function AttachmentTray({ files, onRemove }: { files: File[]; onRemove: (i: number) => void }) {
  if (files.length === 0) return null;
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }}>
      {files.map((f, i) => (
        <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem', background: 'var(--color-bg-soft)', border: '1px solid var(--color-border)', borderRadius: 8, padding: '0.25rem 0.5rem', fontSize: '0.76rem' }}>
          <i className={`bi ${f.type.startsWith('image/') ? 'bi-image' : 'bi-file-earmark'}`} />
          <span style={{ maxWidth: 140, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
          <button onClick={() => onRemove(i)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-danger)', padding: 0 }}>
            <i className="bi bi-x-lg" style={{ fontSize: '0.7rem' }} />
          </button>
        </span>
      ))}
    </div>
  );
}

function MessageBubble({ msg, onResend }: { msg: ChatMessage; onResend: () => void }) {
  const own = msg.fromMe;
  return (
    <div style={{ display: 'flex', justifyContent: own ? 'flex-end' : 'flex-start' }}>
      <div style={{ maxWidth: '72%', display: 'flex', flexDirection: 'column', gap: '0.25rem', alignItems: own ? 'flex-end' : 'flex-start' }}>
        <div style={{
          background: own ? 'var(--color-primary)' : 'var(--color-surface)',
          color: own ? '#fff' : 'var(--color-text)',
          border: own ? 'none' : '1px solid var(--color-border)',
          borderRadius: own ? '18px 18px 4px 18px' : '18px 18px 18px 4px',
          padding: '0.55rem 0.85rem', fontSize: '0.875rem', lineHeight: 1.6,
          whiteSpace: 'pre-wrap', wordBreak: 'break-word', boxShadow: '0 1px 3px rgba(0,0,0,0.07)',
          opacity: msg.status === 'sending' ? 0.7 : 1,
        }}>
          {msg.body}

          {/* server attachments */}
          {msg.attachments?.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem', marginTop: msg.body ? '0.5rem' : 0 }}>
              {msg.attachments.map(a => a.isImage && a.previewUrl ? (
                <a key={a.id} href={a.downloadUrl} target="_blank" rel="noreferrer">
                  <img src={a.previewUrl} alt={a.name} style={{ maxWidth: 220, maxHeight: 220, borderRadius: 10, display: 'block' }} />
                </a>
              ) : (
                <a key={a.id} href={a.downloadUrl} target="_blank" rel="noreferrer"
                   style={{ display: 'flex', alignItems: 'center', gap: '0.45rem', background: own ? 'rgba(255,255,255,0.15)' : 'var(--color-bg-soft)', borderRadius: 8, padding: '0.4rem 0.6rem', color: 'inherit', textDecoration: 'none' }}>
                  <i className="bi bi-file-earmark-arrow-down" style={{ fontSize: '1.1rem' }} />
                  <span style={{ display: 'flex', flexDirection: 'column' }}>
                    <span style={{ fontSize: '0.78rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 160 }}>{a.name}</span>
                    {a.size !== null && <span style={{ fontSize: '0.68rem', opacity: 0.75 }}>{humanSize(a.size)}</span>}
                  </span>
                </a>
              ))}
            </div>
          )}

          {/* optimistic local previews (before server confirms) */}
          {msg.localFiles && msg.localFiles.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem', marginTop: msg.body ? '0.5rem' : 0 }}>
              {msg.localFiles.map((f, i) => f.isImage && f.url ? (
                <img key={i} src={f.url} alt={f.name} style={{ maxWidth: 200, maxHeight: 200, borderRadius: 10, display: 'block', opacity: 0.85 }} />
              ) : (
                <span key={i} style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.78rem', opacity: 0.85 }}>
                  <i className="bi bi-file-earmark" /> {f.name}
                </span>
              ))}
            </div>
          )}
        </div>

        <span style={{ fontSize: '0.66rem', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center', gap: '0.3rem' }}>
          {msg.createdAt}
          {own && msg.status === 'sending' && <i className="bi bi-clock" title="در حال ارسال" />}
          {own && msg.status === 'sent' && <i className={`bi ${msg.read ? 'bi-check2-all' : 'bi-check2'}`} title={msg.read ? 'خوانده شد' : 'ارسال شد'} />}
          {own && msg.status === 'failed' && (
            <button onClick={onResend} style={{ background: 'none', border: 'none', color: 'var(--color-danger)', cursor: 'pointer', padding: 0, fontSize: '0.66rem', display: 'inline-flex', alignItems: 'center', gap: '0.2rem' }}>
              <i className="bi bi-exclamation-circle" /> ارسال نشد — تلاش دوباره
            </button>
          )}
        </span>
      </div>
    </div>
  );
}
