// Conversations 3-column inbox
const { Card: VCard, Pill: VPill, Btn: VBtn, Avatar: VAvatar, Topbar: VTopbar, ConcernIcon: VCon, Tabs: VTabs, Dropdown: VDrop, Input: VInput, Empty: VEmpty, ChannelBadge: VChannel } = window.UI;

const LOOP_EMOJIS = ["😀","😁","😂","🤣","😊","😍","😉","😎","🤗","🤩","🙏","👍","👎","👌","👏","🙌","💪","🔥","✨","🎉","✅","❌","⚠️","💡","📷","📎","💬","❤️","💚","😢","😭","😅","😴","🤔","🙂","😇"];

// Agent-side voice call control for a web conversation. CSR initiates; the
// customer's widget accepts; the agent is the WebRTC caller.
function AgentVoiceCall({ conversationId }) {
  const [status, setStatus] = React.useState("idle"); // idle|ringing|connecting|connected|ended
  const [muted, setMuted] = React.useState(false);
  const callRef = React.useRef(null);
  const audioRef = React.useRef(null);

  const cleanup = React.useCallback(() => {
    if (callRef.current) { callRef.current.hangup(); callRef.current = null; }
    if (audioRef.current) audioRef.current.srcObject = null;
  }, []);

  React.useEffect(() => {
    setStatus("idle"); setMuted(false);
    return () => { if (callRef.current) { callRef.current.hangup(); callRef.current = null; } if (audioRef.current) audioRef.current.srcObject = null; };
  }, [conversationId]);

  React.useEffect(() => {
    const off = window.LoopData.onCall((m) => {
      if (m.conversationId !== conversationId || m.from !== "customer") return;
      if (m.action === "accept") {
        // Guard re-entrancy: a duplicate "accept" (network retry / double-tap)
        // must not spin up a second PeerConnection and orphan the first mic.
        if (callRef.current) return;
        setStatus("connecting");
        const call = window.LoopVoice.createCall({
          conversationId, initiator: true,
          send: (msg) => window.LoopData.sendCall(Object.assign({ conversationId }, msg)),
          onState: (s) => { if (s === "connected") setStatus("connected"); if (s === "closed" || s === "failed") { setStatus("ended"); callRef.current = null; } },
          onRemoteStream: (stream) => { if (audioRef.current) { audioRef.current.srcObject = stream; audioRef.current.play().catch(() => {}); } },
        });
        callRef.current = call;
        call.start().catch(() => setStatus("ended"));
      } else if (m.action === "decline" || m.action === "hangup") {
        cleanup(); setStatus(m.action === "decline" ? "idle" : "ended");
      } else if (callRef.current) {
        callRef.current.handleSignal(m);
      }
    });
    return off;
  }, [conversationId, cleanup]);

  // Ringback while waiting for the customer to pick up.
  React.useEffect(() => {
    if (!window.LoopVoice || !window.LoopVoice.startRing) return;
    if (status === "ringing") window.LoopVoice.startRing(); else window.LoopVoice.stopRing();
    return () => { if (window.LoopVoice && window.LoopVoice.stopRing) window.LoopVoice.stopRing(); };
  }, [status]);

  if (!window.LoopVoice || !window.LoopVoice.supported) return null;

  const invite = () => { if (callRef.current) return; setStatus("ringing"); window.LoopData.sendCall({ conversationId, action: "invite" }); if (audioRef.current) audioRef.current.play().catch(() => {}); };
  const hangup = () => { window.LoopData.sendCall({ conversationId, action: "hangup" }); cleanup(); setStatus("idle"); };
  const toggleMute = () => { const v = !muted; setMuted(v); if (callRef.current) callRef.current.setMuted(v); };
  const label = { ringing: "Ringing…", connecting: "Connecting…", connected: "On call", ended: "Call ended" }[status];

  return (
    <>
      <audio ref={audioRef} autoPlay playsInline />
      {(status === "idle" || status === "ended") ? (
        <button onClick={invite} title="Voice call" className="h-9 px-3 rounded-lg hover:bg-white/[0.05] text-white/60 text-[12px] flex items-center gap-1.5"><Icon.Phone size={14}/> Call</button>
      ) : (
        <div className="flex items-center gap-1.5">
          <span className={`text-[11.5px] flex items-center gap-1.5 ${status === "connected" ? "text-lime" : "text-white/60"}`}><Icon.Phone size={13}/> {label}</span>
          {status === "connected" && <button onClick={toggleMute} title="Mute" className={`h-8 w-8 grid place-items-center rounded-lg ${muted ? "bg-amber-400/15 text-amber-300" : "hover:bg-white/[0.05] text-white/60"}`}><Icon.Mic size={14}/></button>}
          <button onClick={hangup} title="Hang up" className="h-8 px-2.5 grid place-items-center rounded-lg bg-rose-500/15 text-rose-300 hover:bg-rose-500/25 text-[12px]"><Icon.Close size={13}/></button>
        </div>
      )}
    </>
  );
}

