"use client";

// ============================================================
// Messenger — internal team + client chat, powered by IIOS via
// the be-crm data door (crm.messenger.*). Conversation list ⇄
// thread view + composer, with a "new chat" people picker that
// creates a DM (1 person) or group (2+). DM-vs-group and who-can-
// chat are enforced server-side by IIOS/OPA; this is just UI.
// Live messages, typing, read receipts and reactions come over the
// IIOS socket (Shell mode); mock keeps the demo working offline.
// ============================================================

import { type CSSProperties, useEffect, useMemo, useRef, useState } from "react";
import { Avatar, Btn, Field, Icon, Modal, PageHead, Pill, useToast } from "./ui";
import { useMessengerData, useThread, type Membership, type UiAttachment, type UiConversation, type UiMessage, type UiPerson } from "@/lib/messenger-api";
import { MessengerSocketProvider, useMessengerSocket } from "@/lib/messenger-socket";
import { useUploadAttachment, useDownloadUrl, isImage, type UploadedAttachment } from "@/lib/media-api";

const fmtBytes = (n: number) => (n < 1024 ? `${n} B` : n < 1048576 ? `${(n / 1024).toFixed(0)} KB` : `${(n / 1048576).toFixed(1)} MB`);

/** Renders a message attachment — an inline image thumbnail, or a downloadable file chip. */
function AttachmentView({ att }: { att: UiAttachment }) {
  const getUrl = useDownloadUrl();
  const [url, setUrl] = useState<string | null>(null);
  useEffect(() => {
    let alive = true;
    getUrl(att.contentRef, att.mimeType).then((u) => { if (alive) setUrl(u); }).catch(() => {});
    return () => { alive = false; };
  }, [att.contentRef, att.mimeType, getUrl]);

  if (isImage(att.mimeType)) {
    return url ? (
      // eslint-disable-next-line @next/next/no-img-element
      <a href={url} target="_blank" rel="noreferrer"><img src={url} alt="attachment" style={{ maxWidth: 240, maxHeight: 240, borderRadius: 10, display: "block", marginTop: 6, border: "1px solid var(--border)" }} /></a>
    ) : <div style={{ marginTop: 6, color: "var(--muted)", fontSize: 12 }}>Loading image…</div>;
  }
  return (
    <a href={url ?? "#"} target={url ? "_blank" : undefined} rel="noreferrer"
       style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6, padding: "8px 12px", borderRadius: 10, background: "var(--panel-2)", border: "1px solid var(--border)", textDecoration: "none", color: "var(--text)", maxWidth: 240 }}>
      <Icon name="file" size={18} />
      <span style={{ flex: 1, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", fontSize: 13 }}>Attachment</span>
      <span style={{ color: "var(--muted)", fontSize: 12, flexShrink: 0 }}>{fmtBytes(att.sizeBytes)}</span>
    </a>
  );
}

const initialsOf = (name: string) =>
  name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]).join("").toUpperCase() || "?";
const timeOf = (iso?: string) => {
  if (!iso) return "";
  const d = new Date(iso);
  return Number.isNaN(+d) ? "" : d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
};
const GROUP_GRAD = "linear-gradient(135deg,#6366f1,#8b5cf6)";
const CUSTOMER_GRAD = "linear-gradient(135deg,#10b981,#059669)";
const REACTION_EMOJIS = ["👍", "❤️", "😂", "😮", "😢", "🎉"];
const inputStyle: CSSProperties = {
  width: "100%", padding: "9px 12px", borderRadius: 10, border: "1px solid var(--border)",
  background: "var(--panel)", color: "var(--text)", fontSize: 14, outline: "none",
};

export function Messenger() {
  // One shared IIOS socket for the whole panel (live in Shell mode; no-op in mock).
  return (
    <MessengerSocketProvider>
      <MessengerPanel />
    </MessengerSocketProvider>
  );
}

