(function attachPyroSecurityPolicy(root) {
  'use strict';

  const LIMITS = Object.freeze({
    projectFileBytes: 64 * 1024 * 1024,
    svgFileBytes: 32 * 1024 * 1024,
    bitmapFileBytes: 32 * 1024 * 1024,
    embeddedImageCharacters: 48 * 1024 * 1024,
    canvasSide: 10000,
    bitmapSide: 10000,
    bitmapPixels: 32_000_000,
    svgElements: 100000,
    svgDepth: 128,
    svgAttributesPerElement: 256,
    svgAttributeCharacters: 16 * 1024 * 1024,
    svgPathCharacters: 4 * 1024 * 1024
  });

  const FILE_LIMIT_KEYS = Object.freeze({
    project: 'projectFileBytes',
    svg: 'svgFileBytes',
    bitmap: 'bitmapFileBytes'
  });
  const translated = (key, variables, fallback) => root.PyroI18n?.t(key, variables) || fallback;
  const BLOCKED_OBJECT_KEYS = Object.freeze(new Set(['__proto__', 'prototype', 'constructor']));
  const DATA_IMAGE = /^data:image\/(?:png|jpe?g|webp|gif|svg\+xml)(?:;[^,\u0000-\u001f]*)?,/i;
  const XML_BUILTIN_ENTITIES = Object.freeze(new Set(['amp', 'lt', 'gt', 'quot', 'apos']));
  const XML_ENTITY_EXPANSION_LIMIT = 64 * 1024;
  const XML_NAME = /^[A-Za-z_:][A-Za-z0-9_.:-]*$/;

  class SecurityPolicyError extends Error {
    constructor(message, code = 'SECURITY_POLICY_REJECTED', details = null) {
      super(message);
      this.name = 'SecurityPolicyError';
      this.code = code;
      if (details && typeof details === 'object') this.details = Object.freeze({ ...details });
    }
  }

  function resolvedLimits(overrides = {}) {
    const limits = { ...LIMITS };
    if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) return limits;
    Object.keys(limits).forEach((key) => {
      const value = Number(overrides[key]);
      if (Number.isFinite(value) && value > 0) limits[key] = value;
    });
    return limits;
  }

  function assertFileSize(file, kind, overrides = {}) {
    const key = FILE_LIMIT_KEYS[kind];
    if (!key) throw new SecurityPolicyError(translated('error.unknownFileType', {}, 'Unbekannte Dateiart'), 'UNKNOWN_FILE_KIND');
    const maximum = resolvedLimits(overrides)[key];
    const size = Number(file?.size);
    if (!Number.isFinite(size) || size < 0) throw new SecurityPolicyError('Die Dateigröße ist ungültig', 'INVALID_FILE_SIZE');
    if (size > maximum) {
      const labelKeys = { project: 'ui.projectFile', svg: 'ui.svgFile', bitmap: 'ui.imageFile' };
      const fallbackLabels = { project: 'Projektdatei', svg: 'SVG-Datei', bitmap: 'Bilddatei' };
      const type = translated(labelKeys[kind], {}, fallbackLabels[kind]);
      const size = Math.floor(maximum / 1024 / 1024);
      throw new SecurityPolicyError(translated('error.fileTooLarge', { type, size }, `${fallbackLabels[kind]} ist größer als ${size} MB`), 'FILE_TOO_LARGE');
    }
    return size;
  }

  function tokenAt(source, index, token) {
    if (source.slice(index, index + token.length).toLowerCase() !== token.toLowerCase()) return false;
    const next = source[index + token.length] || '';
    return !next || /[\s>\[\]%]/.test(next);
  }

  function markupEnd(source, start, doctype = false) {
    let quote = ''; let subsetDepth = 0; let comment = false;
    for (let index = start; index < source.length; index += 1) {
      if (comment) {
        if (source.startsWith('-->', index)) { comment = false; index += 2; }
        continue;
      }
      const character = source[index];
      if (quote) { if (character === quote) quote = ''; continue; }
      if (source.startsWith('<!--', index)) { comment = true; index += 3; continue; }
      if (character === '"' || character === "'") { quote = character; continue; }
      if (doctype && character === '[') { subsetDepth += 1; continue; }
      if (doctype && character === ']') { subsetDepth = Math.max(0, subsetDepth - 1); continue; }
      if (character === '>' && (!doctype || subsetDepth === 0)) return index;
    }
    return -1;
  }

  function processingInstructionEnd(source, start) {
    const end = source.indexOf('?>', start); return end < 0 ? -1 : end + 1;
  }

  function quotedValue(source, index) {
    const quote = source[index];
    if (quote !== '"' && quote !== "'") return null;
    const end = source.indexOf(quote, index + 1);
    return end < 0 ? null : { value: source.slice(index + 1, end), end: end + 1 };
  }

  function skipWhitespace(source, index) {
    while (index < source.length && /\s/.test(source[index])) index += 1;
    return index;
  }

  function readXmlName(source, index) {
    const start = index;
    while (index < source.length && /[A-Za-z0-9_.:-]/.test(source[index])) index += 1;
    const name = source.slice(start, index);
    return XML_NAME.test(name) ? { name, end: index } : null;
  }

  function unsafeEntity(messageCode, details = {}) {
    return new SecurityPolicyError(
      'Die SVG-Datei enthält eine nicht unterstützte oder unsichere Entity-Deklaration.',
      messageCode,
      details
    );
  }

  function entityReferences(source) {
    const references = [];
    for (let index = 0; index < source.length; index += 1) {
      if (source[index] !== '&') continue;
      const end = source.indexOf(';', index + 1);
      if (end < 0 || end - index > 128) continue;
      const name = source.slice(index + 1, end);
      if (/^#(?:[0-9]+|x[0-9a-f]+)$/i.test(name) || XML_BUILTIN_ENTITIES.has(name)) { index = end; continue; }
      if (name && !/[\s<>&]/.test(name)) references.push(name);
      index = end;
    }
    return references;
  }

  function internalSubset(declaration) {
    let quote = ''; let start = -1; let depth = 0; let comment = false;
    for (let index = 0; index < declaration.length; index += 1) {
      if (comment) {
        if (declaration.startsWith('-->', index)) { comment = false; index += 2; }
        continue;
      }
      const character = declaration[index];
      if (quote) { if (character === quote) quote = ''; continue; }
      if (declaration.startsWith('<!--', index)) { comment = true; index += 3; continue; }
      if (character === '"' || character === "'") { quote = character; continue; }
      if (character === '[') { if (depth === 0) start = index + 1; depth += 1; continue; }
      if (character === ']' && depth > 0) {
        depth -= 1; if (depth === 0 && start >= 0) return declaration.slice(start, index);
      }
    }
    return '';
  }

  function inspectInternalSubset(subset) {
    const declarations = new Map(); let index = 0;
    while (index < subset.length) {
      if (subset.startsWith('<!--', index)) {
        const end = subset.indexOf('-->', index + 4); index = end < 0 ? subset.length : end + 3; continue;
      }
      if (subset[index] === '%' && readXmlName(subset, skipWhitespace(subset, index + 1))) {
        throw unsafeEntity('SVG_PARAMETER_ENTITY_BLOCKED', { kind: 'parameter-reference' });
      }
      if (!tokenAt(subset, index, '<!ENTITY')) { index += 1; continue; }
      const end = markupEnd(subset, index + 9, false);
      if (end < 0) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'unterminated' });
      const declaration = subset.slice(index + 9, end); let cursor = skipWhitespace(declaration, 0);
      if (declaration[cursor] === '%') throw unsafeEntity('SVG_PARAMETER_ENTITY_BLOCKED', { kind: 'parameter-declaration' });
      const parsedName = readXmlName(declaration, cursor);
      if (!parsedName || XML_BUILTIN_ENTITIES.has(parsedName.name)) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'name' });
      cursor = skipWhitespace(declaration, parsedName.end);
      const externalKind = tokenAt(declaration, cursor, 'SYSTEM') ? 'SYSTEM' : tokenAt(declaration, cursor, 'PUBLIC') ? 'PUBLIC' : '';
      if (externalKind) throw unsafeEntity('SVG_EXTERNAL_ENTITY_BLOCKED', { kind: externalKind.toLowerCase(), entity: parsedName.name });
      const literal = quotedValue(declaration, cursor);
      if (!literal) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'value', entity: parsedName.name });
      if (/%\s*[A-Za-z_:][A-Za-z0-9_.:-]*\s*;/.test(literal.value)) {
        throw unsafeEntity('SVG_PARAMETER_ENTITY_BLOCKED', { kind: 'parameter-reference', entity: parsedName.name });
      }
      if (declarations.has(parsedName.name)) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'duplicate', entity: parsedName.name });
      declarations.set(parsedName.name, literal.value); index = end + 1;
    }

    const expandedSizes = new Map();
    const expansionSize = (name, stack = new Set()) => {
      if (expandedSizes.has(name)) return expandedSizes.get(name);
      if (stack.has(name)) throw unsafeEntity('SVG_ENTITY_RECURSIVE', { kind: 'recursive', entity: name });
      const value = declarations.get(name); if (value == null) return 0;
      const nextStack = new Set(stack); nextStack.add(name); let size = value.length;
      for (const reference of entityReferences(value)) {
        if (!declarations.has(reference)) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'unknown-reference', entity: reference });
        size += expansionSize(reference, nextStack);
        if (size > XML_ENTITY_EXPANSION_LIMIT) throw unsafeEntity('SVG_ENTITY_EXPANSION_BLOCKED', { kind: 'expansion-limit', entity: name });
      }
      expandedSizes.set(name, size); return size;
    };
    declarations.forEach((_value, name) => expansionSize(name));
    return declarations;
  }

  function assertKnownXmlEntities(source, declaredEntities) {
    for (let index = 0; index < source.length; index += 1) {
      if (source.startsWith('<!--', index)) {
        const end = source.indexOf('-->', index + 4); index = end < 0 ? source.length : end + 2; continue;
      }
      if (source.startsWith('<![CDATA[', index)) {
        const end = source.indexOf(']]>', index + 9); index = end < 0 ? source.length : end + 2; continue;
      }
      if (source.startsWith('<?', index)) {
        const end = processingInstructionEnd(source, index + 2); index = end < 0 ? source.length : end; continue;
      }
      if (tokenAt(source, index, '<!ENTITY')) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'outside-doctype' });
      if (source[index] !== '&') continue;
      const end = source.indexOf(';', index + 1);
      if (end < 0 || end - index > 128) continue;
      const name = source.slice(index + 1, end);
      if (/^#(?:[0-9]+|x[0-9a-f]+)$/i.test(name) || XML_BUILTIN_ENTITIES.has(name)) { index = end; continue; }
      if (name && !/[\s<>&]/.test(name)) {
        throw unsafeEntity('SVG_CUSTOM_ENTITY_UNSUPPORTED', { kind: declaredEntities.has(name) ? 'declared-reference' : 'unknown-reference', entity: name });
      }
      index = end;
    }
  }

  function prepareSvgSource(source, overrides = {}) {
    const limits = resolvedLimits(overrides); const value = String(source ?? '');
    if (!value.trim()) throw new SecurityPolicyError('SVG-Datei ist leer', 'SVG_EMPTY');
    if (value.length > limits.svgFileBytes) throw new SecurityPolicyError('SVG-Datei ist zu groß', 'SVG_TOO_LARGE');

    const ranges = []; let rootSeen = false; let index = value.charCodeAt(0) === 0xfeff ? 1 : 0;
    while (index < value.length) {
      if (value.startsWith('<!--', index)) {
        const end = value.indexOf('-->', index + 4); index = end < 0 ? value.length : end + 3; continue;
      }
      if (value.startsWith('<?', index)) {
        const end = processingInstructionEnd(value, index + 2); index = end < 0 ? value.length : end + 1; continue;
      }
      if (tokenAt(value, index, '<!DOCTYPE')) {
        if (rootSeen || ranges.length) {
          throw new SecurityPolicyError('Die SVG-Datei enthält eine ungültige oder nicht sicher neutralisierbare DTD-Deklaration.', 'SVG_DTD_INVALID', { kind: rootSeen ? 'after-root' : 'multiple' });
        }
        const end = markupEnd(value, index + 9, true);
        if (end < 0) throw new SecurityPolicyError('Die SVG-Datei enthält eine ungültige oder nicht sicher neutralisierbare DTD-Deklaration.', 'SVG_DTD_INVALID', { kind: 'unterminated' });
        const declaration = value.slice(index, end + 1); const subset = internalSubset(declaration);
        ranges.push({ start: index, end: end + 1, declaration, subset }); index = end + 1; continue;
      }
      if (tokenAt(value, index, '<!ENTITY')) throw unsafeEntity('SVG_ENTITY_DECLARATION_INVALID', { kind: 'outside-doctype' });
      if (value[index] === '<' && !value.startsWith('<!', index) && !value.startsWith('</', index)) {
        rootSeen = true; const end = markupEnd(value, index + 1, false); index = end < 0 ? value.length : end + 1; continue;
      }
      index += 1;
    }

    const declaredEntities = ranges.length ? inspectInternalSubset(ranges[0].subset) : new Map();
    let sanitized = value;
    for (let rangeIndex = ranges.length - 1; rangeIndex >= 0; rangeIndex -= 1) {
      const range = ranges[rangeIndex]; sanitized = sanitized.slice(0, range.start) + sanitized.slice(range.end);
    }
    assertKnownXmlEntities(sanitized, declaredEntities);
    const declaration = ranges[0]?.declaration || '';
    return Object.freeze({
      source: sanitized,
      dtdNeutralized: ranges.length > 0,
      externalSubsetIgnored: /\b(?:SYSTEM|PUBLIC)\b/i.test(declaration.slice(9)),
      internalSubsetIgnored: Boolean(ranges[0]?.subset),
      internalEntityDeclarationsIgnored: declaredEntities.size
    });
  }

  function assertSvgSource(source, overrides = {}) {
    return prepareSvgSource(source, overrides).source;
  }

  function assertSvgDocument(document, overrides = {}) {
    const limits = resolvedLimits(overrides); const rootElement = document?.documentElement;
    if (!rootElement) throw new SecurityPolicyError('SVG-Dokument besitzt kein Wurzelelement', 'SVG_ROOT_MISSING');
    const stack = [{ element: rootElement, depth: 1 }];
    let elements = 0; let attributeCharacters = 0; let pathCharacters = 0; let maximumDepth = 0;
    while (stack.length) {
      const { element, depth } = stack.pop();
      elements += 1; maximumDepth = Math.max(maximumDepth, depth);
      if (elements > limits.svgElements) throw new SecurityPolicyError('SVG enthält zu viele Elemente', 'SVG_TOO_MANY_ELEMENTS');
      if (depth > limits.svgDepth) throw new SecurityPolicyError('SVG ist zu tief verschachtelt', 'SVG_TOO_DEEP');
      const attributes = Array.from(element?.attributes || []);
      if (attributes.length > limits.svgAttributesPerElement) {
        throw new SecurityPolicyError('Ein SVG-Element enthält zu viele Attribute', 'SVG_TOO_MANY_ATTRIBUTES');
      }
      attributes.forEach((attribute) => {
        const value = String(attribute?.value ?? '');
        attributeCharacters += String(attribute?.name || '').length + value.length;
        if (String(attribute?.name || '').toLowerCase() === 'd') pathCharacters += value.length;
      });
      if (attributeCharacters > limits.svgAttributeCharacters) {
        throw new SecurityPolicyError('SVG enthält zu viele Attributdaten', 'SVG_ATTRIBUTES_TOO_LARGE');
      }
      if (pathCharacters > limits.svgPathCharacters) {
        throw new SecurityPolicyError('SVG enthält zu komplexe Pfaddaten', 'SVG_PATH_DATA_TOO_LARGE');
      }
      const children = Array.from(element?.children || []);
      for (let index = children.length - 1; index >= 0; index -= 1) stack.push({ element: children[index], depth: depth + 1 });
    }
    return { elements, maximumDepth, attributeCharacters, pathCharacters };
  }

  function assertBitmapDimensions(width, height, overrides = {}) {
    const limits = resolvedLimits(overrides); const safeWidth = Number(width); const safeHeight = Number(height);
    if (!Number.isFinite(safeWidth) || !Number.isFinite(safeHeight) || safeWidth < 1 || safeHeight < 1) {
      throw new SecurityPolicyError('Das Bild besitzt ungültige Abmessungen', 'BITMAP_DIMENSIONS_INVALID');
    }
    if (safeWidth > limits.bitmapSide || safeHeight > limits.bitmapSide || safeWidth * safeHeight > limits.bitmapPixels) {
      const locale = root.PyroI18n?.language === 'en' ? 'en-GB' : 'de-DE';
      const count = limits.bitmapPixels.toLocaleString(locale);
      throw new SecurityPolicyError(translated('ui.theImageExceedsTheSafeLimitOfCountPixels', { count }, `Das Bild überschreitet das sichere Limit von ${count} Pixeln`), 'BITMAP_TOO_LARGE');
    }
    return { width: safeWidth, height: safeHeight, pixels: safeWidth * safeHeight };
  }

  function safeEmbeddedImageHref(value, maximum = LIMITS.embeddedImageCharacters) {
    if (typeof value !== 'string' || !value || value.length > maximum || /[\u0000-\u001f\u007f]/.test(value.slice(0, value.indexOf(',') + 1))) return '';
    return DATA_IMAGE.test(value) ? value : '';
  }

  function isBlockedObjectKey(value) {
    return BLOCKED_OBJECT_KEYS.has(String(value));
  }

  const api = Object.freeze({
    LIMITS,
    BLOCKED_OBJECT_KEYS,
    SecurityPolicyError,
    resolvedLimits,
    assertFileSize,
    prepareSvgSource,
    assertSvgSource,
    assertSvgDocument,
    assertBitmapDimensions,
    safeEmbeddedImageHref,
    isBlockedObjectKey
  });

  root.PyroSecurityPolicy = api;
  if (typeof module === 'object' && module?.exports) module.exports = api;
})(typeof globalThis !== 'undefined' ? globalThis : window);
