// V2 Composer — segmented control variant adapted from compose_v3.jsx
// Replaces the interaction container at the bottom of the threads view when
// the "Compose box" tweak is set to V2.

(function () {
  const { useState, useEffect, useRef } = React;

  // ─── Icons ───
  const PhoneIcon = ({ size = 14, color = "currentColor" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" /></svg>;

  const MsgIcon = ({ size = 14, color = "currentColor" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>;

  const WhatsAppIcon = ({ size = 14, color = "currentColor" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill={color}><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413z" /></svg>;

  const NoteIcon = ({ size = 14, color = "currentColor" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><path d="M14 2v6h6M16 13H8M16 17H8" /></svg>;

  const ClipIcon = ({ size = 14 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" /></svg>;

  const EmojiIcon = ({ size = 14 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>;

  const SparkIcon = ({ size = 14, color = "#534AB7" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none"><path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8L12 2z" fill={color} /></svg>;

  const ChevDown = ({ size = 10 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M6 9l6 6 6-6" /></svg>;

  const ChevUp = ({ size = 10 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M6 15l6-6 6 6" /></svg>;

  const CheckIcon = ({ size = 14 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="#534AB7" strokeWidth="2.5"><path d="M20 6L9 17l-5-5" /></svg>;

  // currentColor, like ChevDown/ChevUp — so it greys out with the button's
  // label rather than staying white on the disabled fill.
  const SendIcon = () =>
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M22 2L11 13" /><path d="M22 2l-7 20-4-9-9-4 20-7z" /></svg>;

  const CalIcon = ({ size = 14, color = "currentColor" }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2"><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></svg>;


  // ─── Lines — sourced from the app's PHONE_LINES with sms-capability flags ───
  const buildLines = () => {
    const src = window.PHONE_LINES || [];
    // Mark which lines are SMS-capable. Default: all support SMS except the
    // ring-group fallback.
    return src.map((l) => ({
      id: l.id,
      label: l.name,
      number: l.phone,
      flag: l.flag,
      sms: l.id !== "l_default",
      // WhatsApp is a per-number registration, not something every SMS line has.
      wa: !!l.whatsapp
    }));
  };

  const C = {
    accent: "#534AB7", accentLight: "#f7f5fc", accentBorder: "#d5d2e8",
    green: "#1D9E75", greenHover: "#178a67",
    border: "#e4e1ee", borderLight: "#f0edf8",
    muted: "#888", faint: "#bbb", ink: "#1a1230",
    smsBg: "#EEF6FF", smsBorder: "#C8DFF7", smsInk: "#0C447C", smsBtn: "#185FA5"
  };

  const modesConfig = [
  { id: "call", label: "Call", Icon: PhoneIcon, color: C.green },
  { id: "sms", label: "SMS", Icon: MsgIcon, color: C.accent },
  { id: "whatsapp", label: "WhatsApp", Icon: WhatsAppIcon, color: "#25D366" },
  { id: "note", label: "Note", Icon: NoteIcon, color: C.accent }];


  const iconW = 32;
  const activeWidths = { call: 70, sms: 66, whatsapp: 100, note: 68 };
  const segGap = 2;

  function useOutsideClick(ref, handler) {
    useEffect(() => {
      const fn = (e) => {if (ref.current && !ref.current.contains(e.target)) handler();};
      document.addEventListener("mousedown", fn);
      return () => document.removeEventListener("mousedown", fn);
    }, [ref, handler]);
  }

  const toolBtn = {
    width: 28, height: 28, borderRadius: "50%", display: "flex",
    alignItems: "center", justifyContent: "center", cursor: "pointer",
    border: `1px solid ${C.border}`, background: "#fff", color: "#888"
  };

  // The one line wired to the "past WhatsApp's 24-hour window" state. Real
  // products derive this from the last inbound message's timestamp; pinning it
  // to a line keeps the edge case reachable in a prototype with fixed data.
  const WA_GATED_LINE = "l_support_us";

  // Replaces the input and its toolbar once the window has closed. Same heading
  // and description pair as the block it mirrors, with the action directly
  // underneath and left-aligned.
  // Sits directly in the composer's own bordered card — no second box. A panel
  // inside a panel drew a frame around the frame for no extra meaning.
  const WaTemplateGate = ({ onUseTemplate }) =>
  <div style={{ padding: "16px 18px 18px" }}>
      <div style={{ fontSize: 14, fontWeight: 600, color: "#101828", lineHeight: 1.4 }}>
        You’ve reached WhatsApp’s 24 hour reply limit
      </div>
      <div style={{ fontSize: 13, color: "#667085", lineHeight: 1.5, marginTop: 4 }}>
        To continue, you’ll need to send a pre-approved message template. After the
        customer replies, you’ll be able to continue the conversation.
      </div>
      <button
      onClick={onUseTemplate}
      style={{
        display: "inline-flex", alignItems: "center", gap: 7, marginTop: 14,
        height: 36, padding: "0 14px", borderRadius: 8, border: "none",
        background: "#004CE6", color: "#FFFFFF",
        fontSize: 13, fontWeight: 600, fontFamily: "inherit", cursor: "pointer"
      }}>
        <TemplateIcon size={15} /> Use Template
      </button>
    </div>;


  const TemplateIcon = ({ size = 14 }) =>
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="#fff"
  strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <rect x="3" y="4" width="18" height="16" rx="2" />
      <path d="M3 9h6v11M13 9h5M13 13h5" />
    </svg>;


  // ─── Line Dropdown ───
  function LineDropdown({ displayLines, greyedLines, selectedLine, onSelect, channel = "all" }) {
    const isWa = channel === "whatsapp";
    const heading = isWa ? "WHATSAPP-CAPABLE LINES" : greyedLines ? "SMS-CAPABLE LINES" : "ALL LINES";
    return (
      // Capped at 240px — roughly 60% of the height the full roster wants — so the
      // switcher stops swallowing the thread behind it. The heading and the
      // "Need SMS?" CTA stay put; only the lines themselves scroll.
      <div style={{
        position: "absolute", bottom: "calc(100% + 6px)", right: 0, zIndex: 20,
        background: "#fff", border: `1px solid ${C.border}`, borderRadius: 10,
        boxShadow: "0 -4px 24px rgba(0,0,0,0.10)", width: 260, overflow: "hidden",
        maxHeight: 240, display: "flex", flexDirection: "column"
      }}>
        <div style={{ padding: "8px 12px 4px", fontSize: 10, fontWeight: 600, letterSpacing: "0.5px", color: C.muted, flex: "none" }}>
          {heading}
        </div>
        <div style={{ overflowY: "auto", minHeight: 0, flex: 1 }}>
        {displayLines.map((l) =>
        <div key={l.id} onClick={() => onSelect(l.id)} style={{
          display: "flex", alignItems: "center", gap: 8,
          padding: "9px 12px", cursor: "pointer", fontSize: 12,
          background: l.id === selectedLine ? C.accentLight : "transparent",
          borderBottom: `1px solid ${C.borderLight}`
        }}>
            <span style={{ fontSize: 16 }}>{l.flag}</span>
            <div style={{ flex: 1 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 5, fontWeight: 500 }}>
                {l.label}
                {isWa ?
                <span style={{ fontSize: 9, padding: "1px 5px", borderRadius: 3, background: "#E1F5EE", color: "#085041" }}>WhatsApp</span> :
                l.sms ? <span style={{ fontSize: 9, padding: "1px 5px", borderRadius: 3, background: "#E1F5EE", color: "#085041" }}>SMS</span> : <span style={{ fontSize: 9, padding: "1px 5px", borderRadius: 3, background: "#f0eee6", color: "#8a8175" }}>Voice only</span>}
              </div>
              <div style={{ fontSize: 10, color: "#999", marginTop: 1 }}>{l.number}</div>
            </div>
            {l.id === selectedLine && <CheckIcon size={12} />}
          </div>
        )}
        {greyedLines && greyedLines.length > 0 &&
        <React.Fragment>
            <div style={{ padding: "7px 12px 3px", fontSize: 9, fontWeight: 600, letterSpacing: "0.4px", color: C.faint, borderTop: `1px solid ${C.border}` }}>{isWa ? "NOT ON WHATSAPP" : "VOICE ONLY"}</div>
            {greyedLines.map((l) =>
          <div key={l.id} style={{ display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", fontSize: 12, opacity: 0.35, cursor: "not-allowed", borderBottom: `1px solid ${C.borderLight}` }}>
                <span style={{ fontSize: 16 }}>{l.flag}</span>
                <div style={{ flex: 1 }}><div style={{ fontWeight: 500 }}>{l.label}</div><div style={{ fontSize: 10, color: "#999" }}>{l.number}</div></div>
              </div>
          )}
          </React.Fragment>
        }
        </div>
        {greyedLines && greyedLines.length > 0 &&
        <div style={{ padding: "7px 12px 8px", display: "flex", alignItems: "center", gap: 5, background: C.smsBg, fontSize: 10, flex: "none" }}>
            <MsgIcon size={11} color={C.smsBtn} /><span style={{ color: C.smsInk }}>{isWa ? "Need WhatsApp?" : "Need SMS?"}</span>
            <button style={{ background: C.smsBtn, color: "#fff", border: "none", borderRadius: 4, padding: "2px 8px", fontSize: 9, fontWeight: 500, cursor: "pointer", marginLeft: "auto" }}>Enable</button>
          </div>
        }
      </div>);

  }

  // ─── Linkified composer input ───
  // A contentEditable that renders detected URLs in primary blue with a dotted
  // underline on hover. Behaves like a plain textarea to the caller.
  const URL_RE = /((?:https?:\/\/|www\.)[^\s<>"']+|[a-z0-9][a-z0-9-]*(?:\.[a-z0-9-]+)*\.(?:com|net|org|io|co|ai|dev|app|me|in|us|uk|xyz|link|site)(?:\/[^\s<>"']*)?)/gi;

  if (!document.getElementById("composer-linkify-css")) {
    const st = document.createElement("style");
    st.id = "composer-linkify-css";
    st.textContent =
    ".cmp-input{white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}" +
    ".cmp-input:empty:before{content:attr(data-placeholder);color:#98A2B3;pointer-events:none}" +
    ".cmp-input .cmp-url{color:#004CE6;text-decoration:none;border-bottom:1px dotted transparent}" +
    ".cmp-input .cmp-url:hover{border-bottom-color:#004CE6;cursor:text}";
    document.head.appendChild(st);
  }

  function escapeHTML(s) {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  }

  function linkifyHTML(text) {
    URL_RE.lastIndex = 0;
    let out = "",last = 0,m;
    while (m = URL_RE.exec(text)) {
      out += escapeHTML(text.slice(last, m.index));
      out += '<span class="cmp-url">' + escapeHTML(m[0]) + "</span>";
      last = m.index + m[0].length;
    }
    out += escapeHTML(text.slice(last));
    return out;
  }

  function caretOffset(el) {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0) return null;
    const r = sel.getRangeAt(0);
    if (!el.contains(r.startContainer)) return null;
    const pre = r.cloneRange();
    pre.selectNodeContents(el);
    pre.setEnd(r.startContainer, r.startOffset);
    return pre.toString().length;
  }

  function placeCaret(el, offset) {
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
    let n,count = 0,range = document.createRange();
    while (n = walker.nextNode()) {
      const len = n.nodeValue.length;
      if (count + len >= offset) {
        range.setStart(n, Math.max(0, offset - count));
        range.collapse(true);
        const sel = window.getSelection();
        sel.removeAllRanges();sel.addRange(range);
        return;
      }
      count += len;
    }
    range.selectNodeContents(el);
    range.collapse(false);
    const sel = window.getSelection();
    sel.removeAllRanges();sel.addRange(range);
  }

  function LinkifyInput({ value, onChange, placeholder, minHeight = 56, style }) {
    const ref = useRef(null);
    useEffect(() => {
      const el = ref.current;
      if (!el) return;
      if (el.dataset.hl === value) return;
      const focused = document.activeElement === el;
      const off = focused ? caretOffset(el) : null;
      el.innerHTML = value ? linkifyHTML(value) : "";
      el.dataset.hl = value;
      if (off != null) placeCaret(el, Math.min(off, value.length));
    }, [value]);

    return (
      <div
        ref={ref}
        className="cmp-input"
        contentEditable="plaintext-only"
        suppressContentEditableWarning={true}
        data-placeholder={placeholder}
        onInput={(e) => onChange(e.currentTarget.textContent)}
        onPaste={(e) => {
          e.preventDefault();
          const t = (e.clipboardData || window.clipboardData).getData("text/plain");
          document.execCommand("insertText", false, t);
        }}
        style={{
          width: "100%", border: "none", outline: "none",
          fontFamily: "inherit", fontSize: 13, lineHeight: 1.5, color: "#333",
          background: "transparent", minHeight, ...style
        }} />);

  }

  // ═══════════════════════════════════════════════
  // V2 — SEGMENTED CONTROL COMPOSER
  // ═══════════════════════════════════════════════
  // `composeMode` runs the composer for a conversation that doesn't exist yet:
  // Note is off the table until there's a thread to attach it to, interactions
  // are blocked until `canInteract`, and the first Call/SMS/WhatsApp is handed
  // to `onFirstInteraction` instead of being appended to a thread.
  /* `omitModes` drops modes the host has no business offering. The campaign
     queue's thread column is the first: the agent is already on the call it
     would start, and a Call tab there would offer to place a second one. The
     mode the composer opens on is whichever is left first, so removing Call
     lands it on SMS rather than on a tab that is not there. */
  function ComposerV2({ thread, permission, persona, composeMode = false, canInteract = true, onFirstInteraction, omitModes = [] }) {
    const lines = buildLines();
    const initialLine = (() => {
      if (thread && thread.line) {
        const match = lines.find((l) => l.label === thread.line);
        if (match) return match.id;
      }
      const smsCapable = lines.find((l) => l.sms);
      return smsCapable ? smsCapable.id : lines[0] && lines[0].id;
    })();

    // Only one call can be up at a time — app/call-experience.js owns that and
    // says so on cx:active / cx:idle. Starting a second would silently replace
    // the first, so Start Call closes while one is running.
    const [callUp, setCallUp] = useState(!!window.__cxCallActive);
    useEffect(() => {
      const on = () => setCallUp(true);
      const off = () => setCallUp(false);
      window.addEventListener("cx:active", on);
      window.addEventListener("cx:idle", off);
      return () => {
        window.removeEventListener("cx:active", on);
        window.removeEventListener("cx:idle", off);
      };
    }, []);

    const modes = modesConfig.filter((m) => omitModes.indexOf(m.id) < 0);
    const firstMode = (modes[0] && modes[0].id) || "sms";
    const [mode, setMode] = useState(firstMode);
    const [selectedLine, setSelectedLine] = useState(initialLine);
    const [lineDropOpen, setLineDropOpen] = useState(false);
    const [scheduleOpen, setScheduleOpen] = useState(false);
    // "Pick a time..." swaps the preset list for a datetime field in place.
    const [pickerOpen, setPickerOpen] = useState(false);
    const [pickerValue, setPickerValue] = useState("");
    const [msgText, setMsgText] = useState("");
    const [noteText, setNoteText] = useState("");
    const [attachment, setAttachment] = useState(null);
    // Call-mode tip strip visibility — driven by the Tweaks panel (default:
    // hidden, so Call mode stays focused on the Start Call action; a host can
    // bring it back with __TWEAKS.showCallTip = true).
    const readTipTweak = () => !!(window.__TWEAKS && window.__TWEAKS.showCallTip === true);
    const [showCallTip, setShowCallTip] = useState(readTipTweak);
    useEffect(() => {
      const h = () => setShowCallTip(readTipTweak());
      window.addEventListener("convo:tweaksChanged", h);
      return () => window.removeEventListener("convo:tweaksChanged", h);
    }, []);

    const lineRef = useRef(null);
    const schedRef = useRef(null);
    useOutsideClick(lineRef, () => setLineDropOpen(false));
    useOutsideClick(schedRef, () => {setScheduleOpen(false);setPickerOpen(false);});

    // Reset draft state when thread changes
    useEffect(() => {
      setMsgText("");
      setNoteText("");
      setAttachment(null);
      setMode(firstMode);
      setLineDropOpen(false);
      setScheduleOpen(false);
      setPickerOpen(false);
      const next = (() => {
        if (thread && thread.line) {
          const match = lines.find((l) => l.label === thread.line);
          if (match) return match.id;
        }
        const smsCapable = lines.find((l) => l.sms);
        return smsCapable ? smsCapable.id : lines[0] && lines[0].id;
      })();
      setSelectedLine(next);
    }, [thread && thread.id]);

    // The new-conversation modal asks for a channel up front ("Send SMS"), so
    // the composer opens on that mode rather than its Call default.
    useEffect(() => {
      const onMode = (e) => {
        const m = e.detail && e.detail.mode;
        if (!m || m === "note") return;
        setMode(m);
        setLineDropOpen(false);
        setScheduleOpen(false);
        setPickerOpen(false);
      };
      window.addEventListener("convo:composeMode", onMode);
      return () => window.removeEventListener("convo:composeMode", onMode);
    }, []);

    // Listen for AI-driven draft injections (same contract V1 honors)
    useEffect(() => {
      const onDraft = (e) => {
        const { text: draftText, channel, attachment: att } = e.detail || {};
        if (!draftText) return;
        if (channel === "note") {
          setMode("note");
          setNoteText(draftText);
        } else {
          setMode(channel === "whatsapp" ? "whatsapp" : "sms");
          setMsgText(draftText);
          setAttachment(att || null);
        }
        if (window.showAppToast) window.showAppToast("Draft ready — review before sending");
      };
      window.addEventListener("convo:draft", onDraft);
      return () => window.removeEventListener("convo:draft", onDraft);
    }, []);

    const canEdit = permission === "full";

    if (!canEdit) {
      return (
        <div style={{
          padding: 14, borderTop: "1px solid #E4E7EC", background: "#F9FAFB",
          display: "flex", alignItems: "center", gap: 8, justifyContent: "center", color: "#667085", fontSize: 13
        }}>
          <span style={{ display: "inline-flex" }}>🔒</span> You have read-only access to this conversation
        </div>);

    }

    const line = lines.find((l) => l.id === selectedLine) || lines[0];
    const smsLines = lines.filter((l) => l.sms);
    const voiceOnly = lines.filter((l) => !l.sms);
    const waLines = lines.filter((l) => l.wa);
    const notWaLines = lines.filter((l) => !l.wa);
    const isWa = mode === "whatsapp";
    const isMsg = mode === "sms" || isWa;
    const currentMode = modes.find((m) => m.id === mode);

    // Outside WhatsApp's 24-hour reply window the composer can't send free text
    // — only a template Meta has approved. The prototype pins that state to one
    // line (Support — US) so it can be demonstrated on demand rather than
    // depending on a real clock.
    const waGated = isWa && selectedLine === WA_GATED_LINE;
    const [tplOpen, setTplOpen] = useState(false);
    // Leaving WhatsApp (or switching to an ungated line) closes the picker with it.
    useEffect(() => {if (!waGated) setTplOpen(false);}, [waGated]);

    // Send needs somewhere to send to *and* something to say — a recipient
    // alone shouldn't light the button up.
    const canSend = canInteract && msgText.trim().length > 0 && !waGated;
    const canDial = canInteract && !callUp;
    // Which lines the picker offers for the current mode, and which it greys out.
    const pickerLines = isWa ? waLines : isMsg ? smsLines : lines;
    const pickerGreyed = isWa ? notWaLines : isMsg ? voiceOnly : null;

    // Keep the selected line valid for the mode — WhatsApp only runs on the
    // numbers registered for it, which is a narrower set than SMS.
    useEffect(() => {
      if (!line) return;
      if (isWa && !line.wa) {
        const f = waLines[0];
        if (f) setSelectedLine(f.id);
      } else if (isMsg && !isWa && !line.sms) {
        const f = smsLines[0];
        if (f) setSelectedLine(f.id);
      }
    }, [mode]);

    const getPillLeft = () => {
      let left = 3;
      for (const m of modes) {
        if (m.id === mode) break;
        left += iconW + segGap;
      }
      return left;
    };

    const bodyH = 130;

    const fmtClock = (d) => {
      let h = d.getHours();
      const m = String(d.getMinutes()).padStart(2, "0");
      const ampm = h >= 12 ? "PM" : "AM";
      h = h % 12 || 12;
      return `${h}:${m} ${ampm}`;
    };
    const fmtTime = () => fmtClock(new Date());
    // Delivery stamp for a scheduled send. Today needs no date qualifier;
    // anything further out does, or "9:00 AM" on its own reads as today.
    const SCHED_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    const SCHED_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    // Header stamp for the scheduled bubble — the full "when", spelled out the
    // way the strip shows it: "Mon, 5 May · 7:52 pm".
    const fmtScheduleStamp = (d) =>
    `${SCHED_DAYS[d.getDay()]}, ${d.getDate()} ${SCHED_MONTHS[d.getMonth()]} · ${fmtClock(d).toLowerCase()}`;
    const fmtDelivery = (d) => {
      const now = new Date();
      const tomorrow = new Date(now);
      tomorrow.setDate(now.getDate() + 1);
      const clock = fmtClock(d);
      if (d.toDateString() === now.toDateString()) return clock;
      if (d.toDateString() === tomorrow.toDateString()) return `Tomorrow, ${clock}`;
      return `${SCHED_MONTHS[d.getMonth()]} ${d.getDate()}, ${clock}`;
    };
    // Menu label -> the moment the message actually goes out.
    const SCHEDULE_PRESETS = [
    { label: "In 30 minutes", at: () => {const d = new Date();d.setMinutes(d.getMinutes() + 30);return d;} },
    { label: "In 1 hour", at: () => {const d = new Date();d.setHours(d.getHours() + 1);return d;} },
    { label: "Tomorrow 9:00 AM", at: () => {const d = new Date();d.setDate(d.getDate() + 1);d.setHours(9, 0, 0, 0);return d;} }];

    const appendMessage = (msg) => {
      // In compose mode there is no thread yet — hand the message up so the
      // conversation can be created around it.
      if (composeMode) {
        if (onFirstInteraction) onFirstInteraction(msg, line);
        return;
      }
      window.dispatchEvent(new CustomEvent("convo:appendMessage", {
        detail: { threadId: thread && thread.id, message: msg }
      }));
    };
    const sendMessage = () => {
      if (!msgText.trim() || !canInteract) return;
      appendMessage({
        kind: "sms",
        dir: "out",
        text: msgText.trim(),
        attachment: attachment,
        time: fmtTime(),
        author: persona === "admin" ? "You" : "You",
        channel: mode // "sms" | "whatsapp"
      });
      setMsgText("");
      setAttachment(null);
      if (window.showAppToast) window.showAppToast(mode === "whatsapp" ? "WhatsApp sent" : "SMS sent");
    };
    // Scheduling takes the draft out of the composer the same way sending does
    // — it is queued to go out, not still waiting to be written — and drops a
    // bubble in the thread stamped with when it will be delivered, not now.
    // SMS/MMS only: WhatsApp has no schedule affordance.
    const scheduleMessage = (at) => {
      if (!canSend || isWa) return;
      const deliverAt = fmtDelivery(at);
      setScheduleOpen(false);
      setPickerOpen(false);
      appendMessage({
        kind: "sms",
        dir: "out",
        text: msgText.trim(),
        attachment: attachment,
        // The bubble's stamp is the delivery time, not the send time. The
        // header strip carries the full date; the footer just the clock.
        time: fmtClock(at).toLowerCase(),
        scheduled: true,
        source: { type: "scheduled", name: fmtScheduleStamp(at) },
        author: "You",
        channel: "sms"
      });
      setMsgText("");
      setAttachment(null);
      window.dispatchEvent(new CustomEvent("convo:toast", {
        detail: { msg: `SMS scheduled — ${deliverAt}` }
      }));
    };
    // Templates go out as a normal outbound WhatsApp bubble. Placeholders are
    // resolved against the contact the same way the real send would.
    const sendTemplate = (tpl) => {
      // {{1}} name, {{2}} who they're seeing, {{3}} when — the three the
      // seeded templates use. Anything else is left visible rather than
      // silently blanked, so an unfilled variable is obvious.
      const fill = (s) => s.
      replace(/\{\{1\}\}/g, thread && thread.name ? String(thread.name).split(" ")[0] : "there").
      replace(/\{\{2\}\}/g, "your mentor").
      replace(/\{\{3\}\}/g, "Thursday at 5:30 pm");
      appendMessage({
        kind: "sms",
        dir: "out",
        text: [tpl.header, fill(tpl.body)].filter(Boolean).join("\n"),
        time: fmtTime(),
        author: "You",
        channel: "whatsapp"
      });
      setTplOpen(false);
      window.dispatchEvent(new CustomEvent("convo:toast", { detail: { msg: `Template “${tpl.name}” sent` } }));
    };
    const saveNote = () => {
      if (!noteText.trim()) return;
      const userName = window.CURRENT_USERS && window.CURRENT_USERS[persona] && window.CURRENT_USERS[persona].name || "You";
      appendMessage({
        kind: "note",
        dir: "out",
        text: noteText.trim(),
        time: fmtTime(),
        author: userName
      });
      setNoteText("");
      if (window.showAppToast) window.showAppToast("Internal note saved");
    };
    const startCall = () => {
      if (!canInteract || window.__cxCallActive) return;
      const card = {
        kind: "call",
        dir: "out",
        status: "connected",
        duration: "00:00",
        time: fmtTime(),
        author: window.CURRENT_USERS && window.CURRENT_USERS[persona] && window.CURRENT_USERS[persona].name || "You",
        summary: "Call started from composer."
      };
      // Compose mode: the thread has to exist before the dialer can open on it.
      if (composeMode) {
        appendMessage(card);
        return;
      }
      // Trigger the floating dummy dialer (call-widget.jsx listens for this)
      if (window.startCall) {
        window.startCall(thread);
      } else {
        window.dispatchEvent(new CustomEvent("convo:call", { detail: { thread } }));
      }
      /* No card appended here. This used to add `card` "for continuity",
         because the thread had no other sign that a call was going out. The
         thread draws the live call itself now — the ongoing card in
         convo-app.jsx — so appending this one would show the same call twice,
         and the copy of it that is a finished 00:00 call is the one that never
         happened. The compose-mode branch above still appends, because there
         is no call to draw: that path has no thread for the dialer to open on
         yet, so the card is all there is. */
    };

    return (
      /* The strip the composer's card sits on. `--cmp-surface` so a host can
         make it continue whatever the thread above it is on — the campaign
         queue sets it to the scroller's own #FAFBFC, where a white band
         across the bottom read as a separate surface. A variable rather than
         an override, because this background is an inline style and only
         !important would beat it from outside. */
      <div style={{
        borderTop: "1px solid #E4E7EC", background: "var(--cmp-surface, #FFFFFF)",
        flexShrink: 0, padding: "14px 20px 16px",
        fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
      }}>
        <div style={{
          background: "#fff", borderRadius: 14, border: `1px solid ${C.border}`,
          overflow: "visible", position: "relative",
          boxShadow: "0 1px 6px rgba(0,0,0,0.04)"
        }}>

          {/* ═══ HEADER ═══ */}
          <div style={{
            display: "flex", alignItems: "center", padding: "8px 12px",
            borderBottom: `1px solid ${C.border}`, gap: 8
          }}>

            {/* Segmented control */}
            <div style={{
              display: "flex", alignItems: "center", position: "relative",
              borderRadius: 10, padding: 3, gap: segGap, background: "rgb(250, 251, 252)"
            }}>
              <div style={{
                position: "absolute", top: 3, height: "calc(100% - 6px)",
                background: "#fff", borderRadius: 8,
                boxShadow: "0 1px 4px rgba(0,0,0,0.08)",
                transition: "left 0.3s cubic-bezier(0.4,0,0.2,1), width 0.3s cubic-bezier(0.4,0,0.2,1)",
                left: getPillLeft(), width: activeWidths[mode]
              }} />

              {modes.map((m) => {
                const isActive = mode === m.id;
                // A note is an annotation on an existing conversation — there's
                // nothing to annotate until the first real interaction happens.
                const isDisabled = composeMode && m.id === "note";
                const tint = isDisabled ? C.faint : isActive ? m.color : C.muted;
                return (
                  <button
                    key={m.id}
                    disabled={isDisabled}
                    onClick={() => {
                      if (isDisabled) return;
                      setMode(m.id);setLineDropOpen(false);setScheduleOpen(false);setPickerOpen(false);
                    }}
                    // Unselected modes collapse to a bare icon — name them on hover.
                    // The selected one already shows its label beside the icon.
                    {...(isActive ? {} : {
                      "data-jc-tip": isDisabled ? "Start with a call or message first" : m.label,
                      "data-jc-tip-above": "",
                      "aria-label": m.label
                    })}
                    style={{
                      position: "relative", zIndex: 1,
                      display: "flex", alignItems: "center", justifyContent: "center", gap: isActive ? 5 : 0,
                      width: isActive ? activeWidths[m.id] : iconW,
                      height: 32, borderRadius: 8, border: "none",
                      background: "transparent",
                      cursor: isDisabled ? "not-allowed" : "pointer",
                      opacity: isDisabled ? 0.45 : 1,
                      fontSize: 12, fontWeight: 500,
                      color: tint,
                      transition: "width 0.3s cubic-bezier(0.4,0,0.2,1), color 0.2s ease",
                      whiteSpace: "nowrap", overflow: "hidden", flexShrink: 0,
                      fontFamily: "inherit"
                    }}>

                    <m.Icon size={14} color={tint} />
                    {isActive && <span style={{ fontSize: "12px" }}>{m.label}</span>}
                  </button>);

              })}
            </div>

            <div style={{ flex: 1 }} />

            {/* Line selector — Call, SMS, WhatsApp */}
            {(mode === "call" || isMsg) && line &&
            <div ref={lineRef} style={{ position: "relative", display: "flex", alignItems: "center" }}>
                <span style={{ color: C.muted, marginRight: 5, fontSize: "12px" }}>From:</span>
                <button onClick={() => {setLineDropOpen(!lineDropOpen);}} style={{
                display: "flex", alignItems: "center", gap: 5, padding: "0 10px", height: 34,
                borderRadius: 8, fontSize: 12, fontWeight: 500,
                border: `1px solid ${C.border}`, background: "#fff", color: C.ink,
                cursor: "pointer", boxSizing: "border-box", fontFamily: "inherit"
              }}>
                  <span style={{ fontSize: 14 }}>{line.flag}</span>{line.label}
                  {lineDropOpen ? <ChevUp /> : <ChevDown />}
                </button>
                {lineDropOpen &&
              <LineDropdown
                displayLines={pickerLines}
                greyedLines={pickerGreyed}
                channel={isWa ? "whatsapp" : isMsg ? "sms" : "all"}
                selectedLine={selectedLine}
                onSelect={(id) => {setSelectedLine(id);setLineDropOpen(false);}} />

              }
              </div>
            }
          </div>

          {/* ═══ BODY — CALL ═══ */}
          <div style={{
            maxHeight: mode === "call" ? bodyH : 0,
            opacity: mode === "call" ? 1 : 0,
            overflow: mode === "call" ? "visible" : "hidden",
            position: "relative",
            pointerEvents: mode === "call" ? "auto" : "none",
            transition: "max-height 0.3s ease, opacity 0.25s ease"
          }}>
            <div style={{ padding: "12px 14px" }} data-comment-anchor="c42b505e16-div-391-13">
              {/* Tip strip + Start Call — inline row, tip fills available space */}
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                {!showCallTip && <div style={{ flex: 1 }} />}
                {showCallTip && <div style={{
                  display: "flex", alignItems: "center", gap: 6,
                  padding: "6px 10px", background: "#FFF9F0", borderRadius: 8, border: "1px solid #F5E6CC",
                  overflow: "hidden", flex: 1, minWidth: 0, height: 32, boxSizing: "border-box"
                }} data-comment-anchor="526d66e994-div-393-15">
                  <SparkIcon size={13} color="#101828" />
                  <span style={{ fontSize: 10, fontWeight: 600, color: "#101828", letterSpacing: "0.3px", flexShrink: 0 }}>TIP</span>
                  <span style={{ fontSize: 12, color: "#6B4D1A", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", flex: 1, minWidth: 0 }}>
                    {(() => {
                      const first = thread && thread.name ? thread.name.split(" ")[0] : "them";
                      const isNew = !!(thread && thread.__isNew);
                      const contextTips = [
                      `Follow up on the showcase tape ${first} mentioned last week — has it gone out yet?`,
                      `${first}'s daughter had a tournament this weekend — ask how it went before getting into logistics.`,
                      `${first} said they'd send the updated roster by Friday — gentle reminder if it's still pending.`,
                      `Ask ${first} how the new training schedule is landing with the team.`,
                      `${first} flagged a scheduling conflict last call — confirm it's resolved before booking the next session.`];
                      // For brand-new conversations (or when there's no prior
                      // context to mine), fall back to a generic behavioural
                      // opener — small talk before getting into business.
                      const genericTips = [
                      `Open with something light — ask how their week is going before getting into the ask.`,
                      `Mention the weather where they are; quick rapport-builder before the main topic.`,
                      `Ask what they're working on this week — it's a low-pressure way to start the thread.`,
                      `Lead with a friendly intro and your name; keep the first message short and human.`,
                      `Find one personal detail to ask about (weekend plans, hometown team) before pitching anything.`];
                      const tips = isNew ? genericTips : contextTips;
                      const idx = thread && thread.id ?
                      Math.abs([...thread.id].reduce((a, c) => a + c.charCodeAt(0), 0)) % tips.length : 0;
                      return `"${tips[idx]}"`;
                    })()}
                  </span>
                </div>}
                {/* Calls are instant only — scheduling lives on the message
                    composer, where a queued send is the useful action. */}
                <button
                  onClick={startCall}
                  disabled={!canDial}
                  title={callUp ? "You're already on a call" : undefined}
                  style={{
                    display: "flex", alignItems: "center", gap: 6, flexShrink: 0,
                    background: canDial ? "#004CE6" : "#EAECF0",
                    color: canDial ? "#fff" : "#98A2B3", border: "none",
                    borderRadius: 8, padding: "0 14px", height: 32,
                    fontSize: 13, fontWeight: 600,
                    cursor: canDial ? "pointer" : "not-allowed",
                    boxShadow: canDial ? "0 1px 3px rgba(0,76,230,0.2)" : "none", fontFamily: "inherit"
                  }}>

                  <PhoneIcon size={14} color={canDial ? "#fff" : "#98A2B3"} />
                  {callUp ? "On a call" : "Start Call"}
                </button>
              </div>
            </div>
          </div>

          {/* ═══ BODY — SMS / WHATSAPP ═══ */}
          <div style={{
            maxHeight: !isMsg ? 0 : waGated ? bodyH + 80 : bodyH + 40 + (attachment ? 60 : 0),
            opacity: isMsg ? 1 : 0,
            // Clipped by default so the body slides open; only opened up while
            // the schedule menu is showing, which needs to escape upward past
            // the Send button.
            overflow: isMsg && scheduleOpen ? "visible" : "hidden",
            transition: "max-height 0.3s ease, opacity 0.25s ease"
          }}>
            {waGated ?
            /* Past the 24-hour window the input and its toolbar are replaced
               wholesale — there is nothing free-text to send. */
            <WaTemplateGate onUseTemplate={() => setTplOpen(true)} /> :
            <>
            <div style={{ padding: "12px 14px 4px" }}>
              <LinkifyInput
                placeholder={`Write a ${currentMode ? currentMode.label : ""} message...`}
                value={msgText}
                onChange={setMsgText} />
              {attachment &&
              <div style={{
                display: "flex", alignItems: "center", gap: 10, marginTop: 4,
                background: "#F9FAFB", border: `1px solid ${C.border}`, borderRadius: 10,
                padding: "8px 10px", width: 250, height: 50, boxSizing: "border-box"
              }} data-comment-anchor="8f01de9b92-div-500-15">
                <span style={{
                  width: 26, height: 32, borderRadius: 4, flexShrink: 0,
                  background: "#FEF3F2", border: "1px solid #FECDCA", color: "#B42318",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  fontSize: 9, fontWeight: 700, letterSpacing: ".04em"
                }}>PDF</span>
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{
                    fontSize: 12, fontWeight: 600, color: "#101828", lineHeight: 1.35,
                    overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"
                  }}>{attachment.name}</div>
                  <div style={{ fontSize: 11, color: C.muted }}>{attachment.size}</div>
                </div>
                <button
                  onClick={() => setAttachment(null)}
                  title="Remove attachment"
                  style={{
                    border: "none", background: "transparent", cursor: "pointer",
                    color: C.muted, fontSize: 15, lineHeight: 1, padding: 4, fontFamily: "inherit"
                  }}>×</button>
              </div>
              }
            </div>
            <div style={{
              display: "flex", alignItems: "center", justifyContent: "space-between",
              padding: "6px 14px 10px", borderTop: `1px solid ${C.borderLight}`
            }}>
              <div style={{ display: "flex", gap: 5 }}>
                {[ClipIcon, EmojiIcon, SparkIcon].map((I, i) =>
                <div key={i} style={toolBtn}><I size={12} /></div>
                )}
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontSize: 10, color: C.faint }}>{msgText.length}/1600</span>
                {/* Send now, or queue it — the caret opens the schedule menu.
                    WhatsApp has no caret: scheduling is SMS/MMS only, so its
                    Send button keeps the plain full-radius shape. Both halves
                    need something to send, so the caret follows canSend. */}
                <div ref={schedRef} style={{ position: "relative", display: "flex", alignItems: "center" }}>
                  <button
                    onClick={sendMessage}
                    disabled={!canSend}
                    style={{
                      display: "flex", alignItems: "center", gap: 5,
                      background: canSend ? "rgb(0, 76, 230)" : "#EAECF0",
                      color: canSend ? "#fff" : "#98A2B3", border: "none",
                      borderRadius: isWa ? 7 : "7px 0 0 7px",
                      padding: "0 14px", height: 32, fontSize: 13, fontWeight: 600,
                      cursor: canSend ? "pointer" : "not-allowed",
                      fontFamily: "inherit"
                    }}>Send <SendIcon /></button>
                  {!isWa &&
                  <button
                    onClick={() => {if (canSend) {setPickerOpen(false);setScheduleOpen(!scheduleOpen);}}}
                    disabled={!canSend}
                    title={canSend ? "Schedule this message" : "Write a message first"}
                    style={{
                      display: "flex", alignItems: "center", justifyContent: "center",
                      background: canSend ? "rgb(0, 76, 230)" : "#EAECF0",
                      color: canSend ? "#fff" : "#98A2B3", border: "none",
                      borderRadius: "0 7px 7px 0", padding: "0 8px", height: 32,
                      cursor: canSend ? "pointer" : "not-allowed",
                      borderLeft: `1px solid ${canSend ? "rgba(255,255,255,0.25)" : "#DDE1E7"}`,
                      fontFamily: "inherit"
                    }}>
                    {scheduleOpen ? <ChevUp size={10} /> : <ChevDown size={10} />}
                  </button>
                  }

                  {scheduleOpen && canSend && !isWa &&
                  <div style={{
                    position: "absolute", bottom: "calc(100% + 6px)", right: 0, zIndex: 20,
                    background: "#fff", border: `1px solid ${C.border}`, borderRadius: 10,
                    boxShadow: "0 -4px 20px rgba(0,0,0,0.10)", width: 200, overflow: "hidden"
                  }}>
                      <div style={{ padding: "8px 12px 4px", fontSize: 10, fontWeight: 600, letterSpacing: "0.5px", color: C.muted }}>SCHEDULE MESSAGE</div>
                      {!pickerOpen ?
                    <>
                      {SCHEDULE_PRESETS.map((preset) =>
                      <div key={preset.label} onClick={() => scheduleMessage(preset.at())} style={{
                        padding: "9px 12px", fontSize: 12, cursor: "pointer",
                        borderBottom: `1px solid ${C.borderLight}`,
                        display: "flex", alignItems: "center", gap: 7
                      }}
                      onMouseEnter={(e) => {e.currentTarget.style.background = "#F9FAFB";}}
                      onMouseLeave={(e) => {e.currentTarget.style.background = "transparent";}}>
                            <CalIcon size={12} color={C.muted} />
                            <span>{preset.label}</span>
                          </div>
                      )}
                      <div onClick={() => setPickerOpen(true)} style={{
                        padding: "9px 12px", fontSize: 12, cursor: "pointer",
                        display: "flex", alignItems: "center", gap: 7
                      }}
                      onMouseEnter={(e) => {e.currentTarget.style.background = "#F9FAFB";}}
                      onMouseLeave={(e) => {e.currentTarget.style.background = "transparent";}}>
                            <CalIcon size={12} color={C.muted} />
                            <span>Pick a time...</span>
                          </div>
                    </> :
                    <div style={{ padding: "4px 12px 12px", display: "flex", flexDirection: "column", gap: 8 }}>
                          <input
                        type="datetime-local"
                        autoFocus
                        value={pickerValue}
                        onChange={(e) => setPickerValue(e.target.value)}
                        style={{
                          width: "100%", boxSizing: "border-box", height: 32,
                          border: `1px solid ${C.border}`, borderRadius: 7, padding: "0 8px",
                          fontSize: 12, fontFamily: "inherit", color: "#101828", background: "#fff"
                        }} />
                          <div style={{ display: "flex", gap: 6 }}>
                            <button
                          onClick={() => setPickerOpen(false)}
                          style={{
                            flex: 1, height: 30, border: `1px solid ${C.border}`, borderRadius: 7,
                            background: "#fff", color: "#344054", fontSize: 12, fontWeight: 600,
                            cursor: "pointer", fontFamily: "inherit"
                          }}>Back</button>
                            <button
                          disabled={!pickerValue}
                          onClick={() => {const d = new Date(pickerValue);if (!isNaN(d)) scheduleMessage(d);}}
                          style={{
                            flex: 1, height: 30, border: "none", borderRadius: 7,
                            background: pickerValue ? "rgb(0, 76, 230)" : "#EAECF0",
                            color: pickerValue ? "#fff" : "#98A2B3", fontSize: 12, fontWeight: 600,
                            cursor: pickerValue ? "pointer" : "not-allowed", fontFamily: "inherit"
                          }}>Schedule</button>
                          </div>
                        </div>
                    }
                    </div>
                  }
                </div>
              </div>
            </div>
            </>
            }
          </div>

          {/* ═══ BODY — NOTE ═══ */}
          <div style={{
            maxHeight: mode === "note" ? bodyH + 20 : 0,
            opacity: mode === "note" ? 1 : 0,
            overflow: "hidden",
            background: "#FFFBEF",
            transition: "max-height 0.3s ease, opacity 0.25s ease"
          }}>
            <div style={{ padding: "12px 14px 4px" }}>
              <LinkifyInput
                placeholder="Write an internal note (visible to your team only)..."
                value={noteText}
                onChange={setNoteText} />
              
            </div>
            <div style={{
              display: "flex", alignItems: "center", justifyContent: "space-between",
              padding: "6px 14px 10px", borderTop: "1px solid #F5E6C8"
            }}>
              <div style={toolBtn}><ClipIcon size={12} /></div>
              <button
                onClick={saveNote}
                style={{
                  background: "#004CE6", color: "#fff", border: "none",
                  borderRadius: 7, padding: "0 14px", height: 32, fontSize: 13, fontWeight: 600, cursor: "pointer",
                  fontFamily: "inherit"
                }}>Save note</button>
            </div>
          </div>

        </div>

        <WaTemplateModal
          open={tplOpen}
          contactName={thread ? thread.name : ""}
          onClose={() => setTplOpen(false)}
          onSend={sendTemplate} />

      </div>);

  }

  window.ComposerV2 = ComposerV2;
})();