"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isHtmlDocumentRequest = exports.injectPreviewBridge = exports.shouldInjectPreviewBridge = exports.allowPreviewBridgeNonceInCsp = exports.createPreviewBridgeScript = exports.PREVIEW_BRIDGE_CHANNEL = exports.PREVIEW_BRIDGE_SCRIPT_ID = void 0;
exports.PREVIEW_BRIDGE_SCRIPT_ID = 'rivet-preview-bridge';
exports.PREVIEW_BRIDGE_CHANNEL = 'rivet-preview-bridge';
const MAX_SIBLING_CONTEXT_COUNT = 6;
const TEXT_SNIPPET_MAX_LENGTH = 80;
const buildBridgeScript = ({ expectedParentOrigin, } = {}) => `
(() => {
  const CHANNEL = '${exports.PREVIEW_BRIDGE_CHANNEL}';
  const EXPECTED_PARENT_ORIGIN = ${JSON.stringify(expectedParentOrigin ?? null)} || window.location.origin;
  const RIVET_ID_ATTR = 'data-rivet-id';
  const MAX_SIBLING_CONTEXT_COUNT = ${MAX_SIBLING_CONTEXT_COUNT};
  const TEXT_SNIPPET_MAX_LENGTH = ${TEXT_SNIPPET_MAX_LENGTH};
  if (window.__RIVET_PREVIEW_BRIDGE_ACTIVE__) {
    return;
  }
  window.__RIVET_PREVIEW_BRIDGE_ACTIVE__ = true;

  const getClassName = (element) => {
    try {
      const value = element.className;
      if (typeof value === 'string') {
        return value;
      }
      if (value && typeof value === 'object' && 'baseVal' in value) {
        return value.baseVal || '';
      }
    } catch {}
    return '';
  };

  const getTextSnippet = (element) => {
    const text = (element.textContent || '').replace(/\\s+/g, ' ').trim();
    return text.slice(0, TEXT_SNIPPET_MAX_LENGTH);
  };

  const getXPath = (element) => {
    if (element.id) {
      return '//*[@id="' + element.id + '"]';
    }
    if (element === document.body) {
      return '/html/body';
    }
    let index = 0;
    const siblings = element.parentNode ? element.parentNode.childNodes : [];
    for (let i = 0; i < siblings.length; i += 1) {
      const sibling = siblings[i];
      if (sibling === element) {
        const parent = element.parentNode;
        return parent
          ? getXPath(parent) + '/' + element.tagName.toLowerCase() + '[' + (index + 1) + ']'
          : '';
      }
      if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
        index += 1;
      }
    }
    return '';
  };

  const getElementByXPath = (xpath) => {
    try {
      const result = document.evaluate(
        xpath,
        document,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null,
      );
      return result.singleNodeValue || null;
    } catch {
      return null;
    }
  };

  const ensureRivetId = (element) => {
    const existing = element.getAttribute(RIVET_ID_ATTR);
    if (existing && existing.trim()) {
      return existing.trim();
    }
    const id =
      globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function'
        ? globalThis.crypto.randomUUID()
        : 'rivet-' + Math.random().toString(36).slice(2, 11);
    element.setAttribute(RIVET_ID_ATTR, id);
    return id;
  };

  const transformStyles = (styles) => {
    const result = {};
    for (let i = 0; i < styles.length; i += 1) {
      const prop = styles[i];
      result[prop] = styles.getPropertyValue(prop);
    }
    return result;
  };

  const getSiblingElements = (element) => {
    const parent = element.parentElement;
    if (!parent) {
      return undefined;
    }
    const siblings = Array.from(parent.children);
    const currentIndex = siblings.indexOf(element);
    if (currentIndex === -1) {
      return undefined;
    }
    const result = siblings
      .map((sibling, index) => ({ sibling, index }))
      .filter(({ sibling }) => sibling !== element)
      .sort((a, b) => Math.abs(a.index - currentIndex) - Math.abs(b.index - currentIndex))
      .slice(0, MAX_SIBLING_CONTEXT_COUNT)
      .map(({ sibling, index }) => ({
        tagName: sibling.tagName.toLowerCase(),
        className: getClassName(sibling),
        id: sibling.id || '',
        textSnippet: getTextSnippet(sibling),
        relativePosition: index < currentIndex ? 'previous' : 'next',
      }));
    return result.length > 0 ? result : undefined;
  };

  const getParentSummary = (element) => {
    const parent = element.parentElement;
    if (!parent) {
      return undefined;
    }
    return {
      tagName: parent.tagName.toLowerCase(),
      className: getClassName(parent),
      id: parent.id || '',
      textSnippet: getTextSnippet(parent),
    };
  };

  const extractElement = (element) => {
    if (!element) {
      return null;
    }
    const rect = element.getBoundingClientRect();
    const computedStyles = window.getComputedStyle(element);
    const attributes = {};
    for (const attr of Array.from(element.attributes || [])) {
      attributes[attr.name] = attr.value;
    }
    const rivetId = ensureRivetId(element);
    return {
      tagName: element.tagName.toLowerCase(),
      className: getClassName(element),
      id: element.id || '',
      textContent: (element.textContent || '').trim().slice(0, 100),
      innerHTML: (element.innerHTML || '').slice(0, 200),
      attributes,
      computedStyles: transformStyles(computedStyles),
      boundingRect: {
        x: rect.x,
        y: rect.y,
        width: rect.width,
        height: rect.height,
        top: rect.top,
        left: rect.left,
        right: rect.right,
        bottom: rect.bottom,
      },
      xpath: getXPath(element),
      rivetId,
      siblingElements: getSiblingElements(element),
      parentElementSummary: getParentSummary(element),
    };
  };

  const elementFromIdentifier = (identifier) => {
    if (!identifier || typeof identifier !== 'object') {
      return null;
    }
    const rivetId = typeof identifier.rivetId === 'string' ? identifier.rivetId.trim() : '';
    if (rivetId) {
      try {
        const element = document.querySelector('[' + RIVET_ID_ATTR + '="' + CSS.escape(rivetId) + '"]');
        if (element) {
          return element;
        }
      } catch {}
    }
    const xpath = typeof identifier.xpath === 'string' ? identifier.xpath : '';
    return xpath ? getElementByXPath(xpath) : null;
  };

  const elementsInBox = (box) => {
    const candidates = new Set();
    const step = 32;
    const xCount = Math.max(2, Math.min(24, Math.ceil((box.right - box.left) / step) + 1));
    const yCount = Math.max(2, Math.min(24, Math.ceil((box.bottom - box.top) / step) + 1));
    for (let xi = 0; xi < xCount; xi += 1) {
      const x = box.left + ((box.right - box.left) * xi) / (xCount - 1);
      for (let yi = 0; yi < yCount; yi += 1) {
        const y = box.top + ((box.bottom - box.top) * yi) / (yCount - 1);
        const elements =
          typeof document.elementsFromPoint === 'function'
            ? document.elementsFromPoint(x, y)
            : [document.elementFromPoint(x, y)].filter(Boolean);
        for (const element of elements) {
          let current = element;
          while (current) {
            candidates.add(current);
            current = current.parentElement;
          }
        }
      }
    }
    return Array.from(candidates)
      .filter((element) => {
        const rect = element.getBoundingClientRect();
        return (
          rect.left < box.right &&
          rect.right > box.left &&
          rect.top < box.bottom &&
          rect.bottom > box.top &&
          rect.width > 0 &&
          rect.height > 0
        );
      })
      .map(extractElement)
      .filter(Boolean);
  };

  const send = (source, id, responseNonce, payload) => {
    source.postMessage(
      {
        channel: CHANNEL,
        direction: 'response',
        id,
        responseNonce,
        ...payload,
      },
      EXPECTED_PARENT_ORIGIN,
    );
  };

  window.addEventListener('message', (event) => {
    const message = event.data;
    if (!message || message.channel !== CHANNEL || message.direction !== 'request') {
      return;
    }
    if (event.origin !== EXPECTED_PARENT_ORIGIN) {
      return;
    }
    event.stopImmediatePropagation();
    const source = event.source;
    if (!source || typeof source.postMessage !== 'function') {
      return;
    }
    try {
      let result = null;
      if (message.action === 'elementAtPoint') {
        result = extractElement(document.elementFromPoint(message.payload.x, message.payload.y));
      } else if (message.action === 'elementsInBox') {
        result = elementsInBox(message.payload);
      } else if (message.action === 'resolveElement') {
        result = extractElement(elementFromIdentifier(message.payload));
      } else if (message.action === 'setCursor') {
        const cursor = typeof message.payload.cursor === 'string' ? message.payload.cursor : '';
        document.documentElement.style.cursor = cursor;
        document.body.style.cursor = cursor;
        result = true;
      } else if (message.action === 'scrollBy') {
        window.scrollBy(message.payload.deltaX || 0, message.payload.deltaY || 0);
        result = true;
      } else {
        throw new Error('Unknown action');
      }
      send(source, message.id, message.responseNonce, { ok: true, result });
    } catch (error) {
      send(source, message.id, message.responseNonce, {
        ok: false,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  });
})();
`.trim();
const createPreviewBridgeScript = (options = {}) => {
    const nonceAttribute = options.scriptNonce
        ? ` nonce="${options.scriptNonce}"`
        : '';
    return `<script id="${exports.PREVIEW_BRIDGE_SCRIPT_ID}"${nonceAttribute}>${buildBridgeScript(options)}</script>`;
};
exports.createPreviewBridgeScript = createPreviewBridgeScript;
const SCRIPT_DIRECTIVES = new Set(['script-src', 'script-src-elem']);
const splitCspDirective = (directive) => directive
    .trim()
    .split(/\s+/)
    .filter((part) => part.length > 0);