function MessengerPanel() {
  const m = useMessengerData();
  const toast = useToast();
  const [selected, setSelected] = useState<string | null>(null);
  const [newOpen, setNewOpen] = useState(false);

  useEffect(() => {
    if ((!selected || !m.conversations.some((c) => c.threadId === selected)) && m.conversations[0]) {
      setSelected(m.conversations[0].threadId);
    }
  }, [m.conversations, selected]);

  const current = m.conversations.find((c) => c.threadId === selected) ?? null;

  return (
    <div className="view">
      <PageHead
        eyebrow="Communication" title="Messenger" subtitle="Chat with your team and clients — direct or in groups" icon="send"
        actions={<Btn icon="plus" onClick={() => setNewOpen(true)}>New chat</Btn>}
      />
      {!m.live && (
        <div style={{ margin: "0 0 14px", padding: "8px 14px", borderRadius: 10, background: "var(--panel-2)", color: "var(--muted)", fontSize: 13, border: "1px solid var(--border)" }}>
          Demo mode — running on mock data. It goes live once the Shell + be-crm are connected.
        </div>
      )}

      <div className="card" style={{ display: "flex", height: 620, padding: 0, overflow: "hidden" }}>
        <aside style={{ width: 296, borderRight: "1px solid var(--border)", overflowY: "auto", background: "var(--panel)" }}>
          {m.loading && <div style={{ padding: 16, color: "var(--muted)" }}>Loading…</div>}
          {!m.loading && m.conversations.length === 0 && (
            <div style={{ padding: 16, color: "var(--muted)" }}>No conversations yet. Start a new chat.</div>
          )}
          {m.conversations.map((c) => (
            <ConversationRow key={c.threadId} c={c} active={c.threadId === selected} onClick={() => setSelected(c.threadId)} />
          ))}
        </aside>

        <section style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
          {current ? (
            <ThreadView key={current.threadId} conv={current} nameOf={m.nameOf} onError={(msg) => toast.push({ tone: "error", title: "Message failed", desc: msg })} />
          ) : (
            <div style={{ margin: "auto", color: "var(--muted)", textAlign: "center" }}>
              <Icon name="send" size={38} />
              <p>Select or start a conversation</p>
            </div>
          )}
        </section>
      </div>

      <NewChatModal
        open={newOpen} onClose={() => setNewOpen(false)} directory={m.directory}
        onCreate={async (ids, opts) => {
          try {
            const id = await m.openConversation(ids, opts);
            setSelected(id);
            setNewOpen(false);
          } catch (e) {
            toast.push({ tone: "error", title: "Couldn't start chat", desc: (e as Error).message });
          }
        }}
      />
    </div>
  );
}

function ConversationRow({ c, active, onClick }: { c: UiConversation; active: boolean; onClick: () => void }) {
  return (
    <button
      onClick={onClick}
      style={{
        display: "flex", gap: 10, alignItems: "center", width: "100%", textAlign: "left",
        padding: "10px 14px", border: "none", borderBottom: "1px solid var(--border)", cursor: "pointer",
        background: active ? "var(--panel-2)" : "transparent", color: "var(--text)",
      }}
    >
      <Avatar initials={initialsOf(c.title)} size={38} gradient={c.membership === "group" ? GROUP_GRAD : undefined} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "center" }}>
          <span style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.title}</span>
          <span style={{ color: "var(--muted)", fontSize: 11, flexShrink: 0 }}>{timeOf(c.lastAt)}</span>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "center" }}>
          <span style={{ color: "var(--muted)", fontSize: 12.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
            {c.lastMessage ?? "No messages yet"}
          </span>
          {c.unread > 0 && (
            <span style={{ background: "var(--orange)", color: "#fff", borderRadius: 999, fontSize: 11, padding: "1px 7px", flexShrink: 0 }}>{c.unread}</span>
          )}
        </div>
      </div>
    </button>
  );
}

