(() => {
  const anchors = {
    "top-left": [0, 0],
    "top-center": [-50, 0],
    "top-right": [-100, 0],
    "center-left": [0, -50],
    center: [-50, -50],
    "center-right": [-100, -50],
    "bottom-left": [0, -100],
    "bottom-center": [-50, -100],
    "bottom-right": [-100, -100]
  };

  function finite(value, fallback) {
    const number = Number(value);
    return Number.isFinite(number) ? number : fallback;
  }

  function applyBox(element, config) {
    const [translateX, translateY] = anchors[config.anchor] || anchors["top-left"];
    element.style.left = `${finite(config.x, 0)}%`;
    element.style.top = `${finite(config.y, 0)}%`;
    element.style.width = `${finite(config.width, 100)}%`;
    element.style.height = `${finite(config.height, 100)}%`;
    element.style.opacity = `${finite(config.opacity, 1)}`;
    element.style.transform = `translate(${translateX}%, ${translateY}%) translateZ(0)`;
  }

  function shrinkText(box, content, preferredSize, minimumSize, scale) {
    let size = preferredSize;
    const fit = () => {
      content.style.fontSize = `${size * scale}px`;
      if (size <= minimumSize || (content.scrollWidth <= box.clientWidth && content.scrollHeight <= box.clientHeight)) return;
      size = Math.max(minimumSize, size - Math.max(1, preferredSize / 24));
      requestAnimationFrame(fit);
    };
    requestAnimationFrame(fit);
  }

  function restartMediaElement(media) {
    if (!media) return false;
    const startAt = Math.max(0, finite(media.dataset.startAt, 0));
    try { media.currentTime = startAt; } catch {}
    const playback = media.play?.();
    playback?.catch?.(() => {});
    return true;
  }

  function buildMedia(module, values, options, renderType) {
    const media = document.createElement(renderType);
    media.className = `lumi-overlay-${renderType}`;
    media.src = values.url;
    media.dataset.originalUrl = values.url;
    media.dataset.moduleId = module.id;
    media.dataset.startAt = `${finite(values.startAt, 0)}`;
    media.preload = "auto";
    media.playsInline = true;
    media.loop = values.playBehavior === "loop";
    media.controls = values.showControls === true || values.playBehavior === "manual";
    media.muted = values.muted === true;
    media.volume = Math.min(1, Math.max(0, finite(values.volume, 1)));
    media.playbackRate = Math.min(4, Math.max(0.25, finite(values.playbackRate, 1)));
    media.autoplay = !options.editor && values.playBehavior !== "manual";
    media.referrerPolicy = "no-referrer";
    media.style.pointerEvents = !options.editor && media.controls ? "auto" : "none";
    if (renderType === "video") media.style.objectFit = values.fit || "contain";
    media.addEventListener("loadedmetadata", () => {
      const startAt = Math.max(0, finite(values.startAt, 0));
      if (startAt && startAt < finite(media.duration, Infinity)) {
        try { media.currentTime = startAt; } catch {}
      }
      media.playbackRate = Math.min(4, Math.max(0.25, finite(values.playbackRate, 1)));
      if (media.autoplay) media.play().catch(() => {});
    }, { once: true });
    return media;
  }

  function webFrameLayout(values) {
    const left = Math.min(95, Math.max(0, finite(values.cropLeft, 0)));
    const right = Math.min(95, Math.max(0, finite(values.cropRight, 0)));
    const top = Math.min(95, Math.max(0, finite(values.cropTop, 0)));
    const bottom = Math.min(95, Math.max(0, finite(values.cropBottom, 0)));
    const zoom = Math.min(5, Math.max(0.1, finite(values.zoom, 1)));
    const visibleWidth = Math.max(5, 100 - left - right);
    const visibleHeight = Math.max(5, 100 - top - bottom);
    return {
      width: (10000 * zoom) / visibleWidth,
      height: (10000 * zoom) / visibleHeight,
      left: (-100 * left * zoom) / visibleWidth,
      top: (-100 * top * zoom) / visibleHeight
    };
  }

  function buildWebsite(module, values, options) {
    const frame = document.createElement("iframe");
    frame.className = "lumi-overlay-frame";
    frame.src = values.url;
    frame.dataset.originalUrl = values.url;
    frame.dataset.moduleId = module.id;
    let sandbox = "allow-scripts allow-forms allow-popups allow-presentation";
    try {
      if (new URL(values.url, window.location.href).origin !== window.location.origin) sandbox += " allow-same-origin";
    } catch {}
    frame.sandbox = sandbox;
    frame.referrerPolicy = "no-referrer";
    frame.setAttribute("allow", "autoplay; fullscreen");
    frame.style.pointerEvents = options.editor ? "none" : (values.allowPointerEvents ? "auto" : "none");

    const layout = webFrameLayout(values);
    const root = document.createElement("div");
    root.className = "lumi-overlay-web-root";
    const shadow = root.attachShadow({ mode: "open" });
    const style = document.createElement("style");
    style.textContent = `
      :host { display: block; position: relative; width: 100%; height: 100%; overflow: hidden; }
      .lumi-overlay-frame {
        position: absolute; display: block; border: 0; background: transparent;
        width: ${layout.width}%; height: ${layout.height}%;
        left: ${layout.left}%; top: ${layout.top}%;
      }
      ${values.customCss || ""}
    `;
    shadow.append(style, frame);
    return root;
  }

  function findSourceContent(root, moduleId, selector) {
    const box = root?.querySelector?.(`.lumi-overlay-module[data-module-id="${CSS.escape(moduleId)}"]`);
    return box?.querySelector?.(selector) || box?.querySelector?.(".lumi-overlay-web-root")?.shadowRoot?.querySelector?.(selector) || null;
  }

  function buildModule(module, options = {}) {
    const renderType = module.renderType || module.type;
    const values = module.config || {};
    const scale = finite(options.scale, 1);
    const box = document.createElement("section");
    box.className = `lumi-overlay-module lumi-overlay-module-${renderType}`;
    if (options.editor) box.classList.add("is-editor-source");
    box.dataset.moduleId = module.id;
    box.dataset.moduleType = module.type;
    box.dataset.renderType = renderType;
    box.dataset.moduleName = module.name || "Source";
    applyBox(box, values);

    if (renderType === "text") {
      box.classList.add("lumi-overlay-text", `overflow-${values.overflow || "wrap"}`);
      const content = document.createElement("span");
      content.className = "lumi-overlay-text-content";
      content.textContent = values.text || "";
      const horizontal = values.horizontalAlign || values.align || "left";
      const vertical = values.verticalAlign || "center";
      box.style.textAlign = horizontal;
      box.style.justifyContent = horizontal === "center" ? "center" : horizontal === "right" ? "flex-end" : "flex-start";
      box.style.alignItems = vertical === "center" ? "center" : vertical === "bottom" ? "flex-end" : "flex-start";
      box.style.color = values.color || "#ffffff";
      box.style.background = values.background || "transparent";
      box.style.padding = `${finite(values.padding, 0) * scale}px`;
      content.style.fontWeight = `${finite(values.fontWeight, 700)}`;
      content.style.fontSize = `${finite(values.fontSize, 48) * scale}px`;
      box.appendChild(content);
      if (values.overflow === "shrink") shrinkText(box, content, finite(values.fontSize, 48), 8, scale);
    } else if (renderType === "image" && values.url) {
      const image = document.createElement("img");
      image.className = "lumi-overlay-image";
      image.alt = "";
      image.src = values.url;
      image.referrerPolicy = "no-referrer";
      image.style.objectFit = values.fit || "contain";
      box.appendChild(image);
    } else if ((renderType === "video" || renderType === "audio") && values.url) {
      const media = buildMedia(module, values, options, renderType);
      box.appendChild(media);
      if (renderType === "audio" && options.editor) {
        const placeholder = document.createElement("span");
        placeholder.className = "lumi-overlay-audio-placeholder";
        placeholder.textContent = "Audio source";
        box.appendChild(placeholder);
      }
    } else if (renderType === "web" && values.url) {
      box.appendChild(buildWebsite(module, values, options));
    }

    if (options.editor) {
      const tag = document.createElement("span");
      tag.className = "overlay-canvas-source-label";
      tag.textContent = module.name || "Source";
      box.appendChild(tag);
      for (const direction of ["nw", "n", "ne", "e", "se", "s", "sw", "w"]) {
        const handle = document.createElement("button");
        handle.type = "button";
        handle.className = `overlay-resize-handle handle-${direction}`;
        handle.dataset.resizeHandle = direction;
        handle.setAttribute("aria-label", `Resize ${module.name || "source"} from ${direction}`);
        box.appendChild(handle);
      }
    }
    return box;
  }

  function render(root, state, options = {}) {
    const canvas = options.canvas || state?.canvas || { width: 1920, height: 1080 };
    const widthScale = root.clientWidth ? root.clientWidth / finite(canvas.width, 1920) : 1;
    const heightScale = root.clientHeight ? root.clientHeight / finite(canvas.height, 1080) : widthScale;
    const scale = options.scale || Math.min(widthScale, heightScale);
    const fragment = document.createDocumentFragment();
    const elements = new Map();
    if (state?.enabled !== false && state?.scene) {
      for (const module of state.scene.modules || []) {
        if (module.enabled === false) continue;
        const element = buildModule(module, { ...options, scale });
        elements.set(module.id, element);
        fragment.appendChild(element);
      }
    }
    root.replaceChildren(fragment);
    return elements;
  }

  window.LumiOverlayRenderer = { anchors, applyBox, buildModule, findSourceContent, render, restartMediaElement };
})();
