// wiki-chat.jsx — Floating chat bubble that talks to the VirtuaTilt Worker.
//
// Exposes <ChatBubble /> on window. Mounted by wiki-app.jsx inside the App shell.
//
// State persists across navigations within the tab via sessionStorage so refreshing
// doesn't wipe the conversation. The Worker enforces the actual conversation/cost
// caps — this UI just sends every message in the local history.
//
// UI strings + starter chips below are English-only. The wiki went English-only
// in 2026 (the language switcher and translations were removed); the chatbot
// itself still replies in whatever language the customer types.

const CHAT_ENDPOINT = "https://virtuatilt-chat.3dptronics.workers.dev/chat";
const TRACK_ENDPOINT = "https://virtuatilt-chat.3dptronics.workers.dev/track";
const STORAGE_KEY = "vtw-chat-history-v1";

function trackChipClick(stableLabel) {
  try {
    const payload = JSON.stringify({
      url: 'wiki-internal://suggestion-chip',
      source: 'suggestion-chip:' + String(stableLabel || '').slice(0, 80),
      ref: (location.pathname + location.hash).slice(0, 200),
    });
    const blob = new Blob([payload], { type: 'application/json' });
    navigator.sendBeacon && navigator.sendBeacon(TRACK_ENDPOINT, blob);
  } catch (_) {}
}

// ─── UI strings (English-only) ───────────────────────────────────────────────
const UI = {
  title: "VirtuaTilt Helper",
  status: "Online · AI assistant",
  statusOffline: "Offline · service overloaded",
  welcome: "Hi! I'm the VirtuaTilt Wiki assistant. Ask me anything about your VirtuaTilt — setup, BLE pairing, profiles, troubleshooting. I'll answer based on the official Setup Guide.",
  supportDisclaimer: "ⓘ I can also help with third-party software setup (VPX, Future Pinball + BAM, DOF/DOFLinx, PinEvent, etc.) as a compendium — but 3DPTronics only officially supports the VirtuaTilt controller hardware. For deep software issues, refer to each tool's own documentation.",
  placeholder: "Ask about setup, flashing, BLE…",
  thinking: "thinking…",
  send: "Send",
  footer: "3DPTronics supports VirtuaTilt hardware only and NOT software configurations · third-party software guidance is a compendium — see official projects/games docs for support",
  rateLimit: "Rate limit reached — try again in an hour.",
  networkError: "Network error — please check your connection and try again.",
  errorGeneric: "Sorry, something went wrong. Try again in a moment.",
  serviceOverloaded: "The AI service is briefly overloaded. Please try again in a moment.",
  openChat: "Open VirtuaTilt help chat",
  closeChat: "Close chat",
  resetChat: "Start a new conversation",
  startNew: "Start new",
};

// ─── Starter-chip pool (English-only) ────────────────────────────────────────
// Each entry has a `label` (button text) and `q` (the question sent to the bot).
// The bot matches the language of the user's prompt, so a customer writing in
// another language still gets a localized reply. Click analytics use the English
// label as the stable key — the admin "Suggestion chip clicks" table stays stable.
const STARTER_CHIPS_POOL = [
  // Initial setup & firmware
  { label: "Initial setup", q: "How do I do initial setup of my VirtuaTilt?" },
  { label: "Flash the Pico", q: "How do I flash the Pico?" },
  { label: "Update firmware", q: "How do I update the wireless firmware?" },
  { label: "Load a profile", q: "How do I load a profile?" },
  { label: "What's in the box", q: "What's in the box?" },

  // BLE pairing
  { label: "Pair with Quest", q: "What's the BLE pairing process for Meta Quest?" },
  { label: "Pair with PC", q: "How do I pair my VirtuaTilt with a Windows PC?" },
  { label: "Switch profiles", q: "How do I switch between games / profiles?" },
  { label: "Forget old pairings", q: "Why do I need to forget old pairings before re-pairing?" },

  // Game-host setup
  { label: "VPX setup", q: "How do I set up VPX with my VirtuaTilt?" },
  { label: "Pinball FX VR", q: "How do I set up Pinball FX VR on Meta Quest?" },
  { label: "Future Pinball", q: "How do I set up Future Pinball?" },
  { label: "Star Wars VR", q: "How do I set up Star Wars Pinball VR?" },
  { label: "PS4 compatibility", q: "Is it compatible with PS4?" },
  { label: "Switch compatibility", q: "Is it compatible with Nintendo Switch?" },
  { label: "Steam pinball", q: "How do I use Steam pinball games with my VirtuaTilt?" },

  // Hardware tuning & calibration
  { label: "Tune shaker", q: "How do I adjust the shaker motor strength?" },
  { label: "Tune solenoids", q: "How do I adjust the flipper solenoid strength?" },
  { label: "Calibrate plunger", q: "How do I calibrate the plunger?" },
  { label: "Calibrate nudge", q: "How do I calibrate the accelerometer / nudge?" },
  { label: "LED colors", q: "What does the LED color in the central button mean?" },
  { label: "Buttons layout", q: "What's the default button mapping?" },

  // Concepts & differences
  { label: "Wired vs wireless", q: "What's the difference between Wired and Wireless VirtuaTilt?" },
  { label: "DOF / DOFLinx?", q: "Do I need DOF or DOFLinx?" },
  { label: "Use without Wi-Fi", q: "Can I use it without Wi-Fi?" },

  // Troubleshooting
  { label: "Quest can't find it", q: "My Meta Quest can't see my VirtuaTilt — what do I do?" },
  { label: "USB disconnects", q: "USB 3.0 keeps disconnecting — what should I do?" },
  { label: "Reset cabinet", q: "How do I reset the cabinet?" },

  // Themes, downloads, support
  { label: "Available themes", q: "What decal themes are available?" },
  { label: "Config guides", q: "Where can I download the configuration guides?" },
  { label: "Contact support", q: "How do I contact support?" },
  { label: "Buy now", q: "Where can I buy a VirtuaTilt?" },
];

