/* ============================================================
   Temp Mail — main app
   ============================================================ */

const { useState, useEffect, useMemo, useRef, useCallback } = React;
const api = window.TempMailAPI;

/* Tweakable defaults written back to disk by the host on change. */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "accent": "#2BB673",
  "density": "comfortable",
  "font": "geist",
  "autoRefresh": true,
  "refreshInterval": 2,
  "showFooter": true,
  "showPreview": true,
  "soundOnNew": false,
  "neverExpire": true,
  "compactList": false,
  "smartGrouping": true
}/*EDITMODE-END*/;

const FONT_STACKS = {
  geist: {
    label: "Geist",
    sans: "'Geist', ui-sans-serif, system-ui, sans-serif",
    mono: "'Geist Mono', ui-monospace, Menlo, monospace",
  },
  claude: {
    label: "Claude",
    sans: "'Source Serif 4', 'Copernicus', Georgia, serif",
    mono: "'JetBrains Mono', ui-monospace, Menlo, monospace",
  },
  inter: {
    label: "Inter",
    sans: "'Inter', ui-sans-serif, system-ui, sans-serif",
    mono: "'JetBrains Mono', ui-monospace, Menlo, monospace",
  },
  ibm: {
    label: "IBM Plex",
    sans: "'IBM Plex Sans', ui-sans-serif, sans-serif",
    mono: "'IBM Plex Mono', ui-monospace, Menlo, monospace",
  },
  system: {
    label: "System",
    sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    mono: "ui-monospace, SFMono-Regular, Menlo, monospace",
  },
};

/* ---------- toast ---------- */
function playPing() {
  try {
    const ctx = new (window.AudioContext || window.webkitAudioContext)();
    const o = ctx.createOscillator();
    const g = ctx.createGain();
    o.connect(g); g.connect(ctx.destination);
    o.frequency.value = 880;
    o.type = "sine";
    g.gain.setValueAtTime(0.0001, ctx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.18, ctx.currentTime + 0.02);
    g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.4);
    o.start();
    o.stop(ctx.currentTime + 0.42);
  } catch (e) {}
}
function useToasts() {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((text, icon) => {
    const id = Math.random().toString(36).slice(2);
    setToasts((t) => [...t, { id, text, icon }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2200);
  }, []);
  const node = (
    <div className="toast-stack">
      {toasts.map((t) => (
        <div className="toast" key={t.id}>
          {t.icon && <Icon name={t.icon} size={14} />}
          <span>{t.text}</span>
        </div>
      ))}
    </div>
  );
  return [node, push];
}

async function copyText(text) {
  if (navigator.clipboard && window.isSecureContext) {
    await navigator.clipboard.writeText(text);
    return true;
  }
  const ta = document.createElement("textarea");
  ta.value = text;
  ta.setAttribute("readonly", "");
  ta.style.position = "fixed";
  ta.style.left = "-9999px";
  document.body.appendChild(ta);
  ta.select();
  const ok = document.execCommand("copy");
  ta.remove();
  return ok;
}

/* ---------- Modals ---------- */
function CustomAddressModal({ open, onClose, onSubmit, domains, initialLocal }) {
  const [local, setLocal] = useState(initialLocal || "");
  const [domain, setDomain] = useState(domains[0] || "tempbox.email");
  const [customDomain, setCustomDomain] = useState("");
  const [useCustom, setUseCustom] = useState(false);
  const inputRef = useRef(null);

  useEffect(() => { if (open) setTimeout(() => inputRef.current?.focus(), 50); }, [open]);
  useEffect(() => { setLocal(initialLocal || ""); }, [initialLocal, open]);

  if (!open) return null;

  const finalDomain = useCustom ? customDomain.trim() : domain;
  const valid = /^[a-z0-9._-]{2,}$/i.test(local.trim()) && /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(finalDomain);

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h2>Choose an address</h2>
        <p className="subtitle">
          Type any address. If it already exists, you'll be signed straight into its inbox — shared across anyone who knows the address.
        </p>

        <div className="field">
          <label>Address</label>
          <div className="split-input">
            <input
              ref={inputRef}
              type="text"
              value={local}
              onChange={(e) => setLocal(e.target.value.toLowerCase().replace(/[^a-z0-9._-]/g, ""))}
              placeholder="quiet-otter-2241"
              spellCheck={false}
            />
            <span className="at">@</span>
            {useCustom ? (
              <input
                type="text"
                value={customDomain}
                onChange={(e) => setCustomDomain(e.target.value.toLowerCase())}
                placeholder="yourdomain.com"
                spellCheck={false}
                style={{ minWidth: 120 }}
              />
            ) : (
              <select value={domain} onChange={(e) => setDomain(e.target.value)}>
                {domains.map((d) => <option key={d} value={d}>{d}</option>)}
              </select>
            )}
          </div>
          <div className="hint">
            {useCustom ? (
              <>Point an MX record from <code>{customDomain || "yourdomain.com"}</code> to <code>mx.tempbox.email</code> and any mail will land here. <button className="btn ghost" style={{ padding: "2px 6px", fontSize: 11 }} onClick={() => setUseCustom(false)}>Use a built-in domain instead</button></>
            ) : (
              <>Want to use your own domain? <button className="btn ghost" style={{ padding: "2px 6px", fontSize: 11 }} onClick={() => setUseCustom(true)}>Add a custom domain</button></>
            )}
          </div>
        </div>

        <div className="modal-actions">
          <button className="btn ghost" onClick={() => setLocal(api.suggestLocal())}>
            <Icon name="zap" size={12} style={{ marginRight: 4, verticalAlign: -2 }} />
            Suggest
          </button>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button
            className="btn primary"
            disabled={!valid}
            style={{ opacity: valid ? 1 : 0.5, cursor: valid ? "pointer" : "not-allowed" }}
            onClick={() => onSubmit({ local: local.trim(), domain: finalDomain })}
          >
            Open inbox
          </button>
        </div>
      </div>
    </div>
  );
}

