import { encodeBase64, escapeAttribute, escapeHtml, escapeScript, escapeStyle, } from './artifact-escape.js';
import { applyAssetResolution, artifactCsp } from './artifact-asset-resolution.js';
import { frameStepperSelector, frameStepperVisibilityCss } from './artifact-frame-selectors.js';
/** The neutral gray the hosted app presents artifacts on. The web stage rule
 *  (.artifact-stage[data-view-mode='fullscreen'] in ArtifactEntityView.svelte)
 *  must use the same value — a source-sync test pins it. */
export const ARTIFACT_PRESENT_BACKDROP = '#4a4a4a';
function stripNetworkAssetTags(html) {
    return html
        .replace(/<link\b(?=[^>]*\brel\s*=\s*(?:"[^"]*\bstylesheet\b[^"]*"|'[^']*\bstylesheet\b[^']*'|[^\s>]*stylesheet[^\s>]*))[^>]*>/gi, '')
        .replace(/<script\b(?=[^>]*\bsrc\s*=)[^>]*>[\s\S]*?<\/script\s*>/gi, '');
}
function scriptTypeFromAttributes(attrs) {
    const match = attrs.match(/\btype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
    return (match?.[1] ?? match?.[2] ?? match?.[3] ?? null)?.trim().toLowerCase() ?? null;
}
function executableScriptKind(attrs) {
    const type = scriptTypeFromAttributes(attrs);
    if (!type || type === 'text/javascript' || type === 'application/javascript' || type === 'text/ecmascript' || type === 'application/ecmascript') {
        return 'classic';
    }
    return type === 'module' ? 'module' : null;
}
function scriptKindForPath(path) {
    return path.toLowerCase().endsWith('.mjs') ? 'module' : 'classic';
}
function deferInlineScripts(html) {
    const scripts = [];
    const nextHtml = html.replace(/<script\b((?:(?!\bsrc\s*=)[^>])*)>([\s\S]*?)<\/script\s*>/gi, (match, attrs, js) => {
        const kind = executableScriptKind(attrs);
        if (!kind)
            return match;
        if (js.trim())
            scripts.push({ path: `inline-${scripts.length + 1}.${kind === 'module' ? 'mjs' : 'js'}`, js, kind });
        return '';
    });
    return { html: nextHtml, scripts };
}
function styleTags(bundle) {
    return Object.entries(bundle.files)
        .filter(([path]) => path.toLowerCase().endsWith('.css'))
        .map(([path, css]) => `<style data-artifact-file="${escapeAttribute(path)}">\n${escapeStyle(css)}\n</style>`)
        .join('\n');
}
function requestedLibraries(bundle) {
    return bundle.libraries ?? [];
}
function libraryTags(bundle, sources = {}) {
    const libraries = requestedLibraries(bundle);
    if (libraries.length === 0)
        return '';
    const tags = [];
    for (const library of libraries) {
        if (library === 'd3' || library === 'motion') {
            const source = sources[library];
            if (!source)
                continue;
            tags.push(`<script data-artifact-library="${library}">\n${escapeScript(source)}\n</script>`);
        }
    }
    const asyncInitializers = [];
    if (libraries.includes('three') && sources.three) {
        asyncInitializers.push(`
      const decodeLibrarySource = (base64) => {
        const binary = atob(base64);
        const bytes = new Uint8Array(binary.length);
        for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
        return new TextDecoder().decode(bytes);
      };
      const threeSource = decodeLibrarySource(${JSON.stringify(encodeBase64(sources.three))});
      const threeCoreSource = ${sources.threeCore ? `decodeLibrarySource(${JSON.stringify(encodeBase64(sources.threeCore))})` : "''"};
      const threeCoreUrl = threeCoreSource
        ? URL.createObjectURL(new Blob([threeCoreSource], { type: 'text/javascript' }))
        : null;
      const threeModuleSource = threeCoreUrl
        ? threeSource.replace(/from\\s*(['"])\\.\\/three\\.core\\.min\\.js\\1/g, 'from ' + JSON.stringify(threeCoreUrl))
        : threeSource;
      const threeUrl = URL.createObjectURL(new Blob([threeModuleSource], { type: 'text/javascript' }));
      try {
        window.THREE = await import(threeUrl);
      } finally {
        URL.revokeObjectURL(threeUrl);
        if (threeCoreUrl) URL.revokeObjectURL(threeCoreUrl);
      }
    `);
    }
    tags.push(`<script data-artifact-libraries-ready>
window.__STUDIOGRAPH_ARTIFACT_LIBRARIES__ = ${JSON.stringify(libraries)};
window.__STUDIOGRAPH_ARTIFACT_LIBRARIES_READY__ = (async () => {
${asyncInitializers.join('\n')}
  return true;
})().catch((error) => {
  console.error('Artifact library load failed', error);
  throw error;
});
</script>`);
    return tags.join('\n');
}
function scriptTags(bundle, inlineScripts = []) {
    const jsEntries = Object.entries(bundle.files)
        .filter(([path]) => /\.(m?js)$/i.test(path))
        .map(([path, js]) => ({ path, js, kind: scriptKindForPath(path) }));
    if (requestedLibraries(bundle).length > 0) {
        const payload = JSON.stringify([...inlineScripts, ...jsEntries]);
        return `<script data-artifact-user-scripts>
(async () => {
  await (window.__STUDIOGRAPH_ARTIFACT_LIBRARIES_READY__ || Promise.resolve());
  const scripts = ${escapeScript(payload)};
  const scriptUrls = [];
  for (const script of scripts) {
    await new Promise((resolve, reject) => {
      const url = URL.createObjectURL(new Blob([
        String(script.js) + '\\n//# sourceURL=studiograph-artifact://' + String(script.path)
      ], { type: 'text/javascript' }));
      scriptUrls.push(url);
      if (script.kind === 'module') {
        import(url).then(resolve, reject);
        return;
      }
      const element = document.createElement('script');
      element.src = url;
      element.async = false;
      element.dataset.artifactFile = String(script.path);
      element.onload = () => resolve();
      element.onerror = () => reject(new Error('Could not load artifact script: ' + String(script.path)));
      document.head.appendChild(element);
    });
  }
  if (document.readyState !== 'loading') {
    try { document.dispatchEvent(new Event('DOMContentLoaded', { bubbles: true, cancelable: true })); } catch {}
  }
  if (document.readyState === 'complete') {
    try { window.dispatchEvent(new Event('load')); } catch {}
  }
  setTimeout(() => {
    for (const url of scriptUrls) URL.revokeObjectURL(url);
  }, 0);
})().catch((error) => {
  console.error('Artifact script error', error);
  throw error;
});
</script>`;
    }
    return jsEntries
        .map(({ path, js, kind }) => `<script${kind === 'module' ? ' type="module"' : ''} data-artifact-file="${escapeAttribute(path)}">\n${escapeScript(js)}\n</script>`)
        .join('\n');
}
function standaloneFullscreenRuntimeTags(bundle, options) {
    const stepper = options.context === 'fullscreen' ? bundle.display?.controls?.stepper : null;
    if (!stepper)
        return '';
    // Same fallback chain as the in-app runtime + render/export paths — a deck
    // that steps in fullscreen must step identically in its downloaded HTML.
    const selector = frameStepperSelector(stepper.selector);
    return `<script data-artifact-standalone-runtime>
(() => {
  const selector = ${JSON.stringify(selector)};
  let activeIndex = 0;
  const frames = () => {
    try { return Array.from(document.querySelectorAll(selector)).filter((node) => node instanceof HTMLElement); }
    catch { return []; }
  };
  const setActiveFrame = (index) => {
    const allFrames = frames();
    if (allFrames.length === 0) return;
    activeIndex = Math.max(0, Math.min(allFrames.length - 1, Number.isFinite(index) ? Math.round(index) : 0));
    allFrames.forEach((frame, frameIndex) => {
      const active = frameIndex === activeIndex;
      frame.classList.toggle('active', active);
      frame.toggleAttribute('data-studiograph-active-frame', active);
      frame.setAttribute('aria-hidden', active ? 'false' : 'true');
    });
    try { history.replaceState(null, '', '#' + String(activeIndex + 1)); } catch {}
    fitActiveFrame();
  };
  // Standalone fullscreen/share view: there's no parent host to scale the frame,
  // so a fixed-size slide (e.g. 1920x1080) renders at native size and gets
  // clipped at the bottom/right by the overflow:hidden container on any smaller
  // window. Scale the active frame to fit the viewport (contain, centered). Only
  // in the fullscreen context — the export/PDF render keeps frames native-size.
  const fitActiveFrame = () => {
    if (document.documentElement.dataset.artifactContext !== 'fullscreen') return;
    const active = frames()[activeIndex];
    if (!active) return;
    active.style.setProperty('transform', 'none', 'important');
    const fw = active.offsetWidth || 1;
    const fh = active.offsetHeight || 1;
    const ww = window.innerWidth || fw;
    const wh = window.innerHeight || fh;
    const scale = Math.min(ww / fw, wh / fh);
    const left = Math.max(0, (ww - fw * scale) / 2);
    const top = Math.max(0, (wh - fh * scale) / 2);
    active.style.setProperty('transform-origin', 'top left', 'important');
    active.style.setProperty('transform', 'translate(' + left + 'px, ' + top + 'px) scale(' + scale + ')', 'important');
  };
  const stepFrame = (direction) => {
    const allFrames = frames();
    if (allFrames.length === 0) return;
    setActiveFrame((activeIndex + direction + allFrames.length) % allFrames.length);
  };
  const initialize = () => {
    const allFrames = frames();
    if (allFrames.length === 0) return;
    const activeFrameIndex = allFrames.findIndex((frame) => frame.classList.contains('active') || frame.hasAttribute('data-studiograph-active-frame'));
    const hashIndex = /^#\\d+$/.test(location.hash) ? Number(location.hash.slice(1)) - 1 : -1;
    setActiveFrame(hashIndex >= 0 ? hashIndex : activeFrameIndex >= 0 ? activeFrameIndex : 0);
  };
  window.addEventListener('keydown', (event) => {
    if (event.altKey || event.ctrlKey || event.metaKey) return;
    if (event.key === 'ArrowRight' || event.key === 'PageDown' || event.key === ' ') {
      stepFrame(1);
      event.preventDefault();
      return;
    }
    if (event.key === 'ArrowLeft' || event.key === 'PageUp' || event.key === 'Backspace') {
      stepFrame(-1);
      event.preventDefault();
      return;
    }
    if (event.key === 'Home') {
      setActiveFrame(0);
      event.preventDefault();
      return;
    }
    if (event.key === 'End') {
      setActiveFrame(frames().length - 1);
      event.preventDefault();
    }
  });
  window.addEventListener('click', (event) => {
    if (event.target instanceof Element && event.target.closest('a,button,input,textarea,select,summary,[contenteditable="true"],[role="button"],[data-artifact-no-stepper]')) return;
    if (frames().length <= 1) return;
    stepFrame(event.clientX < window.innerWidth * 0.2 ? -1 : 1);
    event.preventDefault();
  }, { capture: true });
  window.addEventListener('resize', fitActiveFrame);
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initialize, { once: true });
  else initialize();
})();
</script>`;
}
function addAttributesToHtmlTag(html, attrs) {
    if (!/<html[\s>]/i.test(html))
        return html;
    return html.replace(/<html\b([^>]*)>/i, (match, existingAttrs) => {
        let nextAttrs = existingAttrs;
        for (const [name, value] of Object.entries(attrs)) {
            if (value === undefined || value === false)
                continue;
            if (new RegExp(`\\b${name}\\s*=`, 'i').test(nextAttrs))
                continue;
            nextAttrs = ` ${name}${value === true ? '' : `="${escapeAttribute(value)}"`}${nextAttrs}`;
        }
        return `<html${nextAttrs}>`;
    });
}
function headAdditions(bundle, entry, options) {
    // The CSP is derived from the SAME AssetResolution that rewrites the asset
    // URLs (finalize step), so this document can't carry URLs from one asset
    // model and a policy from another. `base-uri` opens to exactly the
    // baseHref's origin when the headless render injects a <base> tag.
    // Safe unescaped: every dynamic CSP part is validated upstream (origins are
    // URL-parsed, share tokens charset-checked) — no quote can reach here.
    const csp = artifactCsp(options.assets, { allowBaseOrigin: options.baseHref });
    const additions = [
        '<meta charset="utf-8">',
        `<meta http-equiv="Content-Security-Policy" content="${csp}">`,
        options.baseHref ? `<base href="${escapeAttribute(options.baseHref)}">` : '',
        '<meta name="generator" content="Studiograph Artifact Canvas">',
        /<title[\s>]/i.test(entry) ? '' : `<title>${escapeHtml(bundle.title)}</title>`,
        styleTags(bundle),
        options.includeRenderHostStyles ? artifactRenderHostStyleTags(bundle) : '',
    ];
    return additions.filter(Boolean).join('\n');
}
function withDoctype(html) {
    return /^\s*<!doctype\b/i.test(html) ? html : `<!doctype html>\n${html}`;
}
export function artifactExportFilename(bundle, extension = 'html') {
    const source = bundle.title || bundle.artifactId || 'artifact';
    const slug = source
        .normalize('NFKD')
        .replace(/[\u0300-\u036f]/g, '')
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 80);
    return `${slug || 'artifact'}.${extension}`;
}
function artifactRenderHostStyleTags(bundle) {
    const viewport = bundle.display?.fullscreenViewport ?? bundle.viewport;
    const frameWidth = `${Math.max(1, Math.round(viewport.width))}px`;
    const frameHeight = `${Math.max(1, Math.round(viewport.height))}px`;
    const authoredPageSizing = bundle.viewport.mode === 'paged';
    const exportFrameSizing = authoredPageSizing
        ? '  contain: layout paint !important;'
        : `  width: var(--studiograph-export-frame-width) !important;
  height: var(--studiograph-export-frame-height) !important;
  min-width: var(--studiograph-export-frame-width) !important;
  min-height: var(--studiograph-export-frame-height) !important;
  max-width: var(--studiograph-export-frame-width) !important;
  max-height: var(--studiograph-export-frame-height) !important;
  contain: layout paint size !important;`;
    const exportFlattening = `
html[data-artifact-context="export"] {
  --studiograph-export-frame-width: ${frameWidth};
  --studiograph-export-frame-height: ${frameHeight};
}
html[data-artifact-context="export"],
html[data-artifact-context="export"] body {
  width: 100% !important;
  min-height: 100% !important;
  height: auto !important;
  overflow: visible !important;
}
html[data-artifact-context="export"] .artifact-sequence,
html[data-artifact-context="export"] .deck,
html[data-artifact-context="export"] .pages,
html[data-artifact-context="export"] .document-board,
html[data-artifact-context="export"] .artifact-board,
html[data-artifact-context="export"] [data-artifact-layout="sequence"],
html[data-artifact-context="export"] [data-artifact-layout="paged"] {
  width: 100% !important;
  height: auto !important;
  min-height: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: flex-start !important;
  gap: 0 !important;
  overflow: visible !important;
  background: transparent !important;
  box-shadow: none !important;
}
html[data-artifact-context="export"] [data-artifact-frame],
html[data-artifact-context="export"] [data-artifact-page],
html[data-artifact-context="export"] .page {
  display: block !important;
  position: relative !important;
  box-sizing: border-box !important;
${exportFrameSizing}
  isolation: isolate !important;
  clip-path: inset(0) !important;
  inset: auto !important;
  left: auto !important;
  top: auto !important;
  right: auto !important;
  bottom: auto !important;
  transform: none !important;
  margin: 0 !important;
  flex: none !important;
  overflow: hidden !important;
}
`;
    // The hosted app presents artifacts on a neutral gray stage
    // (.artifact-stage[data-view-mode='fullscreen'] in ArtifactEntityView.svelte
    // — ARTIFACT_PRESENT_BACKDROP, keep in sync). The exported standalone file
    // has no host stage, so the document itself carries the backdrop — but ONLY
    // where the standalone runtime letterboxes content around a stage (stepper
    // present mode, paged reader). A plain fixed/responsive document fills the
    // window with its own page, and forcing its body transparent would strip an
    // AUTHORED background the in-app render keeps. `display.background:
    // 'transparent'` opts out, matching the in-app rule.
    const backdropScope = bundle.display?.controls?.stepper
        ? 'html[data-artifact-context="fullscreen"][data-artifact-stepper="true"]'
        : bundle.viewport.mode === 'paged'
            ? 'html[data-artifact-context="fullscreen"]'
            : null;
    const presentBackdrop = backdropScope && bundle.display?.background !== 'transparent'
        ? `
${backdropScope} {
  background: ${ARTIFACT_PRESENT_BACKDROP} !important;
}
${backdropScope} body {
  background: transparent !important;
}
`
        : '';
    const stepperFullscreen = bundle.display?.controls?.stepper
        ? `
html[data-artifact-context="fullscreen"][data-artifact-stepper="true"],
html[data-artifact-context="fullscreen"][data-artifact-stepper="true"] body {
  width: 100% !important;
  height: 100% !important;
  min-height: 100% !important;
  overflow: hidden !important;
}
html[data-artifact-context="fullscreen"][data-artifact-stepper="true"] .artifact-sequence,
html[data-artifact-context="fullscreen"][data-artifact-stepper="true"] .deck,
html[data-artifact-context="fullscreen"][data-artifact-stepper="true"] [data-artifact-layout="sequence"] {
  width: 100% !important;
  height: 100% !important;
  min-height: 100% !important;
  padding: 0 !important;
  margin: 0 !important;
  display: block !important;
  position: relative !important;
  overflow: hidden !important;
  background: transparent !important;
  box-shadow: none !important;
}
${frameStepperVisibilityCss(frameStepperSelector(bundle.display?.controls?.stepper?.selector), 'html[data-artifact-context="fullscreen"][data-artifact-stepper="true"]')}
`
        : '';
    const pagedFullscreen = bundle.viewport.mode === 'paged'
        ? `
html[data-artifact-context="fullscreen"],
html[data-artifact-context="fullscreen"] body {
  width: 100% !important;
  min-height: 100% !important;
  overflow: auto !important;
}
html[data-artifact-context="fullscreen"] .pages,
html[data-artifact-context="fullscreen"] .document-board,
html[data-artifact-context="fullscreen"] .artifact-board[data-artifact-layout="paged"],
html[data-artifact-context="fullscreen"] [data-artifact-layout="paged"] {
  width: 100% !important;
  height: auto !important;
  min-height: 100% !important;
  padding: 48px 0 !important;
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: flex-start !important;
  align-content: center !important;
  gap: 32px !important;
  background: transparent !important;
  box-shadow: none !important;
  overflow: visible !important;
}
html[data-artifact-context="fullscreen"] .page,
html[data-artifact-context="fullscreen"] [data-artifact-page] {
  position: relative !important;
  left: auto !important;
  right: auto !important;
  top: auto !important;
  bottom: auto !important;
  transform: none !important;
}
`
        : '';
    const printPagination = `
@media print {
  html[data-artifact-context="fullscreen"],
  html[data-artifact-context="fullscreen"] body,
  html[data-artifact-context="export"],
  html[data-artifact-context="export"] body {
    height: auto !important;
    min-height: 0 !important;
    overflow: visible !important;
    /* The on-screen present backdrop must never print as gray pages. */
    background: transparent !important;
  }
  html[data-artifact-context="fullscreen"] .artifact-sequence,
  html[data-artifact-context="fullscreen"] .deck,
  html[data-artifact-context="fullscreen"] .pages,
  html[data-artifact-context="fullscreen"] .document-board,
  html[data-artifact-context="fullscreen"] [data-artifact-layout="sequence"],
  html[data-artifact-context="fullscreen"] [data-artifact-layout="paged"],
  html[data-artifact-context="export"] .artifact-sequence,
  html[data-artifact-context="export"] .deck,
  html[data-artifact-context="export"] .pages,
  html[data-artifact-context="export"] .document-board,
  html[data-artifact-context="export"] .artifact-board,
  html[data-artifact-context="export"] [data-artifact-layout="sequence"],
  html[data-artifact-context="export"] [data-artifact-layout="paged"] {
    display: block !important;
    width: 100% !important;
    height: auto !important;
    min-height: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    overflow: visible !important;
    background: transparent !important;
    box-shadow: none !important;
  }
  html[data-artifact-context="fullscreen"] [data-artifact-frame],
  html[data-artifact-context="fullscreen"] [data-artifact-page],
  html[data-artifact-context="fullscreen"] .page,
  html[data-artifact-context="export"] [data-artifact-frame],
  html[data-artifact-context="export"] [data-artifact-page],
  html[data-artifact-context="export"] .page {
    display: block !important;
    position: relative !important;
    box-sizing: border-box !important;
${exportFrameSizing}
    isolation: isolate !important;
    clip-path: inset(0) !important;
    inset: auto !important;
    left: auto !important;
    top: auto !important;
    right: auto !important;
    bottom: auto !important;
    transform: none !important;
    margin: 0 !important;
    overflow: hidden !important;
    break-after: page;
    page-break-after: always;
  }
  html[data-artifact-context="fullscreen"] [data-artifact-frame]:not(:first-child),
  html[data-artifact-context="fullscreen"] [data-artifact-page]:not(:first-child),
  html[data-artifact-context="fullscreen"] .page:not(:first-child),
  html[data-artifact-context="export"] [data-artifact-frame]:not(:first-child),
  html[data-artifact-context="export"] [data-artifact-page]:not(:first-child),
  html[data-artifact-context="export"] .page:not(:first-child) {
    break-before: page;
    page-break-before: always;
  }
  html[data-artifact-context="fullscreen"] [data-artifact-frame]:last-child,
  html[data-artifact-context="fullscreen"] [data-artifact-page]:last-child,
  html[data-artifact-context="fullscreen"] .page:last-child,
  html[data-artifact-context="export"] [data-artifact-frame]:last-child,
  html[data-artifact-context="export"] [data-artifact-page]:last-child,
  html[data-artifact-context="export"] .page:last-child {
    break-after: auto;
    page-break-after: auto;
  }
}
`;
    return `<style data-artifact-export-host>
html, body {
  margin: 0;
  padding: 0;
}
${presentBackdrop}
${pagedFullscreen}
${stepperFullscreen}
${exportFlattening}
${printPagination}
</style>`;
}
export function buildArtifactHtmlExport(bundle, options = {}) {
    // Resolve workspace-asset URLs per the render's AssetResolution at the
    // single exit (token: append ?at= for the cookieless headless render;
    // relative: ./assets paths + placeholders for the standalone export).
    // The CSP in headAdditions derives from the same value.
    const finalize = (html) => applyAssetResolution(html, options.assets);
    const rawEntry = stripNetworkAssetTags(bundle.files[bundle.entrypoint] ?? '');
    const deferred = requestedLibraries(bundle).length > 0 ? deferInlineScripts(rawEntry) : { html: rawEntry, scripts: [] };
    const entry = deferred.html;
    const styles = headAdditions(bundle, entry, options);
    const libraries = libraryTags(bundle, options.librarySources);
    const scripts = scriptTags(bundle, deferred.scripts);
    const runtime = standaloneFullscreenRuntimeTags(bundle, options);
    const bodyScripts = [runtime, scripts].filter(Boolean).join('\n');
    const htmlAttrs = options.context
        ? {
            'data-artifact-context': options.context,
            'data-artifact-stepper': bundle.display?.controls?.stepper ? 'true' : undefined,
        }
        : {};
    if (/<html[\s>]/i.test(entry)) {
        let html = addAttributesToHtmlTag(entry, htmlAttrs);
        if (/<head[\s>]/i.test(html)) {
            html = html.replace(/<head([^>]*)>/i, `<head$1>\n${styles}\n${libraries}\n`);
        }
        else {
            html = html.replace(/<html([^>]*)>/i, `<html$1>\n<head>\n${styles}\n${libraries}\n</head>`);
        }
        if (bodyScripts) {
            if (/<\/body>/i.test(html)) {
                html = html.replace(/<\/body>/i, `${bodyScripts}\n</body>`);
            }
            else {
                html = `${html}\n${bodyScripts}`;
            }
        }
        return finalize(withDoctype(html));
    }
    return finalize(`<!doctype html>
<html${options.context ? ` data-artifact-context="${escapeAttribute(options.context)}"${bundle.display?.controls?.stepper ? ' data-artifact-stepper="true"' : ''}` : ''}>
<head>
${styles}
${libraries}
</head>
<body>
${entry}
${bodyScripts}
</body>
</html>`);
}
//# sourceMappingURL=artifact-export.js.map