function sampleChips(n) {
  const pool = STARTER_CHIPS_POOL.slice();
  for (let i = pool.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    const tmp = pool[i]; pool[i] = pool[j]; pool[j] = tmp;
  }
  return pool.slice(0, n);
}

// --- Minimal markdown renderer (paragraphs, bold, code, lists, links, tables) ---
function renderMd(raw) {
  const escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  let txt = escape(String(raw || ""));
  txt = txt.replace(/`([^`]+)`/g, "<code>$1</code>");
  txt = txt.replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>");
  // Markdown links: [text](url) → <a> — only allow http(s) / # anchors.
  txt = txt.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (m, label, href) => {
    const cleanHref = href.replace(/&amp;/g, "&");
    if (!/^https?:\/\//i.test(cleanHref) && !cleanHref.startsWith("#")) return m;
    const safeHref = cleanHref.replace(/"/g, "&quot;");
    return `<a href="${safeHref}" target="_blank" rel="noopener noreferrer">${label}</a>`;
  });

  // Tables — scan line-by-line, accumulate consecutive table rows.
  {
    const lines = txt.split("\n");
    const out = [];
    let i = 0;
    const isRow = (s) => /^\s*\|.+\|\s*$/.test(s);
    const isSep = (s) => /^\s*\|[\s|:\-]+\|\s*$/.test(s);
    const splitCells = (s) => s.trim().replace(/^\||\|$/g, "").split("|").map(c => c.trim());
    while (i < lines.length) {
      if (i + 1 < lines.length && isRow(lines[i]) && isSep(lines[i + 1])) {
        const header = splitCells(lines[i]);
        i += 2;
        const body = [];
        while (i < lines.length && isRow(lines[i])) { body.push(splitCells(lines[i])); i++; }
        let html = '<div class="chat-table-wrap"><table><thead><tr>';
        html += header.map(c => `<th>${c}</th>`).join("");
        html += "</tr></thead><tbody>";
        html += body.map(r => "<tr>" + r.map(c => `<td>${c}</td>`).join("") + "</tr>").join("");
        html += "</tbody></table></div>";
        out.push(html);
      } else {
        out.push(lines[i]);
        i++;
      }
    }
    txt = out.join("\n");
  }

  // Bullet lists
  txt = txt.replace(/(?:^|\n)((?:[-*•] .+(?:\n|$))+)/g, (m, block) => {
    const items = block.trim().split("\n").map(l =>
      `<li>${l.replace(/^[-*•]\s*/, "").trim()}</li>`).join("");
    return `\n<ul>${items}</ul>\n`;
  });
  // Numbered lists
  txt = txt.replace(/(?:^|\n)((?:\d+\. .+(?:\n|$))+)/g, (m, block) => {
    const items = block.trim().split("\n").map(l =>
      `<li>${l.replace(/^\d+\.\s*/, "").trim()}</li>`).join("");
    return `\n<ol>${items}</ol>\n`;
  });
  // Paragraphs
  txt = txt.split(/\n\n+/).map(block => {
    const t = block.trim();
    if (!t) return "";
    if (/^<(ul|ol|h\d|div|table)/i.test(t)) return t;
    return `<p>${t.replace(/\n/g, "<br>")}</p>`;
  }).join("");
  return txt;
}

function ChatBubble() {
  const ui = UI;

  const [open, setOpen] = React.useState(false);
  const [messages, setMessages] = React.useState(() => {
    try {
      const raw = sessionStorage.getItem(STORAGE_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch (_) { return []; }
  });
  const [input, setInput] = React.useState("");
  const [sending, setSending] = React.useState(false);
  const [error, setError] = React.useState(null);
  // Reactive service status — starts optimistic; flips to 'offline' after a
  // real upstream / network failure, flips back to 'online' on next success.
  // Rate-limit failures don't change status (they're user-specific, not a
  // service issue).
  const [serviceStatus, setServiceStatus] = React.useState('online');

  const logRef = React.useRef(null);
  const inputRef = React.useRef(null);

  // Random sample of 5 suggestion chips — re-rolled each time the conversation
  // resets to empty (initial mount + after reset button).
  const chipSample = React.useMemo(() => sampleChips(5), [messages.length === 0]);

  React.useEffect(() => {
    try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(messages)); } catch (_) {}
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [messages, sending]);

  React.useEffect(() => {
    if (open && inputRef.current) inputRef.current.focus();
  }, [open]);

  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape" && open) setOpen(false);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);

  async function send(text) {
    text = (text || "").trim();
    if (!text || sending) return;
    setError(null);
    const next = [...messages, { role: "user", content: text }];
    setMessages(next);
    setInput("");
    setSending(true);

    try {
      const res = await fetch(CHAT_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ messages: next }),
      });
      let data = null;
      try { data = await res.json(); } catch (_) {}

      if (!res.ok) {
        const errCode = data && data.error;
        if (errCode === "rate_limited") {
          // Rate-limit is user-specific, NOT a service outage — don't flip status.
          setError(data.message || ui.rateLimit);
        } else if (errCode === "upstream_error" || errCode === "upstream_unreachable") {
          setError(ui.serviceOverloaded);
          setServiceStatus('offline');
        } else {
          setError(ui.errorGeneric);
          setServiceStatus('offline');
        }
        return;
      }
      const reply = (data && data.reply) || "(empty reply)";
      setMessages((prev) => [...prev, { role: "assistant", content: reply }]);
      // Successful reply confirms the service is healthy — recover from any
      // prior 'offline' state.
      setServiceStatus('online');
    } catch (e) {
      setError(ui.networkError);
      setServiceStatus('offline');
    } finally {
      setSending(false);
    }
  }

  function resetConversation() {
    setMessages([]);
    setError(null);
    try { sessionStorage.removeItem(STORAGE_KEY); } catch (_) {}
  }

  // --- Render ---
  if (!open) {
    return (
      <button
        className="chat-bubble"
        onClick={() => setOpen(true)}
        aria-label={ui.openChat}
      >
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor"
             strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
        </svg>
        {messages.length === 0 && <span className="chat-bubble-dot" aria-hidden="true" />}
      </button>
    );
  }

  return (
    <div className="chat-panel" role="dialog" aria-label={ui.title}>
      <div className="chat-header">
        <div className="chat-header-titlewrap">
          <div className="chat-header-title">{ui.title}</div>
          <div className="chat-header-status">
            <span className={"chat-live" + (serviceStatus === 'offline' ? " offline" : "")}>●</span>
            {' '}{serviceStatus === 'offline' ? ui.statusOffline : ui.status}
          </div>
        </div>
        {messages.length > 0 && (
          <button className="chat-reset" onClick={resetConversation}
                  aria-label={ui.resetChat} title={ui.startNew}>↺</button>
        )}
        <button className="chat-close" onClick={() => setOpen(false)} aria-label={ui.closeChat}>×</button>
      </div>

      <div className="chat-log" ref={logRef}>
        {messages.length === 0 && (
          <>
            <div className="chat-msg chat-bot">
              <p>{ui.welcome}</p>
              <p className="chat-disclaimer">{ui.supportDisclaimer}</p>
            </div>
            <div className="chat-suggestions">
              {chipSample.map((c) => {
                const stableLabel = c.label;                     // analytics key (English)
                const localLabel = c.label;                      // displayed label
                const localQ = c.q;                              // question sent to bot
                return (
                  <button key={stableLabel} className="chat-sugg"
                          onClick={() => { trackChipClick(stableLabel); send(localQ); }}>
                    {localLabel}
                  </button>
                );
              })}
            </div>
          </>
        )}

        {messages.map((m, i) => (
          <div key={i} className={"chat-msg " + (m.role === "user" ? "chat-user" : "chat-bot")}>
            {m.role === "user"
              ? <p>{m.content}</p>
              : <div dangerouslySetInnerHTML={{ __html: renderMd(m.content) }} />}
          </div>
        ))}

        {sending && (
          <div className="chat-msg chat-bot chat-typing"><p>{ui.thinking}</p></div>
        )}
        {error && (
          <div className="chat-msg chat-error"><p>{error}</p></div>
        )}
      </div>

      <div className="chat-input-row">
        <textarea
          ref={inputRef}
          className="chat-input"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter" && !e.shiftKey) {
              e.preventDefault();
              send(input);
            }
          }}
          placeholder={ui.placeholder}
          rows={1}
          disabled={sending}
        />
        <button className="chat-send" onClick={() => send(input)} disabled={sending || !input.trim()}>
          {sending ? "…" : ui.send}
        </button>
      </div>

      <div className="chat-footer">
        {ui.footer}
      </div>
    </div>
  );
}

window.ChatBubble = ChatBubble;
