export default function(editor) {
  let toolbar, serializer, touchOnImage, pasteInCaption;
  let resizeGhostElement;

  function isPlaceholder(node) {
    return !!(
      editor.dom.getAttrib(node, "data-mce-placeholder") ||
      editor.dom.getAttrib(node, "data-mce-object")
    );
  }

  function isNonEditable(node) {
    const parent = tinymce.$(node).parents("[contenteditable]");
    return parent && parent.attr("contenteditable") === "false";
  }

  function hasTextContent(node) {
    return node && !!(node.textContent || node.innerText).replace(/\ufeff/g, "");
  }

  function verifyHTML(caption) {
    if (!caption || (caption.indexOf("<") === -1 && caption.indexOf(">") === -1)) {
      return caption;
    }

    if (!serializer) {
      serializer = new tinymce.html.Serializer({}, editor.schema);
    }

    return serializer.serialize(
      editor.parser.parse(caption, { forced_root_block: false })
    );
  }

  function extractImageData(imageNode) {
    const dom = editor.dom;
    const isIntRegExp = /^\d+$/;
    let width, height, classes, extraClasses = [];
    let captionBlock, caption, link;
    let captionClassName = [];

    // Default metadata
    const metadata = {
      attachment_id: false,
      size: "custom",
      caption: "",
      align: "none",
      extraClasses: "",
      link: false,
      linkUrl: "",
      linkClassName: "",
      linkTargetBlank: false,
      linkRel: "",
      title: "",
      url: dom.getAttrib(imageNode, "src"),
      alt: dom.getAttrib(imageNode, "alt"),
      title: dom.getAttrib(imageNode, "title")
    };

    // Get width and height
    width = dom.getAttrib(imageNode, "width");
    height = dom.getAttrib(imageNode, "height");

    if (!isIntRegExp.test(width) || parseInt(width, 10) < 1) {
      width = imageNode.naturalWidth || imageNode.width;
    }

    if (!isIntRegExp.test(height) || parseInt(height, 10) < 1) {
      height = imageNode.naturalHeight || imageNode.height;
    }

    metadata.customWidth = metadata.width = width;
    metadata.customHeight = metadata.height = height;

    // Extract classes
    classes = tinymce.explode(imageNode.className, " ");
    tinymce.each(classes, function(name) {
      if (/^wp-image/.test(name)) {
        metadata.attachment_id = parseInt(name.replace("wp-image-", ""), 10);
      } else if (/^align/.test(name)) {
        metadata.align = name.replace("align", "");
      } else if (/^size/.test(name)) {
        metadata.size = name.replace("size-", "");
      } else {
        extraClasses.push(name);
      }
    });

    metadata.extraClasses = extraClasses.join(" ");

    // Extract caption
    captionBlock = dom.getParents(imageNode, ".wp-caption");
    if (captionBlock.length) {
      captionBlock = captionBlock[0];
      classes = captionBlock.className.split(" ");
      
      tinymce.each(classes, function(name) {
        if (/^align/.test(name)) {
          metadata.align = name.replace("align", "");
        } else if (name && name !== "wp-caption") {
          captionClassName.push(name);
        }
      });

      metadata.captionClassName = captionClassName.join(" ");

      caption = dom.select("dd.wp-caption-dd", captionBlock);
      if (caption.length) {
        caption = caption[0];
        metadata.caption = editor.serializer
          .serialize(caption)
          .replace(/<br[^>]*>/g, "$&\n")
          .replace(/^<p>/, "")
          .replace(/<\/p>$/, "");
      }
    }

    // Extract link
    if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "a") {
      link = imageNode.parentNode;
      metadata.linkUrl = dom.getAttrib(link, "href");
      metadata.linkTargetBlank = dom.getAttrib(link, "target") === "_blank";
      metadata.linkRel = dom.getAttrib(link, "rel");
      metadata.linkClassName = link.className;
    }

    return metadata;
  }

  function updateImage(imageNode, imageData) {
    if (!imageNode) return;

    const dom = editor.dom;
    let classes = tinymce.explode(imageData.extraClasses, " ") || [];
    let linkNode, captionNode, dd, dl, id, attrs, linkAttrs;

    if (!imageData.caption) {
      classes.push("align" + imageData.align);
    }

    if (imageData.attachment_id) {
      classes.push("wp-image-" + imageData.attachment_id);
      if (imageData.size && imageData.size !== "custom") {
        classes.push("size-" + imageData.size);
      }
    }

    let width = imageData.width;
    let height = imageData.height;

    if (imageData.size === "custom") {
      width = imageData.customWidth;
      height = imageData.customHeight;
    }

    // Update image attributes
    attrs = {
      src: imageData.url,
      width: width || null,
      height: height || null,
      title: imageData.title || null,
      class: classes.join(" ") || null,
      alt: imageData.alt || ""
    };

    dom.setAttribs(imageNode, attrs);

    // Handle link
    if (imageData.linkUrl) {
      linkAttrs = {
        href: imageData.linkUrl,
        class: imageData.linkClassName || "image-link",
        target: imageData.linkTargetBlank ? "_blank" : null,
        rel: imageData.linkRel || null
      };

      if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "a") {
        dom.setAttribs(imageNode.parentNode, linkAttrs);
      } else {
        linkNode = dom.create("a", linkAttrs);
        imageNode.parentNode.insertBefore(linkNode, imageNode);
        linkNode.appendChild(imageNode);
      }
    } else if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "a") {
      dom.remove(imageNode.parentNode, true);
    }

    // Handle caption
    if (imageData.caption) {
      const caption = verifyHTML(imageData.caption);
      if (!caption) {
        if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "dl") {
          dom.remove(imageNode.parentNode.parentNode, true);
        }
        return;
      }

      id = imageData.attachment_id ? "attachment_" + imageData.attachment_id : "";
      const align = "align" + (imageData.align || "none");
      classes = ["wp-caption", align];

      if (imageData.captionClassName) {
        classes = classes.concat(tinymce.explode(imageData.captionClassName, " "));
      }

      if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "dl") {
        dl = imageNode.parentNode;
        dd = dom.select("dd.wp-caption-dd", dl)[0];
        dom.setAttribs(dl, {
          id: id,
          class: classes.join(" ")
        });
        dom.setHTML(dd, caption);
      } else {
        const captionHtml = `<dl ${id ? 'id="' + id + '"' : ''} class="${classes.join(" ")}">
          <dt class="wp-caption-dt"></dt>
          <dd class="wp-caption-dd">${caption}</dd>
        </dl>`;
        
        captionNode = dom.create("div", { class: "mceTemp" }, captionHtml);
        captionNode = captionNode.cloneNode(true);
        
        if (imageNode.parentNode) {
          dom.insertAfter(captionNode, imageNode);
          captionNode.insertBefore(imageNode, captionNode.firstChild);
        }
      }
    } else if (imageNode.parentNode && imageNode.parentNode.nodeName.toLowerCase() === "dl") {
      dom.remove(imageNode.parentNode.parentNode, true);
    }

    editor.nodeChanged();
  }

  function editImage(imageNode) {
    let frame, callback;
    const imageData = extractImageData(imageNode);

    // Check if WordPress media is available
    if (typeof wp === "undefined" || !wp.media) {
      editor.execCommand("mceImage");
      return;
    }

    if (imageData.attachment_id) {
      // Mark the image node so we can select it later
      tinymce.$(imageNode).attr("data-wp-editing", 1);

      // Trigger event for media modal
      wp.media.events.trigger("editor:image-edit", {
        editor: editor,
        metadata: imageData,
        image: imageNode
      });

      // Create media frame for editing
      frame = wp.media({
        frame: "image",
        state: "image-details",
        metadata: imageData
      });

      wp.media.events.trigger("editor:frame-create", { frame: frame });

      callback = (newImageData) => {
        editor.undoManager.transact(function() {
          updateImage(imageNode, newImageData);
        });

        editor.focus();
        frame.detach();
      };

      frame.state("image-details").on("update", callback);
      frame.state("replace-image").on("replace", callback);
    } else {
      // Create media frame for inserting new image
      frame = wp.media.frames.items = wp.media({
        title: "Insert image",
        library: {
          type: ["image", "video"]
        }
      });

      frame.on("select", function() {
        const image = frame.state().get("selection").first().toJSON();
        const newImage = Object.assign(image, {
          align: "",
          caption: "",
          classes: [`wp-image-${image.id}`],
          size: "full"
        });
        const imgHtml = wp.media.string.image(newImage);
        editor.insertContent(imgHtml);
      });

      frame.on("close", function() {
        editor.focus();
        frame.detach();
      });
    }

    frame.open();
  }

  function removeImage(node) {
    const dom = editor.dom;
    const parent = node.parentNode;

    if (parent && parent.nodeName.toLowerCase() === "dl") {
      dom.remove(parent.parentNode);
    } else {
      dom.remove(node);
    }

    editor.nodeChanged();
  }

  // Register the image button
  editor.ui.registry.addButton("tdimage", {
    icon: "image",
    tooltip: "Edit Image",
    onAction: () => {
      editImage(editor.selection.getNode());
    }
  });

  // Create toolbar for images
  editor.once("preinit", function() {
    if (editor.wp && editor.wp._createToolbar) {
      toolbar = editor.wp._createToolbar([
        "wp_img_alignleft",
        "wp_img_aligncenter",
        "wp_img_alignright",
        "wp_img_alignnone",
        "wp_img_edit",
        "wp_img_remove"
      ]);
    }
  });

  // Show toolbar on image selection
  editor.on("wptoolbar", function(event) {
    if (event.element.nodeName.toLowerCase() === "img" && !isPlaceholder(event.element)) {
      event.toolbar = toolbar;
    }
  });

  // Handle iOS touch events
  if (tinymce.Env.iOS) {
    editor.on("init", function() {
      editor.on("touchstart", function(event) {
        if (event.target.nodeName.toLowerCase() === "img" && !isNonEditable(event.target)) {
          touchOnImage = true;
        }
      });

      editor.dom.bind(editor.getDoc(), "touchmove", function() {
        touchOnImage = false;
      });

      editor.on("touchend", function(event) {
        if (touchOnImage && event.target.nodeName.toLowerCase() === "img" && !isNonEditable(event.target)) {
          const node = event.target;
          touchOnImage = false;

          window.setTimeout(function() {
            editor.selection.select(node);
            editor.nodeChanged();
          }, 100);
        } else if (toolbar) {
          toolbar.hide();
        }
      });
    });
  }

  // Handle image resizing ghost element
  resizeGhostElement = function(event) {
    const dom = editor.dom;
    const ghostElement = dom.get('mceResizeGhost');
    if (ghostElement) {
      const body = dom.getParent(ghostElement, 'body');
      const bodyWidth = body.offsetWidth;
      const percent = Math.round(((event.clientX / bodyWidth) * 100) / 5) * 5;
      
      if (percent > 100) {
        dom.setStyle(ghostElement, 'width', '');
      } else {
        dom.setStyle(ghostElement, 'width', percent + '%');
      }
    }
  };

  // Handle image resizing
  editor.on("ObjectResizeStart", function(event) {
    const element = event.target;
    if (element.nodeName.toLowerCase() === "img") {
      try {
        const dom = editor.dom;
        // Store original dimensions
        element.setAttribute("data-width", element.width);
        element.setAttribute("data-height", element.height);
        element.setAttribute("data-original-style", element.getAttribute("style") || "");

        const editableDoc = editor.getDoc();
        dom.bind(editableDoc, "mousemove", resizeGhostElement);
      } catch (error) {
        console.error("Error in ObjectResizeStart:", error);
      }
    }
  });

  editor.on("ObjectResized", function(event) {
    try {
      const dom = editor.dom;
      const node = event.target;
      
      if (node.nodeName.toLowerCase() === "img") {
        const body = dom.getParent(node, "body");
        const bodyWidth = body.offsetWidth;
        const captionBlock = dom.getParents(node, ".wp-caption");

        // Clean up resize event binding
        const editableDoc = editor.getDoc();
        dom.unbind(editableDoc, "mousemove", resizeGhostElement);

        const oldWidth = node.getAttribute("data-width");
        const oldHeight = node.getAttribute("data-height");
        const originalStyle = node.getAttribute("data-original-style");

        if (oldWidth && oldHeight) {
          // Restore original dimensions
          node.width = oldWidth;
          node.height = oldHeight;

          // Calculate percentage width
          const percent = Math.round(((event.width / bodyWidth) * 100) / 5) * 5;

          // Apply appropriate styling
          if (percent > 100) {
            if (captionBlock.length) {
              tinymce.$(captionBlock).css({ width: "" });
              node.setAttribute("data-mce-style", originalStyle || "");
            } else {
              node.setAttribute("data-mce-style", originalStyle || "");
            }
          } else {
            if (captionBlock.length) {
              tinymce.$(captionBlock).css({ width: `${percent}%` });
              node.setAttribute("data-mce-style", originalStyle || "");
            } else {
              node.setAttribute("data-mce-style", `${originalStyle || ""} width: ${percent}%;`.trim());
            }
          }

          // Clean up temporary attributes
          node.removeAttribute("data-width");
          node.removeAttribute("data-height");
          node.removeAttribute("data-original-style");
        }
      }
    } catch (error) {
      console.error("Error in ObjectResized:", error);
    }
  });

  // Add paste handling
  editor.on("pastePostProcess", function(event) {
    // Handle pasting in caption nodes
    if (editor.dom.getParent(editor.selection.getNode(), "dd.wp-caption-dd")) {
      // Remove non-block elements that shouldn't be in captions
      editor.$("img, audio, video, object, embed, iframe, script, style", event.node).remove();

      // Process block elements in captions
      tinymce.$("*", event.node).each(function(i, node) {
        if (editor.dom.isBlock(node)) {
          if (tinymce.trim(node.textContent || node.innerText)) {
            editor.dom.insertAfter(editor.dom.create("br"), node);
            editor.dom.remove(node, true);
          } else {
            editor.dom.remove(node);
          }
        }
      });

      // Clean up <br> tags
      tinymce.$("br", event.node).each(function(i, node) {
        if (!node.nextSibling || node.nextSibling.nodeName === "BR" ||
            !node.previousSibling || node.previousSibling.nodeName === "BR") {
          editor.dom.remove(node);
        }
      });

      pasteInCaption = true;
    }
  });

  // Add command execution handling
  editor.on("BeforeExecCommand", function(event) {
    const node = editor.selection.getNode();
    const cmd = event.command;
    const dom = editor.dom;

    if (cmd === "mceInsertContent" || cmd === "Indent" || cmd === "Outdent") {
      const captionParent = dom.getParent(node, "div.mceTemp");

      if (captionParent) {
        if (cmd === "mceInsertContent") {
          if (pasteInCaption) {
            pasteInCaption = false;
            return;
          }

          // Create new paragraph for content outside caption
          const p = dom.create("p");
          dom.insertAfter(p, captionParent);
          editor.selection.setCursorLocation(p, 0);

          // Handle image replacement
          if (node.nodeName.toLowerCase() === "img") {
            tinymce.$(captionParent).remove();
          }

          editor.nodeChanged();
        } else {
          // Prevent breaking captions with indent/outdent
          event.preventDefault();
          event.stopImmediatePropagation();
          return false;
        }
      }
    } else if (["JustifyLeft", "JustifyRight", "JustifyCenter", "wpAlignNone"].includes(cmd)) {
      const align = "align" + cmd.slice(7).toLowerCase();
      const DL = editor.dom.getParent(node, ".wp-caption");

      if (node.nodeName !== "IMG" && !DL) return;

      const targetNode = DL || node;
      const replacement = editor.dom.hasClass(targetNode, align) ? " alignnone" : " " + align;

      targetNode.className = tinymce.trim(
        targetNode.className.replace(/ ?align(left|center|right|none)/g, "") + replacement
      );

      editor.nodeChanged();
      event.preventDefault();

      if (toolbar) {
        toolbar.reposition();
      }

      editor.fire("ExecCommand", {
        command: cmd,
        ui: event.ui,
        value: event.value
      });
    }
  });

  // Add keyboard event handling
  editor.on("keydown", function(event) {
    const selection = editor.selection;
    const keyCode = event.keyCode;
    const dom = editor.dom;
    const VK = tinymce.util.VK;

    if (keyCode === VK.ENTER) {
      // Handle Enter key in captions
      const node = selection.getNode();
      const wrap = dom.getParent(node, "div.mceTemp");

      if (wrap) {
        dom.events.cancel(event);

        // Clean up empty dt and dd elements
        tinymce.each(dom.select("dt, dd", wrap), function(element) {
          if (dom.isEmpty(element)) {
            dom.remove(element);
          }
        });

        const spacer = tinymce.Env.ie && tinymce.Env.ie < 11 ? "" : '<br data-mce-bogus="1" />';
        const P = dom.create("p", null, spacer);

        if (node.nodeName.toLowerCase() === "dd") {
          dom.insertAfter(P, wrap);
        } else {
          wrap.parentNode.insertBefore(P, wrap);
        }

        editor.nodeChanged();
        selection.setCursorLocation(P, 0);
      }
    } else if (keyCode === VK.DELETE || keyCode === VK.BACKSPACE) {
      // Handle Delete/Backspace for images and captions
      const node = selection.getNode();
      let wrap;

      if (node.nodeName.toLowerCase() === "div" && dom.hasClass(node, "mceTemp")) {
        wrap = node;
      } else if (["img", "dt", "a"].includes(node.nodeName.toLowerCase())) {
        wrap = dom.getParent(node, "div.mceTemp");
      }

      if (wrap) {
        dom.events.cancel(event);
        removeImage(node);
        return false;
      }
    }
  });

  // Add undo/redo handling for Firefox
  if (tinymce.Env.gecko) {
    editor.on("undo redo", function() {
      if (editor.selection.getNode().nodeName.toLowerCase() === "img") {
        editor.selection.collapse();
      }
    });
  }

  // Add content processing handlers
  editor.on("beforeGetContent", function(event) {
    if (event.format !== "raw") {
      tinymce.$('img[id="__wp-temp-img-id"]').removeAttr("id");
    }
  });

  editor.on("BeforeSetContent", function(event) {
    if (event.format !== "raw") {
      event.content = editor.wpSetImgCaption(event.content);
    }
  });

  editor.on("PostProcess", function(event) {
    if (event.get) {
      event.content = editor.wpGetImgCaption(event.content);
    }
  });

  // Add drag and drop handling
  let dragWrap;
  editor.on("dragstart", function() {
    const node = editor.selection.getNode();

    if (node.nodeName.toLowerCase() === "img") {
      dragWrap = editor.dom.getParent(node, ".mceTemp");

      if (!dragWrap && node.parentNode.nodeName.toLowerCase() === "a" && !hasTextContent(node.parentNode)) {
        dragWrap = node.parentNode;
      }
    }
  });

  editor.on("drop", function(event) {
    const dom = editor.dom;
    const rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(
      event.clientX,
      event.clientY,
      editor.getDoc()
    );

    // Prevent dropping in captioned images
    if (rng && dom.getParent(rng.startContainer, ".mceTemp")) {
      event.preventDefault();
    } else if (dragWrap) {
      event.preventDefault();

      editor.undoManager.transact(function() {
        if (rng) {
          editor.selection.setRng(rng);
        }

        editor.selection.setNode(dragWrap);
        dom.remove(dragWrap);
      });
    }

    dragWrap = null;
  });

  // Add to editor.wp
  editor.wp = editor.wp || {};
  editor.wp.isPlaceholder = isPlaceholder;
  editor.wpSetImgCaption = function(content) {
    return parseShortcode(content);
  };
  editor.wpGetImgCaption = function(content) {
    return getShortcode(content);
  };

  // Add shortcode parsing functions
  function parseShortcode(content) {
    return content.replace(
      /(?:<p>)?\[(?:wp_)?caption([^\]]+)\]([\s\S]+?)\[\/(?:wp_)?caption\](?:<\/p>)?/g,
      function(a, b, c) {
        let id, align, classes, caption, style, img, width;

        // Extract id
        id = b.match(/id=['"]([^'"]*)['"] ?/);
        if (id) {
          b = b.replace(id[0], "");
        }

        // Extract align
        align = b.match(/align=['"]([^'"]*)['"] ?/);
        if (align) {
          b = b.replace(align[0], "");
        }

        // Extract classes
        classes = b.match(/class=['"]([^'"]*)['"] ?/);
        if (classes) {
          b = b.replace(classes[0], "");
        }

        // Extract width
        width = b.match(/width=['"]([0-9]*)['"] ?/);
        if (width) {
          b = b.replace(width[0], "");
        }

        c = tinymce.trim(c);
        img = c.match(/((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)([\s\S]*)/i);

        if (img && img[2]) {
          caption = tinymce.trim(img[2]);
          img = tinymce.trim(img[1]);

          style = img.match(/<img\s+[^>]*style="([^"]*)"[^>]*>/i);
          if (style && style[1]) {
            img = img.replace(` style="${style[1]}"`, "");
            style = style[1];
          }
        } else {
          // Old captions shortcode style
          caption = tinymce.trim(b)
            .replace(/caption=['"]/, "")
            .replace(/['"]$/, "");
          img = c;
        }

        style = style ? ` style="${style}"` : "";

        id = id && id[1] ? id[1].replace(/[<>&]+/g, "") : "";
        align = align && align[1] ? align[1] : "alignnone";
        classes = classes && classes[1] ? " " + classes[1].replace(/[<>&]+/g, "") : "";

        if (!width && img) {
          width = img.match(/width=['"]([0-9]*)['"]/);
        }

        if (width && width[1]) {
          width = width[1];
        }

        if (!width || !caption) {
          return c;
        }

        width = parseInt(width, 10);
        if (!editor.getParam("wpeditimage_html5_captions")) {
          width += 10;
        }

        return `<div class="mceTemp"><dl id="${id}" class="wp-caption ${align}${classes}"${style}><dt class="wp-caption-dt">${img}</dt><dd class="wp-caption-dd wp-caption-text">${caption}</dd></dl></div>`;
      }
    );
  }

  function getShortcode(content) {
    return content.replace(
      /(?:<div [^>]+mceTemp[^>]+>)?\s*(<dl [^>]+wp-caption[^>]+>[\s\S]+?<\/dl>)\s*(?:<\/div>)?/g,
      function(all, dl) {
        let out = "";

        if (dl.indexOf("<img ") === -1 || dl.indexOf("</p>") !== -1) {
          // Broken caption. The user managed to drag the image out or type in the wrapper div?
          // Remove the <dl>, <dd> and <dt> and return the remaining text.
          return dl
            .replace(/<d[ldt]( [^>]+)?>/g, "")
            .replace(/<\/d[ldt]>/g, "");
        }

        out = dl.replace(
          /\s*<dl ([^>]+)>\s*<dt [^>]+>([\s\S]+?)<\/dt>\s*<dd [^>]+>([\s\S]*?)<\/dd>\s*<\/dl>\s*/gi,
          function(a, b, c, caption) {
            let id, classes, align, style, width;

            width = c.match(/width="([0-9]*)"/);
            width = width && width[1] ? width[1] : "";

            classes = b.match(/class="([^"]*)"/);
            classes = classes && classes[1] ? classes[1] : "";
            align = classes.match(/align[a-z]+/i) || "alignnone";

            if (!width || !caption) {
              if ("alignnone" !== align[0]) {
                c = c.replace(/><img/, ' class="' + align[0] + '"><img');
              }
              return c;
            }

            style = a.match(/style="([^"]*)"/);

            if (style && style[1]) {
              width = style[1];
            }

            id = b.match(/id="([^"]*)"/);
            id = id && id[1] ? id[1] : "";

            classes = classes.replace(/wp-caption ?|align[a-z]+ ?/gi, "");

            if (classes) {
              classes = ' class="' + classes + '"';
            }

            caption = caption
              .replace(/\r\n|\r/g, "\n")
              .replace(/<[a-zA-Z0-9]+( [^<>]+)?>/g, function(a) {
                // No line breaks inside HTML tags.
                return a.replace(/[\r\n\t]+/, " ");
              });

            // Convert remaining line breaks to <br>.
            caption = caption.replace(/\s*\n\s*/g, "<br />");

            return `[caption id="${id}" align="${align}" width="${width}"${classes}]${c}${caption}[/caption]`;
          }
        );

        if (out.indexOf("[caption") === -1) {
          // The caption html seems broken, try to find the image that may be wrapped in a link
          // and may be followed by <p> with the caption text.
          out = dl.replace(
            /[\s\S]*?((?:<a [^>]+>)?<img [^>]+>(?:<\/a>)?)(<p>[\s\S]*<\/p>)?[\s\S]*/gi,
            "<p>$1</p>$2"
          );
        }

        return out;
      }
    );
  }
} 