function ThreadView({ conv, nameOf, onError }: { conv: UiConversation; nameOf: (id: string) => string; onError: (m: string) => void }) {
  const t = useThread(conv.threadId);
  const socket = useMessengerSocket();
  const [draft, setDraft] = useState("");
  const [sending, setSending] = useState(false);
  const [replyTo, setReplyTo] = useState<UiMessage | null>(null);
  const [flashId, setFlashId] = useState<string | null>(null);
  const endRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const msgRefs = useRef<Map<string, HTMLElement>>(new Map());
  const typingSentAt = useRef(0);

  useEffect(() => { endRef.current?.scrollIntoView({ behavior: "smooth" }); }, [t.messages.length]);

  const byId = useMemo(() => Object.fromEntries(t.messages.map((m) => [m.id, m])), [t.messages]);
  const lastMineId = useMemo(() => [...t.messages].reverse().find((m) => m.mine)?.id ?? null, [t.messages]);

  // Reply → focus the composer (bug: it didn't focus, forcing a manual click).
  function startReply(msg: UiMessage) {
    setReplyTo(msg);
    requestAnimationFrame(() => inputRef.current?.focus());
  }
  // Click a quoted message → scroll to the original and flash it.
  function jumpTo(id: string) {
    const el = msgRefs.current.get(id);
    if (!el) return;
    el.scrollIntoView({ behavior: "smooth", block: "center" });
    setFlashId(id);
    setTimeout(() => setFlashId((f) => (f === id ? null : f)), 1200);
  }

  const uploadAttachment = useUploadAttachment();
  const [staged, setStaged] = useState<UploadedAttachment | null>(null);
  const [uploading, setUploading] = useState(false);
  const fileRef = useRef<HTMLInputElement>(null);

  function onDraftChange(v: string) {
    setDraft(v);
    const now = Date.now();
    if (socket && now - typingSentAt.current > 2000) { socket.sendTyping(conv.threadId); typingSentAt.current = now; }
  }

  async function onPickFile(file: File | undefined) {
    if (!file) return;
    setUploading(true);
    try { setStaged(await uploadAttachment(file)); }
    catch (e) { onError((e as Error).message); }
    finally { setUploading(false); if (fileRef.current) fileRef.current.value = ""; }
  }

  async function submit() {
    const text = draft.trim();
    if ((!text && !staged) || sending) return; // allow an attachment with no text
    const parent = replyTo?.id;
    const att = staged;
    setDraft(""); setReplyTo(null); setStaged(null); setSending(true);
    try {
      await t.send(text, {
        ...(parent ? { parentInteractionId: parent } : {}),
        ...(att ? { attachment: { contentRef: att.contentRef, mimeType: att.mimeType, sizeBytes: att.sizeBytes } } : {}),
      });
    } catch (e) { setDraft(text); setStaged(att); onError((e as Error).message); }
    finally { setSending(false); }
  }

  const typingLabel = t.typingUserIds.length === 1
    ? `${nameOf(t.typingUserIds[0])} is typing…`
    : t.typingUserIds.length > 1 ? "Several people are typing…" : "";

  return (
    <>
      <header style={{ display: "flex", alignItems: "center", gap: 10, padding: "12px 18px", borderBottom: "1px solid var(--border)" }}>
        <Avatar initials={initialsOf(conv.title)} size={34} gradient={conv.membership === "group" ? GROUP_GRAD : undefined} />
        <div>
          <div style={{ fontWeight: 600 }}>{conv.title}</div>
          <div style={{ color: "var(--muted)", fontSize: 12 }}>
            {conv.membership === "group" ? `${conv.participants.length} people` : "Direct message"}
          </div>
        </div>
      </header>

      <div style={{ flex: 1, overflowY: "auto", padding: 18, display: "flex", flexDirection: "column", gap: 10, background: "var(--bg)" }}>
        {t.loading && t.messages.length === 0 && <div style={{ margin: "auto", color: "var(--muted)" }}>Loading messages…</div>}
        {!t.loading && t.messages.length === 0 && <div style={{ margin: "auto", color: "var(--muted)" }}>No messages yet — say hello 👋</div>}
        {t.messages.map((msg) => (
          <MessageBubble
            key={msg.id} msg={msg}
            parent={msg.parentInteractionId ? byId[msg.parentInteractionId] : undefined}
            seen={msg.id === lastMineId && t.seenIds.has(msg.id)}
            showStatus={msg.id === lastMineId}
            flash={flashId === msg.id}
            registerRef={(el) => { if (el) msgRefs.current.set(msg.id, el); else msgRefs.current.delete(msg.id); }}
            onReact={(emoji) => t.react(msg.id, emoji)}
            onReply={() => startReply(msg)}
            onQuoteClick={jumpTo}
          />
        ))}
        <div ref={endRef} />
      </div>

      <div style={{ minHeight: 18, padding: "0 18px", color: "var(--muted)", fontSize: 12, fontStyle: "italic" }}>{typingLabel}</div>

      {replyTo && (
        <div style={{ display: "flex", alignItems: "center", gap: 10, margin: "0 14px", padding: "8px 12px", borderRadius: 10, background: "var(--panel-2)", borderLeft: "3px solid var(--orange)" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 11, color: "var(--orange)", fontWeight: 600 }}>Replying to {replyTo.mine ? "yourself" : nameOf(replyTo.senderId ?? "")}</div>
            <div style={{ fontSize: 12.5, color: "var(--muted)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{replyTo.text}</div>
          </div>
          <button onClick={() => setReplyTo(null)} style={{ background: "none", border: "none", color: "var(--muted)", cursor: "pointer", fontSize: 16 }} aria-label="Cancel reply">×</button>
        </div>
      )}

      {staged && (
        <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "0 14px", padding: "8px 12px", borderRadius: 10, background: "var(--panel-2)", border: "1px solid var(--border)" }}>
          <Icon name={isImage(staged.mimeType) ? "image" : "file"} size={16} />
          <span style={{ flex: 1, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", fontSize: 13 }}>{staged.filename}</span>
          <span style={{ color: "var(--muted)", fontSize: 12 }}>{fmtBytes(staged.sizeBytes)}</span>
          <button onClick={() => setStaged(null)} style={{ background: "none", border: "none", color: "var(--muted)", cursor: "pointer", fontSize: 16 }} aria-label="Remove attachment">×</button>
        </div>
      )}

      <footer style={{ display: "flex", gap: 8, padding: 14, borderTop: "1px solid var(--border)", alignItems: "center" }}>
        <input ref={fileRef} type="file" hidden onChange={(e) => void onPickFile(e.target.files?.[0])} />
        <button onClick={() => fileRef.current?.click()} disabled={uploading} title="Attach a file" style={{ ...actionBtnStyle, width: 38, height: 38, flexShrink: 0, opacity: uploading ? 0.5 : 1 }}>
          {uploading ? "…" : "📎"}
        </button>
        <input
          ref={inputRef}
          value={draft} onChange={(e) => onDraftChange(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); void submit(); } }}
          placeholder="Type a message…" style={inputStyle}
        />
        <Btn icon="send" onClick={() => void submit()} disabled={sending || (!draft.trim() && !staged)}>Send</Btn>
      </footer>
    </>
  );
}

