// widget.jsx — the real Blessin AI chat widget on blessinai.com, talking to
// the production chatbot backend through the same-origin proxies in /api.
// UI ported from design-system/ui_kits/widget; the canned responder is
// replaced with /api/chat and the escalation form posts real leads.
const BRAND = "#EF6A4B";   // Blessin coral
const EMBER = "#C2492F";   // AA interactive accent
const GREETING = "Hi! I'm Blessin. Ask me anything about Blessin AI.";
const SUGGESTIONS = [
  "How does Blessin learn my business?",
  "What happens when it can't answer?",
  "How do I add Blessin to my site?",
];
// The product widget keys storage on the tenant publicKey; the key lives
// server-side here, so a fixed site-scoped suffix stands in for it.
const STORE_SUFFIX = "blessinai-landing";
const RATE_LIMIT_COOLDOWN_MS = 5000;
const COUNTRY_CODES = ["+1", "+44", "+91", "+61", "+49", "+971", "+65", "+81"];

function getOrCreateVisitorId() {
  const key = "cwdg_visitor_" + STORE_SUFFIX;
  try {
    let id = window.localStorage.getItem(key);
    if (!id || id.length < 8 || id.length > 64) {
      id = window.crypto && window.crypto.randomUUID
        ? window.crypto.randomUUID()
        : "v" + Math.random().toString(36).slice(2) + Date.now().toString(36);
      window.localStorage.setItem(key, id);
    }
    return id;
  } catch {
    return "v" + Math.random().toString(36).slice(2) + Date.now().toString(36);
  }
}

function loadSavedContact() {
  try {
    const raw = window.localStorage.getItem("cwdg_contact_" + STORE_SUFFIX);
    if (!raw) return null;
    const p = JSON.parse(raw);
    if (typeof p.email !== "string") return null;
    return {
      email: p.email,
      name: typeof p.name === "string" ? p.name : "",
      countryCode: typeof p.countryCode === "string" ? p.countryCode : COUNTRY_CODES[0],
      nationalNumber: typeof p.nationalNumber === "string" ? p.nationalNumber : "",
    };
  } catch { return null; }
}

function saveContact(contact) {
  try { window.localStorage.setItem("cwdg_contact_" + STORE_SUFFIX, JSON.stringify(contact)); } catch {}
}

function OwlHead({ size }) {
  return (
    <svg width={size} height={size} viewBox="0 0 130 130" fill="none" aria-hidden="true">
      <path d="M30 40 q-2 -16 13 -11 M100 40 q2 -16 -13 -11" stroke="#EF6A4B" strokeWidth="6" strokeLinecap="round" fill="none"/>
      <ellipse cx="65" cy="72" rx="42" ry="44" fill="#EF6A4B"/>
      <circle cx="49" cy="62" r="14" fill="#fff"/>
      <circle cx="81" cy="62" r="14" fill="#fff"/>
      <circle cx="51" cy="64" r="6" fill="#57281C"/>
      <circle cx="79" cy="64" r="6" fill="#57281C"/>
      <path d="M60 84 l5 7 l5 -7z" fill="#F0A62B"/>
      <path d="M38 100 q27 14 54 0" stroke="#C2492F" strokeWidth="5" strokeLinecap="round" fill="none"/>
    </svg>
  );
}

function OwlFace({ size }) {
  return (
    <svg width={size} height={size} viewBox="0 0 130 130" fill="none" aria-hidden="true">
      <circle cx="49" cy="62" r="15" fill="#fff"/>
      <circle cx="81" cy="62" r="15" fill="#fff"/>
      <circle cx="51" cy="64" r="6.5" fill="#57281C"/>
      <circle cx="79" cy="64" r="6.5" fill="#57281C"/>
      <path d="M59 86 l6 8 l6 -8z" fill="#F0A62B"/>
    </svg>
  );
}