function HistoryDropdown({ open, onClose, history, current, onPick, onDelete }) {
  if (!open) return null;
  return (
    <div className="dropdown" onClick={(e) => e.stopPropagation()}>
      <div className="dropdown-header">
        <span>Recent addresses</span>
        <span>{history.length}</span>
      </div>
      <div className="dropdown-list">
        {history.length === 0 && (
          <div style={{ padding: 18, fontSize: 12, color: "var(--text-muted)", textAlign: "center" }}>
            No history yet.
          </div>
        )}
        {history.map((h) => (
          <div
            key={h.address}
            className="dropdown-item"
            onClick={() => { onPick(h.address); onClose(); }}
            style={current === h.address ? { background: "var(--bg-sunken)" } : null}
          >
            <div className="addr mono">{h.address}</div>
            <div className="meta">{relTime(h.lastSeenAt)} ago</div>
            <button
              className="delete-x"
              title="Forget this address"
              onClick={(e) => { e.stopPropagation(); onDelete(h.address); }}
            >
              <Icon name="x" size={12} />
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

function QRModal({ open, address, onClose, onCopy }) {
  if (!open) return null;
  const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=12&data=${encodeURIComponent(address)}`;
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal qr-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-head qr-head">
          <div>
            <h2>QR code</h2>
            <p className="subtitle mono">{address}</p>
          </div>
          <button className="icon-btn" onClick={onClose} title="Close"><Icon name="x" /></button>
        </div>
        <div className="qr-box">
          <img src={qrUrl} width="220" height="220" alt={`QR code for ${address}`} />
        </div>
        <div className="modal-actions">
          <button className="btn" onClick={onCopy}>
            <Icon name="copy" size={12} style={{ marginRight: 4, verticalAlign: -2 }} />
            Copy address
          </button>
          <button className="btn primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

/* ---------- Main App ---------- */
function App() {
  const [tweaks, setTweaks] = useTweaks(TWEAK_DEFAULTS);
  const [mobilePreview, setMobilePreview] = useState(false);
  const [domains, setDomains] = useState([]);
  const [address, setAddress] = useState("");
  const [domain, setDomain] = useState("");
  const [messages, setMessages] = useState([]);
  const [selectedId, setSelectedId] = useState(null);
  const [filter, setFilter] = useState("all");
  const [history, setHistory] = useState([]);
  const [showHistory, setShowHistory] = useState(false);
  const [showCustom, setShowCustom] = useState(false);
  const [showQr, setShowQr] = useState(false);
  const [showAbout, setShowAbout] = useState(false);
  const [refreshing, setRefreshing] = useState(false);
  const currentAdapter = api.currentAdapter();
  const [toastNode, pushToast] = useToasts();
  const pollInFlight = useRef(false);

  useEffect(() => {
    if (mobilePreview) {
      document.documentElement.setAttribute("data-preview", "mobile");
    } else {
      document.documentElement.removeAttribute("data-preview");
    }
    return () => document.documentElement.removeAttribute("data-preview");
  }, [mobilePreview]);

  // theme + font application
  useEffect(() => {
    const stack = FONT_STACKS[tweaks.font] || FONT_STACKS.geist;
    document.documentElement.setAttribute("data-theme", tweaks.theme);
    document.documentElement.setAttribute("data-density", tweaks.density);
    document.documentElement.setAttribute("data-font", tweaks.font);
    document.body.style.fontFamily = stack.sans;
    document.documentElement.style.setProperty("--font-sans", stack.sans);
    document.documentElement.style.setProperty("--font-mono", stack.mono);
    document.documentElement.style.setProperty("--accent", tweaks.accent);
    // derive accent-bg + accent-text from accent
    document.documentElement.style.setProperty(
      "--accent-bg",
      tweaks.theme === "dark"
        ? `color-mix(in oklch, ${tweaks.accent} 22%, transparent)`
        : `color-mix(in oklch, ${tweaks.accent} 18%, white)`
    );
    document.documentElement.style.setProperty(
      "--accent-text",
      tweaks.theme === "dark"
        ? `color-mix(in oklch, ${tweaks.accent} 80%, white)`
        : `color-mix(in oklch, ${tweaks.accent} 80%, black)`
    );
  }, [tweaks.theme, tweaks.density, tweaks.accent, tweaks.font]);

  // boot
  useEffect(() => {
    (async () => {
      try {
        const ds = await api.listDomains();
        setDomains(ds);
        const hist = await api.listHistory();
        setHistory(hist);
        // open the most recent address, or generate one
        if (hist.length > 0) {
          await openAddress(hist[0].address);
        } else {
          const local = api.suggestLocal();
          await openAddress(`${local}@${ds[0]}`);
        }
      } catch (e) {
        pushToast(`${currentAdapter.label} unreachable — try Mock`, "x");
        console.error("Boot failure:", e);
        // Fall back to mock so the UI is usable
        api.setBackend("mock");
        const ds = await api.listDomains();
        setDomains(ds);
        const local = api.suggestLocal();
        await openAddress(`${local}@${ds[0]}`);
      }
    })();
  }, []);

  const openAddress = useCallback(async (fullAddr) => {
    try {
      const [local, dom] = fullAddr.split("@");
      const { box, isNew } = await api.getOrCreate({ local, domain: dom });
      setAddress(box.address);
      setDomain(box.domain);
      const msgs = await api.fetchMessages(box.address);
      setMessages(msgs);
      setSelectedId(null);
      const hist = await api.listHistory();
      setHistory(hist);
      pushToast(isNew ? "New inbox created" : "Opened existing inbox", "check");
    } catch (e) {
      pushToast(`Couldn't open address (${e.message || e})`, "x");
      console.error(e);
    }
  }, [pushToast]);

  const pollMessages = useCallback(async ({ silent = true } = {}) => {
    if (!address || pollInFlight.current) return;
    pollInFlight.current = true;
    try {
      const msgs = await api.fetchMessages(address);
      setMessages((prev) => {
        if (tweaks.soundOnNew && msgs.length > prev.length) playPing();
        return msgs;
      });
    } catch (e) {
      if (!silent) pushToast(`Refresh failed (${e.message || e})`, "x");
    } finally {
      pollInFlight.current = false;
    }
  }, [address, tweaks.soundOnNew, pushToast]);

  // Auto refresh. Real inboxes get an immediate poll and a short burst after opening,
  // which makes signup codes show up faster without stacking overlapping requests.
  useEffect(() => {
    if (!tweaks.autoRefresh || !address) return;
    pollMessages();
    const intervalMs = Math.max(1, tweaks.refreshInterval) * 1000;
    const t = setInterval(() => pollMessages(), intervalMs);
    const burst = setInterval(() => pollMessages(), Math.min(intervalMs, 2000));
    const stopBurst = setTimeout(() => clearInterval(burst), 60000);
    const onWake = () => {
      if (document.visibilityState !== "hidden") pollMessages();
    };
    document.addEventListener("visibilitychange", onWake);
    window.addEventListener("focus", onWake);
    return () => {
      clearInterval(t);
      clearInterval(burst);
      clearTimeout(stopBurst);
      document.removeEventListener("visibilitychange", onWake);
      window.removeEventListener("focus", onWake);
    };
  }, [tweaks.autoRefresh, address, tweaks.refreshInterval, pollMessages]);

  // close dropdowns on outside click
  useEffect(() => {
    const h = () => setShowHistory(false);
    if (showHistory) {
      window.addEventListener("click", h);
      return () => window.removeEventListener("click", h);
    }
  }, [showHistory]);

  const refresh = async () => {
    if (!address) return;
    setRefreshing(true);
    await pollMessages({ silent: false });
    setTimeout(() => setRefreshing(false), 400);
  };

  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    try {
      await copyText(address);
      pushToast("Address copied", "copy");
      setCopied(true);
      setTimeout(() => setCopied(false), 1400);
    } catch (e) {
      pushToast("Couldn't copy address", "x");
    }
  };

  const handleGenerateNew = async () => {
    const local = api.suggestLocal();
    await openAddress(`${local}@${domain || domains[0]}`);
  };

  const handleDomainChange = async (newDomain) => {
    const [local] = address.split("@");
    await openAddress(`${local}@${newDomain}`);
  };

  const handleDelete = async () => {
    if (!address) return;
    await api.deleteMailbox(address);
    const hist = await api.listHistory();
    setHistory(hist);
    if (hist.length > 0) await openAddress(hist[0].address);
    else { const local = api.suggestLocal(); await openAddress(`${local}@${domain}`); }
    pushToast("Inbox deleted", "trash");
  };

  const handleSimulate = async () => {
    await api.simulateIncoming(address);
    const msgs = await api.fetchMessages(address);
    setMessages(msgs);
    pushToast("New message received", "mail");
  };

  const handleSelect = async (id) => {
    setSelectedId(id);
    // For real backends, fetch full body since list endpoints often only return excerpts.
    if (currentAdapter.id !== "mock") {
      try {
        const full = await api.fetchMessage(address, id);
        if (full && full.html) {
          setMessages((ms) => ms.map((m) => m.id === id ? { ...m, html: full.html, attachments: full.attachments || m.attachments } : m));
        }
      } catch (e) {
        pushToast("Couldn't load body", "x");
      }
    }
  };

  const handleMarkRead = async (id) => {
    await api.markRead(address, id);
    setMessages((ms) => ms.map((m) => m.id === id ? { ...m, read: true } : m));
  };

  const handleDeleteMessage = async (id) => {
    await api.deleteMessage(address, id);
    setMessages((ms) => ms.filter((m) => m.id !== id));
    if (selectedId === id) setSelectedId(null);
  };

  const counts = useMemo(() => ({
    all: messages.length,
    unread: messages.filter((m) => !m.read).length,
    otp: messages.filter((m) => m.kind === "otp").length,
  }), [messages]);

  const selectedMessage = messages.find((m) => m.id === selectedId);

  return (
    <React.Fragment>
    <div className="app">
      {/* Top bar */}
      <header className="topbar">
        <div className="brand">
          <div className="brand-mark" aria-hidden="true">
            <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
              <rect x="8" y="14" width="48" height="36" rx="8" stroke="currentColor" strokeWidth="6" />
              <path d="M14 19 L32 36 L50 19" stroke="currentColor" strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </div>
          <div className="brand-meta">
            <span>Tempbox</span>
            <small>Disposable mail · v0.4</small>
          </div>
        </div>

        <div className="topbar-right">
          <div className="status-pill" title={`${currentAdapter.label}`}>
            <span className="status-dot"></span>
            <span>{currentAdapter.label}</span>
          </div>
          <div className="dropdown-wrap" onClick={(e) => e.stopPropagation()}>
            <button
              className={`icon-btn ${showHistory ? "active" : ""}`}
              onClick={() => setShowHistory((s) => !s)}
              title="Recent addresses"
            >
              <Icon name="history" />
            </button>
            <HistoryDropdown
              open={showHistory}
              onClose={() => setShowHistory(false)}
              history={history}
              current={address}
              onPick={openAddress}
              onDelete={async (a) => {
                await api.deleteMailbox(a);
                const h = await api.listHistory();
                setHistory(h);
                if (a === address) {
                  if (h.length > 0) openAddress(h[0].address);
                  else handleGenerateNew();
                }
              }}
            />
          </div>
          <button
            className="icon-btn"
            title={tweaks.theme === "dark" ? "Light mode" : "Dark mode"}
            onClick={() => setTweaks({ theme: tweaks.theme === "dark" ? "light" : "dark" })}
          >
            <Icon name={tweaks.theme === "dark" ? "sun" : "moon"} />
          </button>
          <button className="icon-btn" title="Settings" onClick={() => setShowAbout(true)}>
            <Icon name="settings" />
          </button>
        </div>
      </header>

      {/* Main grid */}
      <div className={`main ${selectedMessage ? "reader-open" : ""}`}>
        {/* Sidebar */}
        <aside className="sidebar">
          <div className="address-panel">
            <div className="address-label">
              <span>Your address</span>
              <span className="lifetime">
                {tweaks.neverExpire ? "permanent ∞" : "expires in 24h ↻"}
              </span>
            </div>
            <div className="address-display">
              <input
                className="addr-input mono"
                value={address}
                readOnly
                onFocus={(e) => e.target.select()}
              />
              <button className={`copy-btn ${copied ? "copied" : ""}`} title="Copy" onClick={handleCopy}>
                <Icon name={copied ? "check" : "copy"} />
              </button>
              <button className="copy-btn" title="QR code" onClick={() => setShowQr(true)}><Icon name="qrcode" /></button>
            </div>

            <div className="address-actions">
              <button className="action-btn primary" onClick={handleGenerateNew} title="Generate a new random address">
                <Icon name="plus" size={12} /> New address
              </button>
              <button className="action-btn" onClick={() => setShowCustom(true)} title="Choose a custom address">
                <Icon name="pencil" size={12} /> Custom
              </button>
            </div>

            <div className="domain-row">
              <span>Domain:</span>
              <select
                className="domain-pick mono"
                value={domain}
                onChange={(e) => handleDomainChange(e.target.value)}
              >
                {domains.map((d) => <option key={d} value={d}>{d}</option>)}
              </select>
              <button className="action-btn" style={{ padding: "3px 8px", fontSize: 11 }} onClick={handleDelete} title="Delete this inbox">
                <Icon name="trash" size={11} />
              </button>
            </div>
          </div>

          {/* Tabs */}
          <div className="tabs">
            {[
              { id: "all", label: "Inbox", count: counts.all },
              { id: "unread", label: "Unread", count: counts.unread },
              { id: "otp", label: "Codes", count: counts.otp },
            ].map((t) => (
              <button
                key={t.id}
                className={`tab ${filter === t.id ? "active" : ""}`}
                onClick={() => setFilter(t.id)}
              >
                {t.label}
                <span className="count">{t.count}</span>
              </button>
            ))}
            <div style={{ flex: 1 }} />
            <button
              className="tab"
              style={{ paddingLeft: 8, paddingRight: 8 }}
              onClick={refresh}
              title="Refresh"
            >
              <Icon name="refresh" size={13} className={refreshing ? "spin" : ""} />
            </button>
          </div>

          {/* Message list */}
          <div className="msg-list">
            <MessageList
              messages={messages}
              selectedId={selectedId}
              onSelect={handleSelect}
              filter={filter}
              showPreview={tweaks.showPreview}
              grouped={tweaks.smartGrouping}
            />
          </div>

          {tweaks.showFooter && (
            <div className="footer-strip">
              {currentAdapter.supports?.simulateIncoming ? (
                <button onClick={handleSimulate} style={{ fontSize: 11, color: "var(--text-muted)", textDecoration: "underline", textUnderlineOffset: 2 }}>
                  + simulate inbound (demo)
                </button>
              ) : (
                <span style={{ fontSize: 11 }}>Made by Asim 😎</span>
              )}
              <button
                className={`footer-chip ${mobilePreview ? "active" : ""}`}
                onClick={() => setMobilePreview((m) => !m)}
                title={mobilePreview ? "Exit mobile preview" : "Preview on mobile"}
              >
                <Icon name="phone" size={11} />
                {mobilePreview ? "exit mobile" : "mobile preview"}
              </button>
              <span className="auto-refresh">
                <span className="auto-refresh-dot"></span>
                {tweaks.autoRefresh ? `auto ${tweaks.refreshInterval}s` : "manual"}
              </span>
            </div>
          )}
        </aside>

        {/* Reader */}
        <Reader
          message={selectedMessage}
          onClose={() => setSelectedId(null)}
          onDelete={handleDeleteMessage}
          onMarkRead={handleMarkRead}
        />
      </div>

      {/* Modals + toasts */}
      <CustomAddressModal
        open={showCustom}
        onClose={() => setShowCustom(false)}
        onSubmit={async ({ local, domain }) => {
          setShowCustom(false);
          await openAddress(`${local}@${domain}`);
        }}
        domains={domains}
        initialLocal={address.split("@")[0]}
      />

      <QRModal
        open={showQr}
        address={address}
        onClose={() => setShowQr(false)}
        onCopy={handleCopy}
      />

      {showAbout && (
        <SettingsModal
          onClose={() => setShowAbout(false)}
          address={address}
          tweaks={tweaks}
          setTweaks={setTweaks}
        />
      )}

      {/* Tweaks lives inside App so it shares the same useTweaks state */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Appearance">
          <TweakRadio
            label="Theme"
            value={tweaks.theme}
            onChange={(v) => setTweaks("theme", v)}
            options={[{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]}
          />
          <TweakColor
            label="Accent"
            value={tweaks.accent}
            onChange={(v) => setTweaks("accent", v)}
            options={["#2BB673", "#3B82F6", "#F97316", "#A855F7"]}
          />
          <TweakSelect
            label="Font"
            value={tweaks.font}
            onChange={(v) => setTweaks("font", v)}
            options={Object.entries(FONT_STACKS).map(([k, v]) => ({ value: k, label: v.label }))}
          />
          <TweakRadio
            label="Density"
            value={tweaks.density}
            onChange={(v) => setTweaks("density", v)}
            options={[{ value: "comfortable", label: "Roomy" }, { value: "compact", label: "Compact" }]}
          />
        </TweakSection>

        <TweakSection label="Inbox">
          <TweakToggle
            label="Never expire"
            value={tweaks.neverExpire}
            onChange={(v) => setTweaks("neverExpire", v)}
          />
          <TweakToggle
            label="Show message preview"
            value={tweaks.showPreview}
            onChange={(v) => setTweaks("showPreview", v)}
          />
          <TweakToggle
            label="Group by sender"
            value={tweaks.smartGrouping}
            onChange={(v) => setTweaks("smartGrouping", v)}
          />
        </TweakSection>

        <TweakSection label="Notifications">
          <TweakToggle
            label="Auto-refresh"
            value={tweaks.autoRefresh}
            onChange={(v) => setTweaks("autoRefresh", v)}
          />
          <TweakSlider
            label="Refresh every"
            value={tweaks.refreshInterval}
            min={1}
            max={60}
            step={1}
            unit="s"
            onChange={(v) => setTweaks("refreshInterval", v)}
          />
          <TweakToggle
            label="Sound on new mail"
            value={tweaks.soundOnNew}
            onChange={(v) => setTweaks("soundOnNew", v)}
          />
          <TweakToggle
            label="Show footer strip"
            value={tweaks.showFooter}
            onChange={(v) => setTweaks("showFooter", v)}
          />
        </TweakSection>
      </TweaksPanel>

      {toastNode}
    </div>
    <button
      className="preview-exit"
      onClick={() => setMobilePreview(false)}
      title="Exit mobile preview"
    >
      <span className="dot"></span>
      Mobile preview - 390x844
      <Icon name="x" size={12} />
    </button>
    </React.Fragment>
  );
}

/* ---------- Settings modal (full settings menu) ---------- */
function SettingsModal({ onClose, address, tweaks, setTweaks }) {
  const [tab, setTab] = useState("appearance");
  const T = ({ id, label }) => (
    <button
      className={`settings-tab ${tab === id ? "active" : ""}`}
      onClick={() => setTab(id)}
    >{label}</button>
  );

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal settings-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-head">
          <div>
            <h2>Settings</h2>
            <p className="subtitle">Configure how Tempbox looks and behaves on this device.</p>
          </div>
          <button className="icon-btn" onClick={onClose}><Icon name="x" /></button>
        </div>

        <div className="settings-tabs">
          <T id="appearance" label="Appearance" />
          <T id="inbox" label="Inbox" />
          <T id="notifications" label="Notifications" />
          <T id="privacy" label="Privacy" />
          <T id="api" label="API" />
        </div>

        <div className="settings-body">
          {tab === "appearance" && (
            <>
              <SRow label="Theme" hint="Dark mode uses pure black so it looks right on OLED.">
                <SegBtns
                  value={tweaks.theme}
                  onChange={(v) => setTweaks("theme", v)}
                  options={[
                    { value: "light", label: "Light", icon: "sun" },
                    { value: "dark", label: "Dark", icon: "moon" },
                  ]}
                />
              </SRow>
              <SRow label="Accent color" hint="Used for unread dots, badges, and code blocks.">
                <div className="swatch-row">
                  {[
                    ["#2BB673", "Mint"],
                    ["#3B82F6", "Blue"],
                    ["#F97316", "Orange"],
                    ["#A855F7", "Purple"],
                    ["#EF4444", "Red"],
                    ["#0EA5E9", "Sky"],
                  ].map(([c, n]) => (
                    <button
                      key={c}
                      className={`swatch ${tweaks.accent === c ? "active" : ""}`}
                      style={{ background: c }}
                      title={n}
                      onClick={() => setTweaks("accent", c)}
                    >{tweaks.accent === c && <Icon name="check" size={12} />}</button>
                  ))}
                </div>
              </SRow>
              <SRow label="Font family" hint={fontHint(tweaks.font)}>
                <select
                  className="s-select"
                  value={tweaks.font}
                  onChange={(e) => setTweaks("font", e.target.value)}
                >
                  {Object.entries(FONT_STACKS).map(([k, v]) => (
                    <option key={k} value={k}>{v.label}</option>
                  ))}
                </select>
              </SRow>
              <SRow label="Density" hint="Compact shrinks padding around list rows and headers.">
                <SegBtns
                  value={tweaks.density}
                  onChange={(v) => setTweaks("density", v)}
                  options={[
                    { value: "comfortable", label: "Roomy" },
                    { value: "compact", label: "Compact" },
                  ]}
                />
              </SRow>
            </>
          )}

          {tab === "inbox" && (
            <>
              <SRow
                label="Never expire mail"
                hint="When off, inboxes are forgotten after 24h of inactivity. When on, they stick around forever."
              >
                <Toggle value={tweaks.neverExpire} onChange={(v) => setTweaks("neverExpire", v)} />
              </SRow>
              <SRow
                label="Show message preview"
                hint="The first line of body text under each subject in the list."
              >
                <Toggle value={tweaks.showPreview} onChange={(v) => setTweaks("showPreview", v)} />
              </SRow>
              <SRow
                label="Group conversations by sender"
                hint="Stack repeated emails from the same sender into a single row."
              >
                <Toggle value={tweaks.smartGrouping} onChange={(v) => setTweaks("smartGrouping", v)} />
              </SRow>
            </>
          )}

          {tab === "notifications" && (
            <>
              <SRow label="Auto-refresh" hint="Poll for new mail in the background.">
                <Toggle value={tweaks.autoRefresh} onChange={(v) => setTweaks("autoRefresh", v)} />
              </SRow>
              <SRow label="Refresh interval" hint={`Check for mail every ${tweaks.refreshInterval}s.`}>
                <div className="slider-wrap">
                  <input
                    type="range"
                    min="1" max="60" step="1"
                    value={tweaks.refreshInterval}
                    onChange={(e) => setTweaks("refreshInterval", +e.target.value)}
                  />
                  <span className="mono slider-val">{tweaks.refreshInterval}s</span>
                </div>
              </SRow>
              <SRow label="Play sound on new mail" hint="A short ping when fresh mail arrives.">
                <Toggle value={tweaks.soundOnNew} onChange={(v) => setTweaks("soundOnNew", v)} />
              </SRow>
              <SRow label="Show footer strip" hint="The tiny status bar with auto-refresh state.">
                <Toggle value={tweaks.showFooter} onChange={(v) => setTweaks("showFooter", v)} />
              </SRow>
            </>
          )}

          {tab === "privacy" && (
            <>
              <SRow
                label="Clear local history"
                hint="Erase the list of addresses you've used on this device. Mailboxes themselves stay on the server."
              >
                <button
                  className="btn"
                  onClick={() => {
                    if (confirm("Clear address history? Mailboxes themselves are kept.")) {
                      const idx = JSON.parse(localStorage.getItem("tm:addr-index") || "{}");
                      Object.keys(idx).forEach((a) => localStorage.removeItem("tm:mailbox:" + a));
                      localStorage.removeItem("tm:addr-index");
                      location.reload();
                    }
                  }}
                >Clear all</button>
              </SRow>
              <SRow
                label="Export data"
                hint="Download all locally-stored mailboxes as JSON."
              >
                <button
                  className="btn"
                  onClick={() => {
                    const data = {};
                    Object.keys(localStorage).forEach((k) => {
                      if (k.startsWith("tm:")) data[k] = localStorage.getItem(k);
                    });
                    const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement("a");
                    a.href = url; a.download = "tempbox-export.json"; a.click();
                  }}
                >
                  <Icon name="download" size={12} style={{ marginRight: 4, verticalAlign: -2 }} />
                  Export JSON
                </button>
              </SRow>
              <div className="hint" style={{ marginTop: 8 }}>
                <b>Heads up:</b> any address is reachable by anyone who types it — by design. If you need a private inbox, generate a long random one or use a custom domain.
              </div>
            </>
          )}

          {tab === "api" && (
            <ApiPanel address={address} onSwitched={() => onClose()} />
          )}
        </div>

        <div className="settings-foot">
          <span className="hint" style={{ margin: 0 }}>Tempbox · v0.4 · settings save automatically</span>
          <button className="btn primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

/* ---------- API backend panel ---------- */
function ApiPanel({ onSwitched }) {
  const [backend, setBackend] = useState(() => api.getBackend());
  const [pingState, setPingState] = useState(null); // { ok, ms, error }
  const [pinging, setPinging] = useState(false);
  const [benching, setBenching] = useState(false);
  const [benchResults, setBenchResults] = useState([]);
  const adapters = api.listAdapters();
  const active = adapters.find((a) => a.id === backend) || adapters[0];

  const switchTo = async (id) => {
    api.setBackend(id);
    setBackend(id);
    setPingState(null);
    // Reload to re-bootstrap with a fresh address from the new backend
    setTimeout(() => location.reload(), 300);
  };

  const ping = async () => {
    setPinging(true);
    const r = await api.ping();
    setPingState(r);
    setPinging(false);
  };

  const runBenchmarks = async () => {
    const timed = async (fn) => {
      const start = performance.now();
      const value = await fn();
      return { value, ms: Math.round(performance.now() - start) };
    };
    setBenching(true);
    setBenchResults([]);
    const results = [];
    for (const adapter of adapters) {
      try {
        const domainsRun = await timed(() => adapter.listDomains());
        const domain = domainsRun.value[0];
        const local = `b${Math.random().toString(36).slice(2, 10)}`;
        const openRun = await timed(() => adapter.getOrCreate({ local, domain }));
        const address = openRun.value.box.address;
        const fetchRuns = [];
        for (let i = 0; i < 3; i += 1) {
          const fetchRun = await timed(() => adapter.fetchMessages(address));
          fetchRuns.push(fetchRun.ms);
        }
        const avgFetch = Math.round(fetchRuns.reduce((sum, ms) => sum + ms, 0) / fetchRuns.length);
        results.push({
          id: adapter.id,
          label: adapter.label,
          ok: true,
          domain,
          address,
          domainsMs: domainsRun.ms,
          openMs: openRun.ms,
          fetchMs: avgFetch,
          fastestFetchMs: Math.min(...fetchRuns),
        });
      } catch (e) {
        results.push({ id: adapter.id, label: adapter.label, ok: false, error: e.message || String(e) });
      }
      setBenchResults([...results]);
    }
    setBenching(false);
  };

  const fastest = benchResults
    .filter((r) => r.ok && r.id !== "mock")
    .sort((a, b) => a.fetchMs - b.fetchMs)[0];

  return (
    <>
      <SRow
        label="Backend"
        hint="The mock runs entirely in your browser. Real backends call out over HTTPS — they may be blocked by CORS depending on where this is hosted."
      >
        <div />
      </SRow>

      <div className="backend-grid">
        {adapters.map((a) => (
          <button
            key={a.id}
            className={`backend-card ${backend === a.id ? "active" : ""}`}
            onClick={() => switchTo(a.id)}
          >
            <div className="backend-card-head">
              <span className="backend-name">{a.label}</span>
              {backend === a.id && <span className="backend-active-dot"></span>}
            </div>
            <div className="backend-desc">{a.description}</div>
            <div className="backend-caps">
              {a.supports?.shared && <span className="cap">shared</span>}
              {a.supports?.customDomains && <span className="cap">custom domains</span>}
              {a.supports?.simulateIncoming && <span className="cap">demo</span>}
              {!a.supports?.simulateIncoming && a.id !== "mock" && <span className="cap real">real mail</span>}
            </div>
          </button>
        ))}
      </div>

      <SRow
        label="Speed test"
        hint={fastest ? `${fastest.label} is currently fastest at polling (${fastest.fetchMs}ms avg).` : "Measure domain lookup, inbox open, and message polling from this device."}
      >
        <button className="btn" onClick={runBenchmarks} disabled={benching}>
          {benching ? <Icon name="refresh" size={12} className="spin" /> : <Icon name="zap" size={12} />}
          <span style={{ marginLeft: 6 }}>{benching ? "Testing..." : "Benchmark all"}</span>
        </button>
      </SRow>

      {benchResults.length > 0 && (
        <div className="api-list">
          {benchResults.map((r) => (
            <div className="api-row" key={r.id} style={{ gridTemplateColumns: "92px 1fr 92px" }}>
              <span className={`api-method ${r.ok ? "m-get" : "m-post"}`}>{r.id.toUpperCase()}</span>
              <span className="api-desc">
                {r.ok
                  ? `open ${r.openMs}ms · poll ${r.fetchMs}ms avg · best ${r.fastestFetchMs}ms`
                  : `Failed: ${r.error}`}
              </span>
              {r.ok && r.id !== backend ? (
                <button className="btn ghost" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => switchTo(r.id)}>Use</button>
              ) : (
                <span className="api-desc">{r.id === backend ? "active" : ""}</span>
              )}
            </div>
          ))}
        </div>
      )}

      <SRow
        label="Connection"
        hint={
          pingState
            ? pingState.ok
              ? `Reachable · ${pingState.ms}ms round-trip.`
              : `Failed: ${pingState.error}. Likely CORS — see note below.`
            : "Probe the active backend to confirm it responds."
        }
      >
        <button className="btn" onClick={ping} disabled={pinging}>
          {pinging ? <Icon name="refresh" size={12} className="spin" /> : <Icon name="zap" size={12} />}
          <span style={{ marginLeft: 6 }}>{pinging ? "Testing…" : "Test"}</span>
        </button>
      </SRow>

      <div className="hint" style={{ marginTop: 10 }}>
        <b>About each backend</b>
      </div>
      <div className="api-list">
        <div className="api-row" style={{ gridTemplateColumns: "80px 1fr" }}>
          <span className="api-method m-get">MOCK</span>
          <span className="api-desc">Fixture data in <code className="mono">localStorage</code>. Works offline. Inboxes are "shared" only across this device.</span>
        </div>
        <div className="api-row" style={{ gridTemplateColumns: "80px 1fr" }}>
          <span className="api-method m-post">GUERRILLA</span>
          <span className="api-desc"><code className="mono">api.guerrillamail.com</code> · browser session flow. Real inbound mail, 1h expiry. Their CORS policy is permissive.</span>
        </div>
        <div className="api-row" style={{ gridTemplateColumns: "80px 1fr" }}>
          <span className="api-method m-patch">TMAIL</span>
          <span className="api-desc"><code className="mono">dyzov.com/api</code> · stateless, address-keyed. Add your own key below.</span>
        </div>
      </div>

      {backend === "tmail" && (
        <SRow
          label="TMail API key"
          hint="The key from the docs is filled in by default. Replace with your own if you have one."
        >
          <input
            type="text"
            className="s-text"
            defaultValue={localStorage.getItem("tm:tmail:key") || "rc3PmHzkbUtl4eJBXxgG"}
            onBlur={(e) => localStorage.setItem("tm:tmail:key", e.target.value.trim())}
            spellCheck={false}
            style={{ minWidth: 220 }}
          />
        </SRow>
      )}

      <div className="hint" style={{ marginTop: 14 }}>
        <b>CORS note:</b> third-party APIs that don't send <code className="mono">Access-Control-Allow-Origin: *</code> can't be called from this page directly. If a real backend fails, host this UI behind a small proxy server and point the adapter at <code className="mono">/api</code> instead — the rest of the code doesn't change.
      </div>

      <div className="hint" style={{ marginTop: 10 }}>
        <b>Adapter interface</b> — implement these in <code className="mono">window.&lt;Name&gt;Adapter</code>, then call <code className="mono">window.__registerAdapter(adapter)</code>:
      </div>
      <div className="api-list">
        {[
          ["FN", "listDomains() → string[]", "Domains the user can pick from"],
          ["FN", "getOrCreate({local, domain}) → {box, isNew}", "Sign in or create mailbox"],
          ["FN", "fetchMessages(addr) → Message[]", "Poll for current inbox"],
          ["FN", "fetchMessage(addr, id) → Message", "Full body for one message"],
          ["FN", "deleteMessage(addr, id)", "Remove a message"],
          ["FN", "deleteMailbox(addr)", "Forget the address"],
        ].map(([m, p, d]) => (
          <div className="api-row" key={p}>
            <span className="api-method m-get">{m}</span>
            <code className="mono api-path">{p}</code>
            <span className="api-desc">{d}</span>
          </div>
        ))}
      </div>
    </>
  );
}

function fontHint(font) {
  return {
    geist: "Vercel's neutral grotesque — the default.",
    claude: "Source Serif 4 — same family Anthropic uses on claude.ai.",
    inter: "The web's workhorse. Clean and dense.",
    ibm: "IBM Plex Sans + Mono. Technical and crisp.",
    system: "Your OS's native UI font. Fastest, no download.",
  }[font] || "";
}

/* Small settings primitives */
function SRow({ label, hint, children }) {
  return (
    <div className="s-row">
      <div className="s-row-text">
        <div className="s-row-label">{label}</div>
        {hint && <div className="s-row-hint">{hint}</div>}
      </div>
      <div className="s-row-control">{children}</div>
    </div>
  );
}
function SegBtns({ value, onChange, options }) {
  return (
    <div className="segbtns">
      {options.map((o) => (
        <button
          key={o.value}
          className={`segbtn ${value === o.value ? "active" : ""}`}
          onClick={() => onChange(o.value)}
        >
          {o.icon && <Icon name={o.icon} size={12} />}
          {o.label}
        </button>
      ))}
    </div>
  );
}
function Toggle({ value, onChange }) {
  return (
    <button
      className={`toggle ${value ? "on" : ""}`}
      onClick={() => onChange(!value)}
      role="switch"
      aria-checked={value}
    >
      <span className="toggle-thumb"></span>
    </button>
  );
}

/* ---------- Mount ---------- */
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