const allowPreviewBridgeNonceInCsp = (csp, { scriptNonce }) => {
    const nonceSource = `'nonce-${scriptNonce}'`;
    const directives = csp
        .split(';')
        .map((directive) => directive.trim())
        .filter((directive) => directive.length > 0);
    let hasScriptDirective = false;
    const nextDirectives = directives.map((directive) => {
        const parts = splitCspDirective(directive);
        const name = parts[0]?.toLowerCase();
        if (!SCRIPT_DIRECTIVES.has(name)) {
            return directive;
        }
        hasScriptDirective = true;
        return parts.includes(nonceSource)
            ? directive
            : [...parts, nonceSource].join(' ');
    });
    if (!hasScriptDirective) {
        const defaultDirective = directives.find((directive) => splitCspDirective(directive)[0]?.toLowerCase() === 'default-src');
        const defaultSources = defaultDirective
            ? splitCspDirective(defaultDirective).slice(1)
            : [];
        nextDirectives.push(['script-src', ...defaultSources, nonceSource].join(' '));
    }
    return nextDirectives.join('; ');
};
exports.allowPreviewBridgeNonceInCsp = allowPreviewBridgeNonceInCsp;
const firstHeaderValue = (value) => Array.isArray(value) ? (value[0] ?? '') : (value ?? '');
/** Decides whether a proxied response should receive the isolated-preview bridge. */
const shouldInjectPreviewBridge = ({ method, accept, contentType, }) => {
    if (method != null && method !== 'GET') {
        return false;
    }
    const normalizedAccept = firstHeaderValue(accept).toLowerCase();
    const normalizedContentType = firstHeaderValue(contentType).toLowerCase();
    return (normalizedAccept.includes('text/html') &&
        normalizedContentType.includes('text/html'));
};
exports.shouldInjectPreviewBridge = shouldInjectPreviewBridge;
/** Adds the isolated-preview bridge to a proxied HTML document. */
const injectPreviewBridge = (html, options = {}) => {
    if (html.includes(`id="${exports.PREVIEW_BRIDGE_SCRIPT_ID}"`)) {
        return html;
    }
    const script = (0, exports.createPreviewBridgeScript)(options);
    const headMatch = html.match(/<head[^>]*>/i);
    if (headMatch?.index !== undefined) {
        const insertAt = headMatch.index + headMatch[0].length;
        return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
    }
    if (html.includes('</head>')) {
        return html.replace('</head>', `${script}</head>`);
    }
    if (html.includes('</body>')) {
        return html.replace('</body>', `${script}</body>`);
    }
    return `${script}${html}`;
};
exports.injectPreviewBridge = injectPreviewBridge;
const isHtmlDocumentRequest = (req) => req.method === 'GET' &&
    firstHeaderValue(req.headers.accept).toLowerCase().includes('text/html');
exports.isHtmlDocumentRequest = isHtmlDocumentRequest;
//# sourceMappingURL=preview-bridge.js.map