import { jsx, Fragment } from 'react/jsx-runtime';
import { createContext, forwardRef, useState, useCallback, useMemo, useEffect, useContext } from 'react';
import hash from '../../../../../../../_virtual/index4.mjs';
import { createPortal } from 'react-dom';

const styleSelector = 'style, link[rel="stylesheet"]';
const collectStyles = (doc) => {
  const collected = [];
  doc.querySelectorAll(styleSelector).forEach((style) => {
    collected.push(style);
  });
  return collected;
};
const getStyleSheet = (el) => {
  return Array.from(document.styleSheets).find((ss) => {
    const ownerNode = ss.ownerNode;
    return ownerNode.href === el.href;
  });
};
const getStyles = (styleSheet) => {
  if (styleSheet) {
    try {
      return Array.from(styleSheet.cssRules).map((rule) => rule.cssText).join("");
    } catch (e) {
      console.warn(
        "Access to stylesheet %s is denied. Ignoring\u2026",
        styleSheet.href,
        e.message
      );
    }
  }
  return "";
};
const syncAttributes = (sourceElement, targetElement) => {
  const attributes = sourceElement.attributes;
  if (attributes?.length > 0) {
    Array.from(attributes).forEach((attribute) => {
      targetElement.setAttribute(attribute.name, attribute.value);
    });
  }
};
const defer = (fn) => setTimeout(fn, 0);
const CopyHostStyles = ({
  children,
  debug = false,
  onStylesLoaded = () => null
}) => {
  const { document: doc, window: win } = useFrame();
  useEffect(() => {
    if (!win || !doc) {
      return () => {
      };
    }
    const elements = [];
    const hashes = {};
    const lookupEl = (el) => elements.findIndex((elementMap) => elementMap.original === el);
    const mirrorEl = async (el, inlineStyles = false) => {
      let mirror;
      if (el.nodeName === "LINK" && inlineStyles) {
        mirror = document.createElement("style");
        mirror.type = "text/css";
        let styleSheet = getStyleSheet(el);
        if (!styleSheet) {
          await new Promise((resolve) => {
            const fn = () => {
              resolve();
              el.removeEventListener("load", fn);
            };
            el.addEventListener("load", fn);
          });
          styleSheet = getStyleSheet(el);
        }
        const styles = getStyles(styleSheet);
        if (!styles) {
          if (debug) {
            console.warn(
              `Tried to load styles for link element, but couldn't find them. Skipping...`
            );
          }
          return;
        }
        mirror.innerHTML = styles;
        mirror.setAttribute("data-href", el.getAttribute("href"));
      } else {
        mirror = el.cloneNode(true);
      }
      return mirror;
    };
    const addEl = async (el) => {
      const index = lookupEl(el);
      if (index > -1) {
        if (debug)
          console.log(
            `Tried to add an element that was already mirrored. Updating instead...`
          );
        const element = elements[index];
        if (element?.mirror) {
          element.mirror.innerText = el.innerText;
        }
        return;
      }
      const mirror = await mirrorEl(el);
      if (!mirror) {
        return;
      }
      const elHash = hash(mirror.outerHTML);
      if (hashes[elHash]) {
        if (debug)
          console.log(
            `iframe already contains element that is being mirrored. Skipping...`
          );
        return;
      }
      hashes[elHash] = true;
      doc.head.append(mirror);
      elements.push({ original: el, mirror });
      if (debug) console.log(`Added style node ${el.outerHTML}`);
    };
    const removeEl = (el) => {
      const index = lookupEl(el);
      if (index === -1) {
        if (debug)
          console.log(
            `Tried to remove an element that did not exist. Skipping...`
          );
        return;
      }
      const elHash = hash(el.outerHTML);
      elements[index]?.mirror?.remove();
      delete hashes[elHash];
      if (debug) console.log(`Removed style node ${el.outerHTML}`);
    };
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === "childList") {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE) {
              const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
              if (el && el.matches(styleSelector)) {
                defer(() => addEl(el));
              }
            }
          });
          mutation.removedNodes.forEach((node) => {
            if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE) {
              const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
              if (el && el.matches(styleSelector)) {
                defer(() => removeEl(el));
              }
            }
          });
        }
      });
    });
    const parentDocument = win.parent.document;
    const collectedStyles = collectStyles(parentDocument);
    const hrefs = [];
    let stylesLoaded = 0;
    const parentHtml = parentDocument.getElementsByTagName("html")[0];
    syncAttributes(parentHtml, doc.documentElement);
    const parentBody = parentDocument.getElementsByTagName("body")[0];
    syncAttributes(parentBody, doc.body);
    doc.body.style.backgroundColor = "transparent";
    const parentComputedStyle = win.parent.getComputedStyle(parentHtml);
    for (let i = 0; i < parentComputedStyle.length; i++) {
      const property = parentComputedStyle[i];
      if (property?.startsWith("--")) {
        const value = parentComputedStyle.getPropertyValue(property);
        doc.documentElement.style.setProperty(property, value);
        doc.body.style.setProperty(property, value);
      }
    }
    parentBody?.classList.forEach((className) => {
      if (className.includes("font-") || className === "antialiased" || className.includes("__variable")) {
        doc.body.classList.add(className);
      }
    });
    Promise.all(
      collectedStyles.map(async (styleNode) => {
        if (styleNode.nodeName === "LINK") {
          const linkHref = styleNode.href;
          if (hrefs.indexOf(linkHref) > -1) {
            return;
          }
          hrefs.push(linkHref);
        }
        const mirror = await mirrorEl(styleNode);
        if (!mirror) return;
        elements.push({ original: styleNode, mirror });
        return mirror;
      })
    ).then((mirrorStyles) => {
      const filtered = mirrorStyles.filter(
        (el) => typeof el !== "undefined"
      );
      filtered.forEach((mirror) => {
        mirror.onload = () => {
          stylesLoaded = stylesLoaded + 1;
          if (stylesLoaded >= elements.length) {
            onStylesLoaded();
          }
        };
        mirror.onerror = () => {
          console.warn(`AutoFrame couldn't load a stylesheet`);
          stylesLoaded = stylesLoaded + 1;
          if (stylesLoaded >= elements.length) {
            onStylesLoaded();
          }
        };
      });
      doc.head.innerHTML = "";
      doc.head.append(...filtered);
      observer.observe(parentDocument.head, { childList: true, subtree: true });
      filtered.forEach((el) => {
        const elHash = hash(el.outerHTML);
        hashes[elHash] = true;
      });
    });
    return () => {
      observer.disconnect();
    };
  }, [debug, doc, onStylesLoaded, win]);
  return /* @__PURE__ */ jsx(Fragment, { children });
};
const autoFrameContext = createContext({});
const useFrame = () => useContext(autoFrameContext);
const AutoFrame = forwardRef(({
  children,
  className,
  debug,
  id,
  onReady = () => {
  },
  onNotReady = () => {
  },
  style,
  pointerEventsEnabled = true,
  ...props
}, ref) => {
  const [loaded, setLoaded] = useState(false);
  const [ctx, setCtx] = useState({});
  const [mountTarget, setMountTarget] = useState();
  const [stylesLoaded, setStylesLoaded] = useState(false);
  const handleLoad = useCallback(() => {
    setLoaded(true);
  }, []);
  const handleStylesLoaded = useCallback(() => {
    setStylesLoaded(true);
  }, []);
  const iframeStyle = useMemo(
    () => ({
      pointerEvents: pointerEventsEnabled ? "all" : "none",
      ...style
    }),
    [pointerEventsEnabled, style]
  );
  useEffect(() => {
    if (ref && "current" in ref && ref.current) {
      const doc = ref.current.contentDocument;
      const win = ref.current.contentWindow;
      setCtx({
        document: doc || void 0,
        window: win || void 0
      });
      setMountTarget(
        ref.current.contentDocument?.getElementById("frame-root")
      );
      if (doc && win && stylesLoaded) {
        onReady();
      } else {
        onNotReady();
      }
    }
  }, [ref, loaded, stylesLoaded]);
  return /* @__PURE__ */ jsx(
    "iframe",
    {
      ...props,
      className,
      id,
      "data-testid": "auto-frame",
      srcDoc: '<!DOCTYPE html><html><head></head><body><div id="frame-root" data-autoform-entry style="display: contents;"></div></body></html>',
      ref,
      style: iframeStyle,
      onLoad: handleLoad,
      children: /* @__PURE__ */ jsx(autoFrameContext.Provider, { value: ctx, children: loaded && mountTarget && /* @__PURE__ */ jsx(CopyHostStyles, { debug, onStylesLoaded: handleStylesLoaded, children: createPortal(children, mountTarget) }) })
    }
  );
});
AutoFrame.displayName = "AutoFrame";

export { autoFrameContext, AutoFrame as default, useFrame };