function Conversations({ authAgent, defaultChannel = "all" }) {
  const [tab, setTab] = React.useState("active");
  const [filterSite, setFilterSite] = React.useState("all");
  const [filterConcern, setFilterConcern] = React.useState("all");
  const [filterChannel, setFilterChannel] = React.useState(defaultChannel);
  const [search, setSearch] = React.useState("");
  const [activeId, setActiveId] = React.useState(null);
  const [showDetail, setShowDetail] = React.useState(false); // mobile detail slide-over
  const [draft, setDraft] = React.useState("");
  const [subject, setSubject] = React.useState("");
  const [suggesting, setSuggesting] = React.useState(false);
  const [showCanned, setShowCanned] = React.useState(false);
  const [showEmoji, setShowEmoji] = React.useState(false);
  const [noteDraft, setNoteDraft] = React.useState("");
  const [showNoteInput, setShowNoteInput] = React.useState(false);
  const [assignOpen, setAssignOpen] = React.useState(false);
  const [tagInputOpen, setTagInputOpen] = React.useState(false);
  const [tagDraft, setTagDraft] = React.useState("");
  const [chats, setChats] = React.useState([]);
  const [typing, setTyping] = React.useState(false);
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    let alive = true;
    const load = () => window.LoopData.loadConversations({}).then(rows => { if (alive) setChats(rows); });
    load();
    const off = window.LoopData.onChange(load);
    return () => { alive = false; off && off(); };
  }, []);

  // Close the mobile detail slide-over when switching conversations.
  React.useEffect(() => { setShowDetail(false); }, [activeId]);

  const filtered = chats.filter(c => {
    if (tab !== "all" && c.status !== tab) return false;
    if (filterSite !== "all" && c.site !== filterSite) return false;
    if (filterConcern !== "all" && c.concern !== filterConcern) return false;
    if (filterChannel !== "all" && c.channel !== filterChannel) return false;
    if (search) {
      const q = search.toLowerCase();
      if (!c.visitor.toLowerCase().includes(q) && !c.username.toLowerCase().includes(q) && !(c.last || "").toLowerCase().includes(q)) return false;
    }
    return true;
  });

  const active = chats.find(c => c.id === activeId) || filtered[0] || null;
  const site = active ? (window.Mock.websites || []).find(w => w.id === active.site) || { name: active.site, color: "#A6F84A", domain: "" } : null;
  const concern = active ? (window.Mock.concerns || []).find(x => x.id === active.concern) || { label: active.concern, tint: "#A6F84A" } : null;
  const isEmail = !!active && active.channel === "email";
  const isMessenger = !!active && active.channel === "messenger";
  const myAgent = authAgent || window.Mock._authAgent || { name: "Agent", color: "#A6F84A", id: null };
  const assignedAgent = active && active.assignee ? (window.Mock.agents || []).find(a => a.id === active.assignee) || null : null;

  const reload = async () => {
    const rows = await window.LoopData.loadConversations({});
    setChats(rows);
  };

  const sendMsg = async () => {
    if (!draft.trim() || !active) return;
    const text = draft.trim();
    const id = active.id;
    setDraft("");
    setChats(prev => prev.map(c => c.id === id ? { ...c, messages: [...c.messages, { who: "a", t: text }], last: text, status: "active" } : c));
    try { await window.LoopData.sendAgentMessage(id, text); } catch (e) { console.warn("send failed", e); }
    if (window.LoopData.mode === "mock") {
      setTyping(true);
      setTimeout(() => {
        setTyping(false);
        setChats(prev => prev.map(c => c.id === id ? { ...c, messages: [...c.messages, { who: "v", t: "Salamat po!" }] } : c));
      }, 2200);
    }
  };

  const suggestReply = async () => {
    if (!active || suggesting) return;
    setSuggesting(true);
    try {
      const r = await fetch("/api/suggest", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ conversationId: active.id }),
      });
      const data = await r.json();
      if (data.suggestion) setDraft(data.suggestion);
    } catch (e) { console.warn("suggest error", e); }
    finally { setSuggesting(false); }
  };

  React.useEffect(() => { setSubject(active && active.channel === "email" ? "Re: your support request" : ""); }, [activeId]);
  React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [active && active.messages && active.messages.length, typing]);

  const fileInputRef = React.useRef(null);
  const attachInputRef = React.useRef(null);
  const onPickAttachment = (e) => {
    const f = e.target.files && e.target.files[0]; if (!f || !active) return;
    // Capture synchronously — closures in async callbacks would read stale state
    const capturedId = active.id;
    const capturedDraft = draft.trim();
    const isImg = (f.type || "").startsWith("image/");

    if (isImg) {
      if (f.size > 15000000) { alert("Image is too large (max 15MB)."); e.target.value = ""; return; }
      const reader = new FileReader();
      reader.onerror = () => alert("Could not read image file.");
      reader.onload = (ev) => {
        const img = new Image();
        img.onload = () => {
          try {
            const MAX = 1200;
            const scale = Math.min(1, MAX / Math.max(img.width || 1, img.height || 1));
            const w = Math.max(1, Math.round(img.width * scale));
            const h = Math.max(1, Math.round(img.height * scale));
            const canvas = document.createElement("canvas");
            canvas.width = w; canvas.height = h;
            const ctx = canvas.getContext("2d");
            if (!ctx) { alert("Could not process image. Please try again."); return; }
            ctx.fillStyle = "#ffffff";
            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(img, 0, 0, w, h);
            const compressed = canvas.toDataURL("image/jpeg", 0.82);
            setChats(prev => prev.map(c => c.id === capturedId ? { ...c, messages: [...c.messages, { who: "a", t: capturedDraft, image: compressed }], last: capturedDraft || "📷 Image", status: "active" } : c));
            window.LoopData.sendAgentMessage(capturedId, capturedDraft, compressed);
            setDraft("");
          } catch (err) { console.error("Image compress error:", err); alert("Could not process image. Please try a PNG or JPG."); }
        };
        img.onerror = () => alert("Could not load image. Please try a different file.");
        img.src = ev.target.result;
      };
      reader.readAsDataURL(f);
    } else {
      if (f.size > 5000000) { alert("File is too large (max 5MB)."); e.target.value = ""; return; }
      const reader = new FileReader();
      reader.onerror = () => alert("Could not read file. Please try again.");
      reader.onload = () => {
        const file = { name: f.name, type: f.type || "application/octet-stream", data: reader.result };
        setChats(prev => prev.map(c => c.id === capturedId ? { ...c, messages: [...c.messages, { who: "a", t: capturedDraft, file }], last: capturedDraft || ("📎 " + f.name), status: "active" } : c));
        window.LoopData.sendAgentMessage(capturedId, capturedDraft, null, file);
        setDraft("");
      };
      reader.readAsDataURL(f);
    }
    e.target.value = "";
  };

  const closeChat = async () => { if (!active) return; await window.LoopData.closeConversation(active.id); };

  const [aiBusy, setAiBusy] = React.useState(false);
  const toggleAi = async () => {
    if (!active || aiBusy) return;
    const aiOn = !!active._aiMode && !active._humanRequested && !active._csrActive;
    setAiBusy(true);
    try { await window.LoopData.setConversationAi(active.id, !aiOn); await reload(); }
    catch (e) { console.warn("ai toggle failed", e); }
    finally { setAiBusy(false); }
  };

  const saveNote = async () => {
    const body = noteDraft.trim();
    if (!body || !active) return;
    try { await window.LoopData.addNote(active.id, body); } catch (e) { console.warn("note failed", e); }
    setNoteDraft(""); setShowNoteInput(false);
    try { const rows = await window.LoopData.loadConversations({}); setChats(rows); } catch (e) {}
  };

  const doAssign = async (agentId) => {
    if (!active) return;
    try { await window.LoopData.assignConversation(active.id, agentId); setAssignOpen(false); await reload(); }
    catch (e) { console.warn("assign failed", e); }
  };

  const addTag = async () => {
    const tag = tagDraft.trim(); if (!tag || !active) return;
    const newTags = [...new Set([...(active.tags || []), tag])];
    try { await window.LoopData.setTags(active.id, newTags); setTagDraft(""); setTagInputOpen(false); await reload(); }
    catch (e) { console.warn("tag failed", e); }
  };

  const removeTag = async (tag) => {
    if (!active) return;
    const newTags = (active.tags || []).filter(t => t !== tag);
    try { await window.LoopData.setTags(active.id, newTags); await reload(); }
    catch (e) { console.warn("remove tag failed", e); }
  };

  const noteAgo = (ts) => {
    if (!ts) return "";
    const m = Math.max(0, Math.floor((Date.now() - ts) / 60000));
    if (m < 1) return "just now"; if (m < 60) return m + "m ago";
    const h = Math.floor(m / 60); if (h < 24) return h + "h ago";
    return Math.floor(h / 24) + "d ago";
  };

  const fillTemplate = (body) => {
    if (!body) return body;
    const full = (active && active.visitor) || "there"; const first = full.split(" ")[0] || full;
    const siteName = (site && site.name) || (active && active.site) || "";
    return body.replace(/\{name\}/gi, full).replace(/\{first\}/gi, first).replace(/\{site\}/gi, siteName);
  };

  return (
    <div className="flex-1 flex flex-col h-full overflow-hidden">
      <VTopbar
        title="Conversations"
        subtitle={`${filtered.length} matching · ${chats.filter(c => c.status === "waiting").length} waiting`}
        right={<VBtn variant="ghost" size="md" onClick={reload}><Icon.Refresh size={14}/> Sync</VBtn>}
      />
      <div className="flex-1 min-h-0 flex flex-col md:grid md:grid-cols-[300px_1fr] lg:grid-cols-[300px_1fr_320px]">
        {/* LEFT — chat list (full screen on mobile until a chat is opened) */}
        <aside className={`border-r border-white/[0.05] flex-1 flex-col min-h-0 bg-ink-950/30 ${activeId ? "hidden md:flex" : "flex"}`}>
          <div className="p-3 space-y-2.5 border-b border-white/[0.05]">
            <div className="relative">
              <Icon.Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-white/40"/>
              <input
                className="w-full h-9 pl-8 pr-3 rounded-xl bg-white/[0.03] border border-white/[0.07] text-[12.5px] text-white/85 outline-none focus:border-lime/40"
                placeholder="Search visitors, messages…"
                value={search}
                onChange={e => setSearch(e.target.value)}
              />
            </div>
            <div className="grid grid-cols-2 gap-2">
              <VDrop value={filterSite} onChange={setFilterSite} options={[{ value: "all", label: "All websites" }, ...(window.Mock.websites || []).map(w => ({ value: w.id, label: w.name }))]} />
              <VDrop value={filterConcern} onChange={setFilterConcern} options={[{ value: "all", label: "All concerns" }, ...(window.Mock.concerns || []).map(c => ({ value: c.id, label: c.label }))]} />
            </div>
            <VDrop value={filterChannel} onChange={setFilterChannel} options={[{ value: "all", label: "All channels" }, { value: "web", label: "Web chat" }, { value: "email", label: "Email" }, { value: "messenger", label: "Messenger" }]} />
            <VTabs value={tab} onChange={setTab} tabs={[
              { value: "waiting", label: "Waiting", count: chats.filter(c => c.status === "waiting").length },
              { value: "active", label: "Active", count: chats.filter(c => c.status === "active").length },
              { value: "closed", label: "Closed", count: chats.filter(c => c.status === "closed").length },
            ]} />
          </div>
          {chats.some(c => c._humanRequested && c.status !== "closed") && (
            <div className="mx-3 mb-2 px-3 py-2 rounded-xl bg-rose-500/15 border border-rose-400/30 flex items-center gap-2">
              <Icon.Bell size={13} className="text-rose-300 shrink-0"/>
              <span className="text-[11.5px] text-rose-200 font-medium">CSR requested — {chats.filter(c => c._humanRequested && c.status !== "closed").length} conversation{chats.filter(c => c._humanRequested && c.status !== "closed").length > 1 ? "s" : ""} need attention</span>
            </div>
          )}
          <div className="flex-1 overflow-y-auto">
            {filtered.length === 0 ? (
              <VEmpty icon="ChatDots" title={search ? "No results" : "No conversations"} body={search ? `No match for "${search}"` : "Try changing your filters above."}/>
            ) : filtered.map(c => {
              const s = (window.Mock.websites || []).find(w => w.id === c.site) || { name: c.site, color: "#A6F84A" };
              const isActive = c.id === activeId;
              return (
                <button key={c.id} onClick={() => setActiveId(c.id)}
                  className={`relative w-full text-left flex items-start gap-3 px-4 py-3 border-l-2 transition ${isActive ? "bg-white/[0.04] border-lime" : "border-transparent hover:bg-white/[0.02]"}`}>
                  <VAvatar name={c.visitor} color={c.color} size={36} status={c.status === "active" ? "online" : c.status === "waiting" ? "away" : "offline"} />
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center justify-between gap-2">
                      <div className="text-[13px] font-medium text-white/95 truncate">{c.visitor}</div>
                      <div className="text-[10.5px] text-white/40 shrink-0">{c.minutes}m</div>
                    </div>
                    <div className="flex items-center gap-1.5 mt-0.5">
                      <span className="w-1.5 h-1.5 rounded-full" style={{ background: s.color }}/>
                      <span className="text-[10.5px] text-white/45 truncate">{s.name} · {c.username}</span>
                    </div>
                    <div className="text-[12px] text-white/65 line-clamp-2 mt-1.5 leading-snug">{c.last}</div>
                    <div className="flex items-center gap-1.5 mt-2">
                      <VCon id={c.concern} size={10}/>
                      <VChannel channel={c.channel} size="xs"/>
                      {c._humanRequested && <span className="text-[9.5px] font-bold px-1.5 py-0.5 rounded-full bg-rose-500 text-white">CSR</span>}
                      {c.unread > 0 && <span className="text-[9.5px] font-bold px-1.5 py-0.5 rounded-full bg-lime text-ink-950">{c.unread} new</span>}
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        </aside>

        {/* CENTER — open conversation */}
        <section className={`flex-1 flex-col min-h-0 bg-ink-950/10 ${activeId ? "flex" : "hidden md:flex"}`}>
          {!active ? (
            <VEmpty icon="Chat" title="Pick a conversation" body="Select a chat from the inbox to start replying."/>
          ) : (
            <>
              <div className="flex items-center justify-between gap-2 px-3 md:px-6 py-3.5 border-b border-white/[0.05]">
                <div className="flex items-center gap-2 md:gap-3 min-w-0">
                  <button onClick={() => setActiveId(null)} aria-label="Back" className="md:hidden h-9 w-9 -ml-1 shrink-0 grid place-items-center rounded-lg hover:bg-white/[0.05] text-white/70"><Icon.ChevronLeft size={18}/></button>
                  <VAvatar name={active.visitor} color={active.color} size={38} status={active.status === "active" ? "online" : active.status === "waiting" ? "away" : "offline"}/>
                  <div className="min-w-0">
                    <div className="text-[14px] font-medium text-white/95 truncate">{active.visitor}</div>
                    <div className="text-[11.5px] text-white/45 flex items-center gap-1.5 min-w-0">
                      <span className="w-1.5 h-1.5 rounded-full shrink-0" style={{ background: site.color }}/>
                      <span className="truncate">{site.name}</span>
                      <VChannel channel={active.channel} size="sm" withLabel/>
                      {isMessenger && <VPill tone="lime" className="ml-1">Window open</VPill>}
                      <span className="text-white/25 mx-0.5 hidden lg:inline">·</span>
                      <span className="font-mono text-white/65 truncate hidden lg:inline">{active.page}</span>
                    </div>
                  </div>
                </div>
                <div className="flex items-center gap-1 shrink-0">
                  {active.channel === "web" && <AgentVoiceCall conversationId={active.id} />}
                  {(() => {
                    const aiOn = !!active._aiMode && !active._humanRequested && !active._csrActive;
                    return (
                      <button onClick={toggleAi} disabled={aiBusy}
                        title={aiOn ? "Loop AI is replying — click to take over this chat" : "Hand this chat back to the AI assistant"}
                        className={`h-9 px-3 rounded-lg text-[12px] flex items-center gap-1.5 ${aiBusy ? "opacity-50 cursor-wait" : ""} ${aiOn ? "bg-lime/15 text-lime hover:bg-lime/25" : "hover:bg-white/[0.05] text-white/60"}`}>
                        <Icon.Sparkle size={14}/> <span className="hidden sm:inline">{aiOn ? "AI on" : "Hand to AI"}</span>
                      </button>
                    );
                  })()}
                  <button onClick={() => setAssignOpen(true)} className="h-9 px-3 rounded-lg hover:bg-white/[0.05] text-white/60 text-[12px] flex items-center gap-1.5"><Icon.Users size={14}/> <span className="hidden sm:inline">Assign</span></button>
                  <button onClick={() => setShowDetail(true)} aria-label="Visitor details" className="lg:hidden h-9 w-9 grid place-items-center rounded-lg hover:bg-white/[0.05] text-white/60"><Icon.Id size={16}/></button>
                </div>
              </div>

              <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 md:px-6 py-4 md:py-5 space-y-3.5">
                <div className="text-center"><span className="text-[10.5px] uppercase tracking-wider text-white/35 px-3 py-1 rounded-full bg-white/[0.04]">Today</span></div>
                {active.messages.map((m, i) => m.who === "s" ? (
                  <div key={i} className="text-center my-1"><span className="text-[10.5px] text-white/45 px-3 py-1 rounded-full bg-white/[0.04]">{m.t}</span></div>
                ) : (
                  <div key={i} className={`flex gap-2 ${m.who === "a" ? "justify-end" : "justify-start"}`}>
                    {m.who === "v" && <VAvatar name={active.visitor} color={active.color} size={28} />}
                    <div className={`max-w-[80%] md:max-w-[60%] px-4 py-2.5 rounded-2xl text-[13.5px] leading-relaxed ${m.who === "a" ? "bg-lime text-ink-950 rounded-br-md" : "glass text-white/90 rounded-bl-md"}`}>
                      {m.image && <img src={m.image} alt="" className="rounded-lg max-w-[220px] mb-1 block"/>}
                      {m.file && <a href={m.file.data} download={m.file.name} className={`mb-1 inline-flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-[12px] ${m.who==="a"?"bg-ink-950/15 text-ink-950":"bg-white/10 text-white/90"}`}><Icon.Paperclip size={13}/><span className="truncate max-w-[180px]">{m.file.name}</span></a>}
                      {m.t}
                      {m.auto && <span className="ml-1.5 text-[10px] opacity-60 italic">auto</span>}
                    </div>
                    {m.who === "a" && <VAvatar name={myAgent.name} color={myAgent.color} size={28}/>}
                  </div>
                ))}
                {typing && (
                  <div className="flex gap-2"><VAvatar name={active.visitor} color={active.color} size={28} />
                    <div className="glass rounded-2xl rounded-bl-md px-4 py-3 flex items-center gap-1">
                      <span className="typing-dot w-1.5 h-1.5 rounded-full bg-white/60"/><span className="typing-dot w-1.5 h-1.5 rounded-full bg-white/60"/><span className="typing-dot w-1.5 h-1.5 rounded-full bg-white/60"/>
                    </div>
                  </div>
                )}
              </div>

              <div className="px-4 pb-4 pt-3 border-t border-white/[0.05]">
                {showCanned && (
                  <motion.div initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} className="mb-2 glass rounded-xl p-1.5 max-h-[200px] overflow-y-auto">
                    {(window.Mock.cannedResponses || []).map(r => (
                      <button key={r.id} onClick={() => { setDraft(fillTemplate(r.body)); setShowCanned(false); }} className="w-full text-left px-3 py-2 rounded-lg hover:bg-white/[0.05] flex items-start gap-3">
                        <span className="text-[10.5px] font-mono text-lime mt-0.5">{r.shortcut}</span>
                        <div className="flex-1"><div className="text-[12.5px] font-medium text-white/95">{r.title}</div><div className="text-[11.5px] text-white/50 line-clamp-2">{fillTemplate(r.body)}</div></div>
                      </button>
                    ))}
                  </motion.div>
                )}
                {showEmoji && (
                  <motion.div initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} className="mb-2 glass rounded-xl p-2 max-h-[160px] overflow-y-auto" style={{ display: "flex", flexWrap: "wrap", gap: "2px" }}>
                    {LOOP_EMOJIS.map((em, i) => <button key={i} onClick={() => { setDraft(d => d + em); setShowEmoji(false); }} style={{ width: 32, height: 32, display: "grid", placeItems: "center", fontSize: 18, borderRadius: 8, flexShrink: 0 }} className="hover:bg-white/[0.08]">{em}</button>)}
                  </motion.div>
                )}
                {isEmail && (
                  <div className="mb-2 flex items-center gap-2 px-3 h-9 rounded-xl bg-white/[0.03] border border-white/[0.07]">
                    <span className="text-[11px] text-white/40">Subject</span>
                    <input value={subject} onChange={e => setSubject(e.target.value)} className="flex-1 bg-transparent outline-none text-[12.5px] text-white/90" placeholder="Subject line"/>
                  </div>
                )}
                <div className="glass-strong rounded-2xl">
                  <textarea value={draft} onChange={e => setDraft(e.target.value)} onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMsg(); } }}
                    placeholder={isEmail ? "Write an email reply…" : isMessenger ? "Reply on Messenger…" : `Reply to ${active.visitor.split(" ")[0]}…`}
                    rows={2} className="w-full resize-none bg-transparent outline-none text-[13.5px] text-white/95 placeholder:text-white/30 px-4 pt-3 pb-1"/>
                  <div className="flex items-center justify-between px-2 pb-2">
                    <div className="flex items-center gap-0.5">
                      <input type="file" ref={attachInputRef} onChange={onPickAttachment} style={{display:"none"}}/>
                      <button onClick={() => attachInputRef.current && attachInputRef.current.click()} className="h-8 w-8 grid place-items-center rounded-lg hover:bg-white/[0.05] text-white/55"><Icon.Paperclip size={15}/></button>
                      <input type="file" accept="image/*" ref={fileInputRef} onChange={onPickAttachment} style={{display:"none"}}/>
                      <button onClick={() => fileInputRef.current && fileInputRef.current.click()} className="h-8 w-8 grid place-items-center rounded-lg hover:bg-white/[0.05] text-white/55"><Icon.Image size={15}/></button>
                      <button onClick={() => { setShowEmoji(s => !s); setShowCanned(false); }} className={`h-8 w-8 grid place-items-center rounded-lg ${showEmoji ? "bg-lime/15 text-lime" : "hover:bg-white/[0.05] text-white/55"}`}><Icon.Smile size={15}/></button>
                      <span className="w-px h-5 bg-white/[0.08] mx-1"/>
                      <button onClick={() => setShowCanned(s => !s)} className={`h-8 px-2.5 rounded-lg text-[12px] flex items-center gap-1.5 ${showCanned ? "bg-lime/15 text-lime" : "hover:bg-white/[0.05] text-white/65"}`}><Icon.Bookmark size={13}/> Canned</button>
                      <button onClick={suggestReply} disabled={!active || suggesting} title="AI suggest reply" className={`h-8 px-2.5 rounded-lg text-[12px] flex items-center gap-1.5 transition ${suggesting ? "text-lime/70 cursor-wait" : "hover:bg-lime/10 hover:text-lime text-white/65"}`}>
                        {suggesting ? <Icon.Refresh size={13} className="animate-spin"/> : <Icon.Sparkle size={13}/>} AI
                      </button>
                    </div>
                    <div className="flex items-center gap-2">
                      <span className="text-[10.5px] text-white/35">↵ to send</span>
                      <button onClick={sendMsg} disabled={!draft.trim()} className="btn-lime h-8 px-3.5 rounded-lg text-[12.5px] font-semibold disabled:opacity-40 flex items-center gap-1.5">Send <Icon.Send size={12}/></button>
                    </div>
                  </div>
                </div>
              </div>
            </>
          )}
        </section>

        {/* RIGHT — visitor details (static column on lg, slide-over below it) */}
        <aside className={`border-l border-white/[0.05] bg-ink-950/30 overflow-y-auto ${showDetail ? "fixed inset-0 z-[60] block" : "hidden"} lg:static lg:block lg:z-auto`}>
          {active && (
            <div className="relative p-5 space-y-5">
              <button onClick={() => setShowDetail(false)} aria-label="Close" className="lg:hidden absolute top-3 right-3 h-8 w-8 grid place-items-center rounded-lg bg-white/[0.05] text-white/70 hover:text-white"><Icon.Close size={16}/></button>
              <div className="text-center pb-5 border-b border-white/[0.05]">
                <div className="mx-auto w-16 h-16 rounded-2xl grid place-items-center font-bold text-ink-950 text-[20px]" style={{ background: `linear-gradient(135deg, ${active.color}, ${active.color}aa)` }}>{active.initials}</div>
                <div className="text-[15px] font-medium text-white/95 mt-3">{active.visitor}</div>
                <div className="text-[12px] text-white/45">{active.username}</div>
                <div className="flex items-center justify-center gap-1.5 mt-1.5">
                  <span className={`w-1.5 h-1.5 rounded-full ${active.status === "active" ? "bg-lime dot-online" : active.status === "waiting" ? "bg-yellow-300" : "bg-zinc-500"}`}/>
                  <span className="text-[11px] text-white/55 capitalize">{active.status}</span>
                </div>
              </div>

              {active.rating && (
                <div className="rounded-xl bg-white/[0.03] border border-white/[0.06] p-3 flex items-center justify-between">
                  <span className="text-[11px] text-white/55">Chat rating</span>
                  <span className="text-[13px] text-lime font-semibold">{"★".repeat(active.rating.stars || 0)}{active.rating.thumbs === "up" ? " 👍" : active.rating.thumbs === "down" ? " 👎" : ""}</span>
                </div>
              )}

              <div>
                <div className="text-[10.5px] uppercase tracking-[0.1em] text-white/40 mb-2">Visitor details</div>
                <div className="space-y-2.5">
                  <DetailRow label="Website" value={<span className="flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full" style={{background: site.color}}/>{site.name}</span>}/>
                  <DetailRow label="Username" value={active.userId || active.username} />
                  {active.phone && <DetailRow label="Phone" value={<span className="font-mono text-[11.5px] text-white/80">{active.phone}</span>} />}
                  <DetailRow label="Concern" value={<span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full" style={{ background: concern.tint }}/>{concern.label}</span>}/>
                  <DetailRow label="Current page" value={<span className="font-mono text-[11.5px] text-lime">{site.domain}{active.page}</span>} />
                  {active.ip && <DetailRow label="IP address" value={<span className="font-mono text-[11.5px] text-white/70">{active.ip}</span>} />}
                  <DetailRow label="Messages" value={(active.messages || []).length} />
                  <DetailRow label="Channel" value={<VChannel channel={active.channel} size="sm" withLabel/>} />
                </div>
              </div>

              <div>
                <div className="flex items-center justify-between mb-2">
                  <div className="text-[10.5px] uppercase tracking-[0.1em] text-white/40">Tags</div>
                  <button onClick={() => setTagInputOpen(t => !t)} className="text-white/55 hover:text-white"><Icon.Plus size={12}/></button>
                </div>
                <div className="flex flex-wrap gap-1.5">
                  {(active.tags || []).map(t => (
                    <span key={t} className="group flex items-center gap-1 text-[11px] px-2.5 py-0.5 rounded-full bg-white/[0.06] border border-white/[0.1] text-white/70">
                      {t}
                      <button onClick={() => removeTag(t)} className="opacity-0 group-hover:opacity-100 text-white/40 hover:text-rose-300 ml-0.5">×</button>
                    </span>
                  ))}
                  {tagInputOpen && (
                    <div className="w-full flex gap-1.5 mt-1">
                      <input value={tagDraft} onChange={e => setTagDraft(e.target.value)} onKeyDown={e => { if (e.key === "Enter") addTag(); if (e.key === "Escape") { setTagInputOpen(false); setTagDraft(""); } }}
                        autoFocus placeholder="Tag name…" className="flex-1 h-7 px-2.5 rounded-lg bg-white/[0.05] border border-white/[0.1] text-[12px] text-white/90 outline-none focus:border-lime/40"/>
                      <button onClick={addTag} disabled={!tagDraft.trim()} className="h-7 px-2.5 rounded-lg bg-lime/20 text-lime text-[11.5px] disabled:opacity-40">Add</button>
                    </div>
                  )}
                </div>
              </div>

              <div>
                <div className="flex items-center justify-between mb-2">
                  <div className="text-[10.5px] uppercase tracking-[0.1em] text-white/40">Internal notes</div>
                  <Icon.Note size={12} className="text-white/40"/>
                </div>
                <div className="space-y-2">
                  {(active.notes || []).length === 0 && !showNoteInput && (
                    <div className="text-[11.5px] text-white/35 italic px-1">No notes yet.</div>
                  )}
                  {(active.notes || []).map((n) => (
                    <div key={n.id} className="rounded-xl bg-yellow-300/[0.06] border border-yellow-300/[0.18] p-3">
                      <div className="text-[12px] text-yellow-100/90 leading-relaxed whitespace-pre-wrap">{n.body}</div>
                      <div className="text-[10.5px] text-yellow-200/40 mt-1.5">{n.author || "Agent"} · {noteAgo(n.at)}</div>
                    </div>
                  ))}
                </div>
                {showNoteInput ? (
                  <div className="mt-2 space-y-2">
                    <textarea value={noteDraft} onChange={(e) => setNoteDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); saveNote(); } }} rows={3}
                      placeholder="Internal note (team-only)…" className="w-full px-3 py-2 rounded-xl bg-yellow-300/[0.05] border border-yellow-300/[0.20] text-[12.5px] text-yellow-100/90 placeholder:text-yellow-200/30 outline-none focus:border-yellow-300/40 resize-none"/>
                    <div className="flex items-center gap-2">
                      <button onClick={saveNote} disabled={!noteDraft.trim()} className="flex-1 h-8 rounded-lg bg-yellow-300/20 hover:bg-yellow-300/30 text-yellow-100 text-[12px] font-medium disabled:opacity-40">Save note</button>
                      <button onClick={() => { setShowNoteInput(false); setNoteDraft(""); }} className="h-8 px-3 rounded-lg text-[12px] text-white/55 hover:text-white hover:bg-white/[0.05]">Cancel</button>
                    </div>
                  </div>
                ) : (
                  <button onClick={() => setShowNoteInput(true)} className="mt-2 w-full text-[11.5px] text-white/55 hover:text-white border border-dashed border-white/15 rounded-xl py-2">+ Add note</button>
                )}
              </div>

              <div>
                <div className="text-[10.5px] uppercase tracking-[0.1em] text-white/40 mb-2">Assigned to</div>
                <div className="flex items-center justify-between">
                  {assignedAgent ? (
                    <div className="flex items-center gap-2">
                      <VAvatar name={assignedAgent.name} color={assignedAgent.color} size={28} status={assignedAgent.status}/>
                      <div><div className="text-[12.5px] text-white/95">{assignedAgent.name}</div><div className="text-[10.5px] text-white/45">{assignedAgent.role}</div></div>
                    </div>
                  ) : (
                    <div className="text-[12px] text-white/35 italic">Unassigned</div>
                  )}
                  <button onClick={() => setAssignOpen(true)} className="text-[11px] text-lime hover:underline">
                    {assignedAgent ? "Reassign" : "Assign"}
                  </button>
                </div>
              </div>

              <div className="space-y-2 pt-4 border-t border-white/[0.05]">
                <VBtn variant="ghost" onClick={() => setAssignOpen(true)} className="w-full justify-center"><Icon.Users size={14}/> Assign agent</VBtn>
                <VBtn variant="danger" onClick={closeChat} className="w-full justify-center"><Icon.Close size={14}/> Close chat</VBtn>
              </div>
            </div>
          )}
        </aside>
      </div>

      {/* Assign agent modal */}
      {assignOpen && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" onClick={() => setAssignOpen(false)}>
          <div className="glass-strong rounded-2xl p-5 w-80 space-y-2" onClick={e => e.stopPropagation()}>
            <div className="text-[14px] font-medium text-white/95 mb-3">Assign conversation</div>
            {(window.Mock.agents || []).map(a => (
              <button key={a.id} onClick={() => doAssign(a.id)}
                className={`w-full flex items-center gap-3 px-3 py-2 rounded-xl border transition ${active && active.assignee === a.id ? "border-lime/40 bg-lime/10" : "border-transparent hover:bg-white/[0.05]"}`}>
                <VAvatar name={a.name} color={a.color} size={28} status={a.status}/>
                <div className="flex-1 text-left">
                  <div className="text-[13px] text-white/95">{a.name}</div>
                  <div className="text-[11px] text-white/45">{a.role} · {a.status}</div>
                </div>
                {active && active.assignee === a.id && <span className="text-lime text-[10.5px]">current</span>}
              </button>
            ))}
            <button onClick={() => setAssignOpen(false)} className="w-full text-[12px] text-white/50 hover:text-white pt-2">Cancel</button>
          </div>
        </div>
      )}
    </div>
  );
}

function DetailRow({ label, value }) {
  return (
    <div className="flex items-start justify-between gap-3 text-[12.5px]">
      <span className="text-white/45 shrink-0">{label}</span>
      <span className="text-white/85 text-right">{value}</span>
    </div>
  );
}

window.Conversations = Conversations;