// 3D Sage owl mount — renders the animated mascot when Owl3D is available,
// otherwise keeps the SVG fallback (reduced motion, no WebGL, CDN failure).
function Owl3DMount({ size, state, gesture, fallback }) {
  const ref = React.useRef(null);
  const owlRef = React.useRef(null);
  const [ready, setReady] = React.useState(false);
  React.useEffect(() => {
    let cancelled = false;
    function init() {
      const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      if (reduced) return; // keep the static SVG
      window.Owl3D.create(ref.current, { size }).then((owl) => {
        if (!owl) return;
        if (cancelled) { owl.destroy(); return; }
        owlRef.current = owl;
        setReady(true);
      });
    }
    if (window.Owl3D) init();
    else window.addEventListener("owl3d-ready", init, { once: true });
    return () => {
      cancelled = true;
      window.removeEventListener("owl3d-ready", init);
      if (owlRef.current) { owlRef.current.destroy(); owlRef.current = null; }
    };
  }, []);
  React.useEffect(() => { if (owlRef.current && state) owlRef.current.setState(state); }, [state, ready]);
  React.useEffect(() => { if (owlRef.current && gesture) owlRef.current.play(gesture.name); }, [gesture, ready]);
  return (
    <span ref={ref} style={{ width: size, height: size, display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
      {ready ? null : fallback}
    </span>
  );
}

// Escaping must run before any markup is generated: content comes from the
// live backend and lands in dangerouslySetInnerHTML.
function mdInline(escaped) {
  return escaped
    .replace(/\[([^\]]+)\]\((https?:[^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" style="color:#C2492F;font-weight:500">$1</a>')
    .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
}

function renderMd(text) {
  let src = String(text || "");
  // Some backend replies arrive as a single line with " * " bullet markers;
  // give them real line breaks so the list parser below can pick them up.
  // Delete this once the backend reliably sends newlines.
  if (!src.includes("\n") && (src.match(/ \* /g) || []).length >= 2) {
    src = src.replace(/ \* /g, "\n* ");
  }
  const escaped = src.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  const html = [];
  let list = null; // "ul" | "ol" while inside a list run
  let para = [];
  const closeList = () => { if (list) { html.push(`</${list}>`); list = null; } };
  const closePara = () => { if (para.length) { html.push(`<p>${para.join("<br>")}</p>`); para = []; } };
  for (const line of escaped.split("\n")) {
    const bullet = line.match(/^\s*[*-]\s+(.*)/);
    const numbered = bullet ? null : line.match(/^\s*\d+[.)]\s+(.*)/);
    if (bullet || numbered) {
      closePara();
      const tag = bullet ? "ul" : "ol";
      if (list !== tag) { closeList(); html.push(`<${tag}>`); list = tag; }
      html.push(`<li>${mdInline((bullet || numbered)[1])}</li>`);
    } else if (!line.trim()) {
      closeList(); closePara();
    } else {
      closeList();
      para.push(mdInline(line));
    }
  }
  closeList(); closePara();
  return { __html: html.join("") };
}

function Bubbles({ messages, pending, onEscalationSuccess }) {
  return (
    <div style={{ flex: 1, overflowY: "auto", padding: "12px" }}>
      <ul style={{ display: "flex", flexDirection: "column", gap: 8, listStyle: "none", margin: 0, padding: 0 }}>
        {messages.map((msg) => (
          <li key={msg.id} style={{ display: "flex", flexDirection: "column", alignItems: msg.role === "visitor" ? "flex-end" : "stretch" }}>
            <div style={{
              maxWidth: "85%", borderRadius: "var(--radius-bubble, 1rem)", padding: "8px 12px", fontSize: 14, lineHeight: 1.45,
              background: msg.role === "visitor" ? EMBER : "var(--zinc-100, #f4f4f5)",
              color: msg.role === "visitor" ? "#fff" : "var(--zinc-900, #18181b)",
              alignSelf: msg.role === "visitor" ? "flex-end" : "flex-start",
            }} className="bw-md" dangerouslySetInnerHTML={renderMd(msg.content)} />
            {msg.viewMoreUrl ? (
              <a href={msg.viewMoreUrl} target="_blank" rel="noopener noreferrer" style={{ marginTop: 4, fontSize: 13, fontWeight: 500, color: EMBER, alignSelf: "flex-start" }}>View more →</a>
            ) : null}
            {msg.escalationSlot === "form" ? (
              <EscalationForm
                conversationId={msg.conversationId}
                initialQuestion={msg.initialQuestion}
                onSuccess={() => onEscalationSuccess(msg.id)}
              />
            ) : null}
            {msg.escalationSlot === "success" ? (
              <div style={{ marginTop: 8, background: "var(--widget-success-bg, #ecfdf5)", color: "var(--widget-success-fg, #065f46)", borderRadius: "var(--radius-lg, 12px)", padding: "8px 12px", fontSize: 14, alignSelf: "stretch" }}>
                Thanks! The team has your question and will email you shortly.
              </div>
            ) : null}
          </li>
        ))}
        {pending ? (
          <li style={{ display: "flex" }}>
            <div style={{ display: "flex", gap: 4, alignItems: "center", background: "var(--zinc-100, #f4f4f5)", borderRadius: "var(--radius-bubble, 1rem)", padding: "10px 12px" }}>
              {[0, 1, 2].map((i) => <span key={i} style={{ width: 6, height: 6, borderRadius: 999, background: BRAND, animation: `bw-bounce 1s ${i * 0.12}s infinite` }}></span>)}
            </div>
          </li>
        ) : null}
      </ul>
    </div>
  );
}

function EscalationForm({ conversationId, initialQuestion, onSuccess }) {
  const saved = loadSavedContact();
  const [name, setName] = React.useState(saved ? saved.name : "");
  const [email, setEmail] = React.useState(saved ? saved.email : "");
  const [countryCode, setCountryCode] = React.useState(saved ? saved.countryCode : COUNTRY_CODES[0]);
  const [nationalNumber, setNationalNumber] = React.useState(saved ? saved.nationalNumber : "");
  const [q, setQ] = React.useState(initialQuestion || "");
  const [error, setError] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);

  const inp = { border: "1px solid var(--zinc-300, #d4d4d8)", borderRadius: "var(--radius-md, 8px)", padding: "6px 8px", fontSize: 14, color: "var(--zinc-900, #18181b)", background: "#fff", outline: "none", width: "100%", boxSizing: "border-box", fontFamily: "inherit" };
  const lbl = { fontSize: 12, fontWeight: 500, color: "var(--zinc-600, #52525b)" };

  async function handleSubmit(e) {
    e.preventDefault();
    setError(null);
    const trimmedEmail = email.trim();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) { setError("Please enter a valid email."); return; }
    const digits = nationalNumber.replace(/\D/g, "");
    const phone = digits ? countryCode + digits : undefined;
    if (phone && !/^\+[1-9]\d{6,14}$/.test(phone)) { setError("That phone number doesn't look right."); return; }
    const question = q.trim() || initialQuestion || "";
    setSubmitting(true);
    try {
      let res;
      if (conversationId) {
        res = await fetch("/api/escalate", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            conversationId,
            email: trimmedEmail,
            question,
            ...(name.trim() ? { name: name.trim() } : {}),
            ...(phone ? { phone } : {}),
          }),
        });
      } else {
        // No conversation exists (the chat backend never answered), so the
        // backend can't accept an escalation. Fall back to the landing lead
        // endpoint so the visitor's question is still captured and emailed.
        res = await fetch("/api/lead", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            name: name.trim() || "Chat visitor",
            email: trimmedEmail,
            phone: phone || "",
            need: question,
          }),
        });
      }
      if (!res.ok) {
        setError("Couldn't send that just now. Please try again.");
        setSubmitting(false);
        return;
      }
      saveContact({ email: trimmedEmail, name: name.trim(), countryCode, nationalNumber });
      onSuccess();
    } catch {
      setError("Couldn't send that just now. Please try again.");
      setSubmitting(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} data-testid="bw-escalation-form" style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 8, border: "1px solid var(--zinc-200, #e4e4e7)", background: "#fff", borderRadius: "var(--radius-lg, 12px)", padding: 12, alignSelf: "stretch" }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 3 }}><label style={lbl}>Name (optional)</label><input type="text" value={name} onChange={(e) => setName(e.target.value)} style={inp} /></div>
      <div style={{ display: "flex", flexDirection: "column", gap: 3 }}><label style={lbl}>Email</label><input required type="email" value={email} onChange={(e) => setEmail(e.target.value)} style={inp} /></div>
      <div style={{ display: "flex", flexDirection: "column", gap: 3 }}><label style={lbl}>Phone (optional)</label>
        <div style={{ display: "flex", gap: 4 }}>
          <select value={countryCode} onChange={(e) => setCountryCode(e.target.value)} aria-label="Country code" style={{ ...inp, width: 78 }}>
            {COUNTRY_CODES.map((c) => <option key={c} value={c}>{c}</option>)}
          </select>
          <input type="tel" value={nationalNumber} onChange={(e) => setNationalNumber(e.target.value)} style={inp} />
        </div>
      </div>
      <div style={{ display: "flex", flexDirection: "column", gap: 3 }}><label style={lbl}>Your question</label><textarea rows={2} value={q} onChange={(e) => setQ(e.target.value)} style={{ ...inp, resize: "none" }} /></div>
      {error ? <p role="alert" style={{ margin: 0, fontSize: 12, color: "#dc2626" }}>{error}</p> : null}
      <button type="submit" disabled={submitting} style={{ marginTop: 2, background: EMBER, color: "#fff", border: "none", borderRadius: "var(--radius-md, 8px)", padding: "7px 12px", fontSize: 14, fontWeight: 500, cursor: "pointer", opacity: submitting ? 0.6 : 1 }}>{submitting ? "Sending…" : "Send to the team"}</button>
    </form>
  );
}