function MessageBubble({
  msg, parent, seen, showStatus, flash, registerRef, onReact, onReply, onQuoteClick,
}: {
  msg: UiMessage; parent?: UiMessage; seen: boolean; showStatus: boolean; flash?: boolean;
  registerRef?: (el: HTMLElement | null) => void;
  onReact: (emoji: string) => void; onReply: () => void; onQuoteClick?: (id: string) => void;
}) {
  const [hover, setHover] = useState(false);
  const [picker, setPicker] = useState(false);

  return (
    <div
      ref={registerRef}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => { setHover(false); setPicker(false); }}
      style={{
        alignSelf: msg.mine ? "flex-end" : "flex-start", maxWidth: "72%", display: "flex", flexDirection: "column",
        alignItems: msg.mine ? "flex-end" : "flex-start", position: "relative",
        borderRadius: 14, padding: 2, transition: "background 0.4s",
        background: flash ? "rgba(253,169,19,0.22)" : "transparent",
      }}
    >
      {parent && (
        <button
          type="button"
          onClick={() => parent.id && onQuoteClick?.(parent.id)}
          title="Go to message"
          style={{ maxWidth: "100%", padding: "4px 10px", marginBottom: 3, borderRadius: 8, background: "var(--panel-2)", borderLeft: "3px solid var(--orange)", border: "none", borderLeftWidth: 3, fontSize: 12, color: "var(--muted)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", cursor: "pointer", textAlign: "left" }}
        >
          <span style={{ opacity: 0.8 }}>↩ {parent.text}</span>
        </button>
      )}

      <div style={{ display: "flex", alignItems: "center", gap: 6, flexDirection: msg.mine ? "row-reverse" : "row" }}>
        {(msg.text || !msg.attachment) && (
          <div style={{
            background: msg.mine ? "var(--grad-brand)" : "var(--panel)", color: msg.mine ? "#fff" : "var(--text)",
            padding: "8px 12px", borderRadius: 14,
            borderBottomRightRadius: msg.mine ? 4 : 14, borderBottomLeftRadius: msg.mine ? 14 : 4,
            border: msg.mine ? "none" : "1px solid var(--border)",
            whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 14,
          }}>
            {msg.text}
          </div>
        )}
        {hover && (
          <div style={{ display: "flex", gap: 2, position: "relative" }}>
            <button onClick={() => setPicker((p) => !p)} title="React" style={actionBtnStyle}>🙂</button>
            <button onClick={onReply} title="Reply" style={actionBtnStyle}>↩</button>
            {picker && (
              <div style={{ position: "absolute", bottom: "100%", [msg.mine ? "right" : "left"]: 0, marginBottom: 4, display: "flex", gap: 2, padding: 4, borderRadius: 999, background: "var(--panel)", border: "1px solid var(--border)", boxShadow: "0 6px 20px rgba(0,0,0,0.35)", zIndex: 5 }}>
                {REACTION_EMOJIS.map((e) => (
                  <button key={e} onClick={() => { onReact(e); setPicker(false); }} style={{ ...actionBtnStyle, fontSize: 16 }}>{e}</button>
                ))}
              </div>
            )}
          </div>
        )}
      </div>

      {msg.attachment && (
        <div style={{ marginTop: 4, display: "flex", justifyContent: msg.mine ? "flex-end" : "flex-start" }}>
          <AttachmentView att={msg.attachment} />
        </div>
      )}

      {msg.reactions && msg.reactions.length > 0 && (
        <div style={{ display: "flex", gap: 4, marginTop: 3, flexWrap: "wrap" }}>
          {msg.reactions.map((r) => (
            <button
              key={r.emoji} onClick={() => onReact(r.emoji)}
              style={{
                display: "inline-flex", alignItems: "center", gap: 3, padding: "1px 7px", borderRadius: 999, fontSize: 12, cursor: "pointer",
                background: r.mine ? "rgba(253,169,19,0.18)" : "var(--panel-2)",
                border: `1px solid ${r.mine ? "var(--orange)" : "var(--border)"}`, color: "var(--text)",
              }}
            >
              <span>{r.emoji}</span><span style={{ color: "var(--muted)" }}>{r.count}</span>
            </button>
          ))}
        </div>
      )}

      <div style={{ fontSize: 10.5, color: "var(--muted)", marginTop: 2 }}>
        {timeOf(msg.at)}{showStatus && msg.mine ? ` · ${seen ? "Seen" : "Sent"}` : ""}
      </div>
    </div>
  );
}

