// =================================================================
// Minimal rich-text editor for advertiser-authored article bodies (press releases, advertorials).
//
// This is an authoring convenience, NOT a security control. It emits the same small tag subset the
// server allowlist accepts, so what the advertiser sees is close to what will publish — but the
// server sanitizer is the authority, and it will strip anything this editor lets through. Never
// treat "the editor produced it" as a reason to trust markup.
// =================================================================

const EDITOR_TOOLS = [
  { cmd: "bold", label: "B", title: "Bold", style: { fontWeight: 700 } },
  { cmd: "italic", label: "I", title: "Italic", style: { fontStyle: "italic" } },
  { cmd: "formatBlock", arg: "<h2>", label: "H2", title: "Heading" },
  { cmd: "formatBlock", arg: "<h3>", label: "H3", title: "Subheading" },
  { cmd: "insertUnorderedList", label: "• List", title: "Bulleted list" },
  { cmd: "insertOrderedList", label: "1. List", title: "Numbered list" },
  { cmd: "formatBlock", arg: "<blockquote>", label: "Quote", title: "Block quote" },
  { cmd: "formatBlock", arg: "<p>", label: "¶", title: "Paragraph" },
];

// Mirrors the server allowlist in packages/api/src/sanitizeHtml.ts. Kept deliberately small: a tag
// the server will strip is worse than one the editor never offered, because the advertiser only
// discovers the loss after publishing.
const EDITOR_ALLOWED_TAGS = new Set([
  "P", "BR", "STRONG", "B", "EM", "I", "U", "S", "H2", "H3", "H4",
  "UL", "OL", "LI", "BLOCKQUOTE", "A", "IMG", "FIGURE", "FIGCAPTION", "HR",
]);

// Browser paste carries the source site's markup wholesale — fonts, spans, trackers, scripts.
// Walk it and keep only the allowlisted skeleton before it lands in the document.
const cleanPastedHtml = (html) => {
  const doc = new DOMParser().parseFromString(String(html || ""), "text/html");
  const walk = (node) => {
    [...node.childNodes].forEach(child => {
      if (child.nodeType === 3) return; // text
      if (child.nodeType !== 1) { child.remove(); return; }
      if (!EDITOR_ALLOWED_TAGS.has(child.tagName)) {
        // Unwrap rather than delete, so the prose inside a <div> or <span> survives.
        const parent = child.parentNode;
        while (child.firstChild) parent.insertBefore(child.firstChild, child);
        child.remove();
        return;
      }
      [...child.attributes].forEach(a => {
        const keep = (child.tagName === "A" && a.name === "href") ||
          (child.tagName === "IMG" && ["src", "alt"].includes(a.name));
        if (!keep) child.removeAttribute(a.name);
      });
      walk(child);
    });
  };
  walk(doc.body);
  return doc.body.innerHTML;
};

const wordCount = (html) => {
  const text = String(html || "").replace(/<[^>]*>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
  return text ? text.split(" ").length : 0;
};

const RichTextEditor = ({ value, onChange, minWords = 0, placeholder = "Write the article…" }) => {
  const ref = React.useRef(null);
  // The editor is uncontrolled once mounted: writing `value` back into innerHTML on every keystroke
  // would reset the caret to the start of the document on every character typed.
  React.useEffect(() => {
    if (ref.current && ref.current.innerHTML !== (value || "")) ref.current.innerHTML = value || "";
  }, []);

  const emit = () => onChange(ref.current ? ref.current.innerHTML : "");
  const exec = (tool) => {
    ref.current?.focus();
    document.execCommand(tool.cmd, false, tool.arg || null);
    emit();
  };
  const addLink = () => {
    const url = window.prompt("Link URL (https://…)");
    if (!url) return;
    if (!/^https?:\/\//i.test(url)) { window.__toast?.("Links must start with http:// or https://", "bad"); return; }
    ref.current?.focus();
    document.execCommand("createLink", false, url);
    emit();
  };
  const onPaste = (e) => {
    e.preventDefault();
    const html = e.clipboardData?.getData("text/html");
    const text = e.clipboardData?.getData("text/plain") || "";
    document.execCommand("insertHTML", false, html ? cleanPastedHtml(html) : text.replace(/[&<>]/g, ch => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[ch]));
    emit();
  };

  const words = wordCount(value);
  const short = minWords > 0 && words < minWords;

  return <div style={{ border: "1px solid var(--border)", borderRadius: "var(--r-md)", overflow: "hidden" }}>
    <div style={{ display: "flex", gap: 4, flexWrap: "wrap", padding: 6, borderBottom: "1px solid var(--border)", background: "var(--panel-2, transparent)" }}>
      {EDITOR_TOOLS.map((t, i) => (
        <button key={i} type="button" title={t.title} onClick={() => exec(t)}
          style={{ ...(t.style || {}), minWidth: 30, padding: "4px 8px", fontSize: 12.5, border: "1px solid var(--border)", borderRadius: 6, background: "var(--panel)", color: "var(--ink)", cursor: "pointer" }}>
          {t.label}
        </button>
      ))}
      <button type="button" title="Insert link" onClick={addLink}
        style={{ padding: "4px 8px", fontSize: 12.5, border: "1px solid var(--border)", borderRadius: 6, background: "var(--panel)", color: "var(--ink)", cursor: "pointer" }}>Link</button>
    </div>
    <div ref={ref} className="pw-rte-body" contentEditable suppressContentEditableWarning role="textbox" aria-multiline="true"
      data-placeholder={placeholder}
      onInput={emit} onBlur={emit} onPaste={onPaste}
      style={{ minHeight: 260, padding: "14px 16px", fontSize: 15, lineHeight: 1.65, outline: "none", background: "var(--panel)", color: "var(--ink)" }}/>
    <style>{`
      /* :empty alone is not enough — the browser leaves a stray <br> behind after the last
         character is deleted, so match that too or the placeholder never comes back. */
      .pw-rte-body:empty:before,
      .pw-rte-body:has(> br:only-child):before {
        content: attr(data-placeholder);
        color: var(--muted);
        pointer-events: none;
      }
      .pw-rte-body h2 { font-size: 20px; margin: 18px 0 8px; }
      .pw-rte-body h3 { font-size: 17px; margin: 16px 0 6px; }
      .pw-rte-body p { margin: 0 0 12px; }
      .pw-rte-body blockquote { margin: 14px 0; padding-left: 12px; border-left: 3px solid var(--line); color: var(--ink-2); }
      .pw-rte-body ul, .pw-rte-body ol { margin: 0 0 12px; padding-left: 22px; }
      .pw-rte-body a { color: var(--accent); }
    `}</style>
    <div style={{ display: "flex", justifyContent: "space-between", padding: "6px 10px", borderTop: "1px solid var(--border)", fontSize: 12, color: short ? "var(--danger, #dc2626)" : "var(--muted)" }}>
      <span>{words} {words === 1 ? "word" : "words"}</span>
      <span>{short ? `At least ${minWords} words required` : "Formatting outside the toolbar is removed on publish."}</span>
    </div>
  </div>;
};

Object.assign(window, { RichTextEditor, cleanPastedHtml, wordCount });