function Widget() {
  const [open, setOpen] = React.useState(false);
  const [messages, setMessages] = React.useState([{ id: "m-greeting", role: "bot", content: GREETING }]);
  const [input, setInput] = React.useState("");
  const [pending, setPending] = React.useState(false);
  const [cooldownUntil, setCooldownUntil] = React.useState(0);
  const [, forceTick] = React.useState(0);
  const [inputFocused, setInputFocused] = React.useState(false);
  const [asleep, setAsleep] = React.useState(false);
  const [gesture, setGesture] = React.useState(null);
  const endRef = React.useRef(null);
  const nid = React.useRef(0);
  const gid = React.useRef(0);
  const visitorIdRef = React.useRef("");
  const conversationIdRef = React.useRef(null);
  const newId = () => `m${nid.current++}`;
  const owlPlay = (name) => setGesture({ name, n: gid.current++ });

  React.useEffect(() => { visitorIdRef.current = getOrCreateVisitorId(); }, []);

  const coolingDown = cooldownUntil > Date.now();
  // Owl mood follows the conversation state.
  const owlState = pending ? "thinking" : asleep ? "sleeping" : (inputFocused || input) ? "listening" : "idle";

  // Doze off after a quiet minute (tunable for tests via ?owlSleepMs=…); any activity wakes the owl.
  React.useEffect(() => {
    setAsleep(false);
    const ms = parseInt(new URLSearchParams(window.location.search).get("owlSleepMs"), 10) || 60000;
    const t = setTimeout(() => setAsleep(true), ms);
    return () => clearTimeout(t);
  }, [messages, input, open, inputFocused, pending]);

  // Re-render once the rate-limit cooldown lapses so the input re-enables.
  React.useEffect(() => {
    if (!coolingDown) return;
    const t = setTimeout(() => forceTick((n) => n + 1), cooldownUntil - Date.now() + 50);
    return () => clearTimeout(t);
  }, [cooldownUntil, coolingDown]);

  // Closed launcher: occasionally point at itself as an attractor.
  React.useEffect(() => {
    if (open) return;
    const t = setInterval(() => owlPlay("point"), 20000);
    return () => clearInterval(t);
  }, [open]);

  React.useEffect(() => { endRef.current && endRef.current.scrollIntoView({ behavior: "smooth" }); }, [messages, pending]);

  function handleOpen() {
    setOpen(true);
    owlPlay("wave");
  }

  async function send(text) {
    const t = text.trim();
    if (!t || pending || coolingDown) { if (!t) owlPlay("confused"); return; }
    setMessages((p) => [...p, { id: newId(), role: "visitor", content: t }]);
    setInput("");
    setPending(true);

    let data = null;
    let status = 0;
    try {
      const res = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          visitorId: visitorIdRef.current,
          message: t.slice(0, 1000),
          pageUrl: window.location.href,
        }),
      });
      status = res.status;
      data = await res.json().catch(() => null);
    } catch {
      data = null;
    }
    setPending(false);

    if (status === 429) {
      setCooldownUntil(Date.now() + RATE_LIMIT_COOLDOWN_MS);
      owlPlay("confused");
      setMessages((p) => [...p, {
        id: newId(), role: "bot",
        content: (data && data.content) || "You're sending messages faster than I can keep up. Give me a moment, then try again.",
      }]);
      return;
    }

    if (!data || !data.kind || (status !== 200 && data.kind !== "error_offer")) {
      // Network failure or an unexpected upstream response: same treatment
      // as a backend error_offer — apologize and offer the form.
      owlPlay("confused");
      setMessages((p) => [...p, {
        id: newId(), role: "bot",
        content: "I'm having a little trouble on my end right now. Leave your details and the team will follow up with you directly.",
        escalationSlot: "form", initialQuestion: t, conversationId: conversationIdRef.current,
      }]);
      return;
    }

    if (data.conversationId) conversationIdRef.current = data.conversationId;

    const needsForm = data.kind === "escalation_offer" || data.kind === "error_offer";
    if (needsForm) owlPlay("notes");
    else if (data.kind === "smalltalk" && /thank/i.test(t)) owlPlay("highfive");
    else if (data.kind === "redirect") owlPlay("confused");
    else owlPlay("nod");

    setMessages((p) => [...p, {
      id: newId(), role: "bot",
      content: data.content || "",
      viewMoreUrl: data.kind === "product_results" ? data.viewMoreUrl : undefined,
      escalationSlot: needsForm ? "form" : undefined,
      initialQuestion: t,
      conversationId: conversationIdRef.current,
    }]);
  }

  function onEscalationSuccess(id) {
    owlPlay("celebrate");
    setMessages((p) => p.map((m) => (m.id === id ? { ...m, escalationSlot: "success" } : m)));
  }

  if (!open) {
    return (
      <button onClick={handleOpen} aria-label="Chat with Blessin" data-testid="bw-launcher" className="bw-launcher" style={{ width: 56, height: 56, borderRadius: 999, border: "none", background: BRAND, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", boxShadow: "var(--shadow-lg, 0 10px 15px -3px rgba(0,0,0,.2))" }}>
        <Owl3DMount size={44} state="idle" gesture={gesture} fallback={<OwlFace size={38} />} />
      </button>
    );
  }

  return (
    <div className="bw-panel" data-testid="bw-panel" style={{ display: "flex", flexDirection: "column", overflow: "hidden", background: "#fff", boxShadow: "var(--shadow-xl, 0 20px 25px -5px rgba(0,0,0,.25))" }}>
      <header style={{ display: "flex", alignItems: "center", gap: 8, padding: "12px 16px", color: "#fff", background: BRAND }}>
        <div style={{ width: 34, height: 34, borderRadius: 999, background: "rgba(255,255,255,.92)", display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden" }}><Owl3DMount size={30} state={owlState} gesture={gesture} fallback={<OwlHead size={26} />} /></div>
        <div style={{ flex: 1, display: "flex", flexDirection: "column", lineHeight: 1.15 }}>
          <span style={{ fontSize: 14, fontWeight: 600 }}>Blessin</span>
          <span style={{ fontSize: 12, color: "rgba(255,255,255,.85)", display: "flex", alignItems: "center", gap: 4 }}><span style={{ width: 6, height: 6, borderRadius: 999, background: "#7CE3B2" }}></span>Read up &amp; ready</span>
        </div>
        <button onClick={() => setOpen(false)} aria-label="Close chat" style={{ border: "none", background: "transparent", color: "#fff", cursor: "pointer", display: "flex", padding: 4, borderRadius: 999, fontSize: 18, lineHeight: 1 }}>✕</button>
      </header>

      <Bubbles messages={messages} pending={pending} onEscalationSuccess={onEscalationSuccess} />

      {messages.length <= 1 ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 6, padding: "0 12px 8px" }}>
          {SUGGESTIONS.map((s) => (
            <button key={s} onClick={() => send(s)} style={{ textAlign: "left", border: "1px solid var(--zinc-200, #e4e4e7)", background: "#fff", borderRadius: 999, padding: "7px 12px", fontSize: 13, color: "var(--zinc-900, #18181b)", cursor: "pointer", fontFamily: "inherit" }}>{s}</button>
          ))}
        </div>
      ) : null}

      <div ref={endRef} />
      <form onSubmit={(e) => { e.preventDefault(); send(input); }} style={{ display: "flex", alignItems: "center", gap: 8, borderTop: "1px solid var(--zinc-200, #e4e4e7)", padding: "8px 12px", background: "#fff" }}>
        <input value={input} disabled={coolingDown} onChange={(e) => setInput(e.target.value)} onFocus={() => setInputFocused(true)} onBlur={() => setInputFocused(false)} placeholder={coolingDown ? "One moment…" : "Type your message…"} maxLength={1000} style={{ flex: 1, border: "1px solid var(--zinc-300, #d4d4d8)", borderRadius: 999, padding: "8px 14px", fontSize: 14, outline: "none", fontFamily: "inherit", color: "var(--zinc-900, #18181b)", background: "#fff", opacity: coolingDown ? 0.6 : 1 }} />
        <button type="submit" disabled={!input.trim() || pending || coolingDown} aria-label="Send" style={{ width: 36, height: 36, borderRadius: 999, border: "none", background: EMBER, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", opacity: input.trim() && !pending && !coolingDown ? 1 : 0.4 }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>
        </button>
      </form>
      <footer style={{ textAlign: "center", padding: "6px", fontSize: 11, color: "var(--zinc-400, #a1a1aa)", borderTop: "1px solid var(--zinc-100, #f4f4f5)", background: "#fff" }}>Powered by Blessin AI</footer>
    </div>
  );
}

const bwStyle = document.createElement("style");
bwStyle.textContent = [
  "@keyframes bw-bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}",
  ".bw-md p,.bw-md ul,.bw-md ol{margin:0 0 6px}",
  ".bw-md>:last-child{margin-bottom:0}",
  ".bw-md ul,.bw-md ol{padding-left:18px}",
  ".bw-md li{margin-bottom:2px}",
].join("");
document.head.appendChild(bwStyle);

ReactDOM.createRoot(document.getElementById("blessin-widget")).render(<Widget />);