const actionBtnStyle: CSSProperties = {
  background: "var(--panel-2)", border: "1px solid var(--border)", borderRadius: 8,
  width: 26, height: 26, display: "grid", placeItems: "center", cursor: "pointer", fontSize: 13, color: "var(--text)", padding: 0,
};

function NewChatModal({
  open, onClose, directory, onCreate,
}: {
  open: boolean; onClose: () => void; directory: UiPerson[];
  onCreate: (ids: string[], opts: { membership: Membership; subject?: string }) => Promise<void>;
}) {
  const [picked, setPicked] = useState<string[]>([]);
  const [subject, setSubject] = useState("");
  const [q, setQ] = useState("");
  const [busy, setBusy] = useState(false);

  useEffect(() => { if (!open) { setPicked([]); setSubject(""); setQ(""); setBusy(false); } }, [open]);

  const membership: Membership = picked.length > 1 ? "group" : "dm";
  const filtered = directory.filter((p) => p.name.toLowerCase().includes(q.trim().toLowerCase()));
  const toggle = (id: string) => setPicked((l) => (l.includes(id) ? l.filter((x) => x !== id) : [...l, id]));

  async function create() {
    if (!picked.length || busy) return;
    setBusy(true);
    await onCreate(picked, { membership, ...(membership === "group" && subject.trim() ? { subject: subject.trim() } : {}) });
    setBusy(false);
  }

  return (
    <Modal
      open={open} onClose={onClose} title="New conversation"
      subtitle={membership === "group" ? "Group chat" : "Direct message"} icon="send"
      footer={<>
        <Btn variant="ghost" onClick={onClose}>Cancel</Btn>
        <Btn icon="send" onClick={() => void create()} disabled={!picked.length || busy}>{busy ? "Starting…" : "Start chat"}</Btn>
      </>}
    >
      <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search people…" style={{ ...inputStyle, marginBottom: 10 }} />
      {membership === "group" && (
        <Field label="Group name (optional)">
          <input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder="e.g. Storm response" style={inputStyle} />
        </Field>
      )}
      <div style={{ maxHeight: 320, overflowY: "auto", marginTop: 8, display: "flex", flexDirection: "column", gap: 2 }}>
        {filtered.length === 0 && <div style={{ color: "var(--muted)", padding: 10 }}>No people found.</div>}
        {filtered.map((p) => (
          <label key={p.id} style={{
            display: "flex", alignItems: "center", gap: 10, padding: "8px 10px", borderRadius: 10, cursor: "pointer",
            background: picked.includes(p.id) ? "var(--panel-2)" : "transparent",
          }}>
            <input type="checkbox" checked={picked.includes(p.id)} onChange={() => toggle(p.id)} />
            <Avatar initials={initialsOf(p.name)} size={30} gradient={p.kind === "customer" ? CUSTOMER_GRAD : undefined} />
            <span style={{ flex: 1 }}>{p.name}</span>
            <Pill tone="muted">{p.kind}</Pill>
          </label>
        ))}
      </div>
    </Modal>
  );
}
