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

  const FALLBACK_LANGUAGE = 'de';
  const SYSTEM_FALLBACK_LANGUAGE = 'en';
  const STORAGE_KEY = 'pyro-ui-language';
  const PREFERENCES = new Set(['auto', 'de', 'en', 'fr', 'ru', 'es', 'zh']);
  const LANGUAGE_FLAGS = Object.freeze({
    de: 'icons/flagpack/DE.svg',
    en: 'icons/flagpack/GB-UKM.svg',
    fr: 'icons/flagpack/FR.svg',
    ru: 'icons/flagpack/RU.svg',
    es: 'icons/flagpack/ES.svg',
    zh: 'icons/flagpack/CN.svg'
  });
  const LOCALIZED_ATTRIBUTES = ['title', 'aria-label', 'placeholder', 'alt'];
  const catalogs = new Map();
  const sourceIndex = new Map();
  const templateIndex = [];
  const textRecords = new WeakMap();
  const attributeRecords = new WeakMap();
  const missingKeys = new Set();
  const nestedTranslationVariables = new Set(['action', 'current', 'detail', 'effect', 'label', 'mode', 'next', 'operation', 'orientation', 'path', 'reason', 'section', 'shape', 'tool', 'type']);
  const ignoredSelector = [
    '[data-i18n-ignore]',
    '#artboard',
    '#inlineTextEditor',
    '#debugConsoleEntries'
  ].join(', ');

  let preference = 'auto';
  let language = FALLBACK_LANGUAGE;
  let observer = null;
  let started = false;
  let applying = false;

  function normalizeLanguage(value) {
    const normalized = String(value || '').trim().toLowerCase().replace('_', '-');
    return normalized.split('-')[0];
  }

  function automaticLanguage() {
    const requested = Array.isArray(root.navigator?.languages) && root.navigator.languages.length
      ? root.navigator.languages
      : [root.navigator?.language];
    for (const candidate of requested) {
      const normalized = normalizeLanguage(candidate);
      if (PREFERENCES.has(normalized) && normalized !== 'auto') return normalized;
    }
    return SYSTEM_FALLBACK_LANGUAGE;
  }

  function resolveLanguage(nextPreference = preference) {
    const requested = nextPreference === 'auto' ? automaticLanguage() : normalizeLanguage(nextPreference);
    return catalogs.has(requested) ? requested : FALLBACK_LANGUAGE;
  }

  function interpolate(value, variables = {}) {
    return String(value).replace(/\{([a-zA-Z][\w.-]*)\}/g, (match, name) => (
      Object.prototype.hasOwnProperty.call(variables, name) ? String(variables[name]) : match
    ));
  }

  function t(key, variables = {}, requestedLanguage = language) {
    const fallback = catalogs.get(FALLBACK_LANGUAGE) || {};
    const selected = catalogs.get(requestedLanguage) || fallback;
    let value = selected[key];
    if (typeof value !== 'string') value = fallback[key];
    if (typeof value !== 'string') {
      if (!missingKeys.has(key)) {
        missingKeys.add(key);
        root.console?.debug?.(`[PYRO i18n] Fehlender Übersetzungsschlüssel: ${key}`);
      }
      return String(key);
    }
    return interpolate(value, variables);
  }

  function catalog(requestedLanguage = language) {
    const normalized = normalizeLanguage(requestedLanguage);
    return catalogs.get(normalized) || catalogs.get(FALLBACK_LANGUAGE) || Object.freeze({});
  }

  function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function rebuildSourceIndex() {
    sourceIndex.clear();
    templateIndex.length = 0;
    const orderedCatalogs = [catalogs.get(FALLBACK_LANGUAGE), ...[...catalogs.entries()].filter(([code]) => code !== FALLBACK_LANGUAGE).map(([, messages]) => messages)].filter(Boolean);
    orderedCatalogs.forEach((messages) => Object.entries(messages).forEach(([key, source]) => {
      if (typeof source !== 'string' || !source) return;
      if (!/\{[a-zA-Z][\w.-]*\}/.test(source)) {
        if (!sourceIndex.has(source)) sourceIndex.set(source, key);
        return;
      }
      const names = [];
      let cursor = 0;
      let expression = '^';
      for (const match of source.matchAll(/\{([a-zA-Z][\w.-]*)\}/g)) {
        expression += escapeRegExp(source.slice(cursor, match.index));
        expression += '([\\s\\S]*?)';
        names.push(match[1]);
        cursor = match.index + match[0].length;
      }
      expression += `${escapeRegExp(source.slice(cursor))}$`;
      templateIndex.push({ key, names, expression: new RegExp(expression), weight: source.replace(/\{[^}]+\}/g, '').length });
    }));
    templateIndex.sort((left, right) => right.weight - left.weight);
  }

  function sourceMatch(source) {
    if (typeof source !== 'string' || !source) return null;
    const directKey = sourceIndex.get(source);
    if (directKey) return { key: directKey, names: [], values: [] };
    for (const template of templateIndex) {
      const match = template.expression.exec(source);
      if (!match) continue;
      return { key: template.key, names: template.names, values: match.slice(1) };
    }
    return null;
  }

  function keyForSource(source) {
    return sourceMatch(source)?.key || '';
  }

  function translateSource(source, requestedLanguage = language) {
    if (typeof source !== 'string' || !source) return source;
    const match = sourceMatch(source);
    if (!match) return source;
    const variables = Object.fromEntries(match.names.map((name, index) => [
      name,
      nestedTranslationVariables.has(name) ? translateSource(match.values[index], requestedLanguage) : match.values[index]
    ]));
    return t(match.key, variables, requestedLanguage);
  }

  function ignored(node) {
    const element = node?.nodeType === 1 ? node : node?.parentElement;
    return !element || Boolean(element.closest?.(ignoredSelector));
  }

  function textIgnored(node) {
    return ignored(node) || Boolean(node?.parentElement?.closest?.('[data-i18n-ignore-text]'));
  }

  function localizeTextNode(node) {
    if (!node?.parentElement || textIgnored(node) || ['SCRIPT', 'STYLE', 'TEMPLATE'].includes(node.parentElement.tagName)) return;
    const current = node.data;
    if (!current.trim()) return;
    let record = textRecords.get(node);
    if (!record || current !== record.rendered) record = { source: current, rendered: current };
    const leading = record.source.match(/^\s*/)?.[0] || '';
    const trailing = record.source.match(/\s*$/)?.[0] || '';
    const source = record.source.slice(leading.length, record.source.length - trailing.length || undefined);
    const translated = `${leading}${translateSource(source)}${trailing}`;
    record.rendered = translated;
    textRecords.set(node, record);
    if (translated !== current) node.data = translated;
  }

  function attributeRecord(element) {
    let record = attributeRecords.get(element);
    if (!record) {
      record = new Map();
      attributeRecords.set(element, record);
    }
    return record;
  }

  function localizeAttribute(element, attribute) {
    if (!element.hasAttribute(attribute) || ignored(element)) return;
    const current = element.getAttribute(attribute) || '';
    if (!current) return;
    const records = attributeRecord(element);
    let record = records.get(attribute);
    if (!record || current !== record.rendered) record = { source: current, rendered: current };
    const translated = translateSource(record.source);
    record.rendered = translated;
    records.set(attribute, record);
    if (translated !== current) element.setAttribute(attribute, translated);
  }

  function localizeExplicitElement(element) {
    const textKey = element.dataset.i18n;
    if (textKey) element.textContent = t(textKey);
    const mappings = [
      ['i18nTitle', 'title'],
      ['i18nAriaLabel', 'aria-label'],
      ['i18nPlaceholder', 'placeholder'],
      ['i18nAlt', 'alt'],
      ['i18nContent', 'content']
    ];
    mappings.forEach(([datasetKey, attribute]) => {
      const key = element.dataset[datasetKey];
      if (key) element.setAttribute(attribute, t(key));
    });
  }

  function localizeElement(element) {
    if (!(element instanceof Element) || ignored(element)) return;
    localizeExplicitElement(element);
    LOCALIZED_ATTRIBUTES.forEach((attribute) => localizeAttribute(element, attribute));
    if (element.matches('meta[name="description"], meta[property^="og:"], meta[name^="twitter:"]')) localizeAttribute(element, 'content');
  }

  function localizeTree(rootNode = document.documentElement) {
    if (!rootNode || applying) return;
    applying = true;
    try {
      if (rootNode.nodeType === Node.TEXT_NODE) {
        localizeTextNode(rootNode);
        return;
      }
      if (rootNode.nodeType !== Node.ELEMENT_NODE && rootNode.nodeType !== Node.DOCUMENT_NODE) return;
      if (rootNode.nodeType === Node.ELEMENT_NODE) localizeElement(rootNode);
      rootNode.querySelectorAll?.('*').forEach(localizeElement);
      const walker = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT);
      let node;
      while ((node = walker.nextNode())) localizeTextNode(node);
    } finally {
      applying = false;
    }
  }

  function updateLanguageControl() {
    const select = document.getElementById('languageSelect');
    if (select && select.value !== preference) select.value = preference;
    const labels = { auto: t('language.auto'), de: t('language.german'), en: t('language.english'), fr: t('language.french'), ru: t('language.russian'), es: t('language.spanish'), zh: t('language.chinese') };
    select?.querySelectorAll('option').forEach((option) => { option.textContent = labels[option.value] || option.textContent; });
    const button = document.getElementById('languageMenuButton');
    const buttonLabel = document.getElementById('languageMenuButtonLabel');
    if (buttonLabel) buttonLabel.textContent = labels[preference] || labels.auto;
    if (button) {
      button.title = t('language.select');
      button.setAttribute('aria-label', t('language.select'));
    }
    const menu = document.getElementById('languageMenu');
    if (menu) menu.setAttribute('aria-label', t('language.select'));
    menu?.querySelectorAll('[data-language]').forEach((option) => {
      const code = option.dataset.language;
      const label = option.querySelector('span');
      if (label) label.textContent = labels[code] || code;
      option.setAttribute('aria-selected', String(code === preference));
    });
    const image = document.querySelector('.language-flag img');
    const flagSource = LANGUAGE_FLAGS[language] || LANGUAGE_FLAGS[FALLBACK_LANGUAGE];
    if (image) image.setAttribute('src', flagSource);
    const systemFlagSource = LANGUAGE_FLAGS[automaticLanguage()] || LANGUAGE_FLAGS[SYSTEM_FALLBACK_LANGUAGE];
    document.getElementById('languageAutoFlag')?.setAttribute('src', systemFlagSource);
    const manifestSource = { de: 'manifest.webmanifest', en: 'manifest.en.webmanifest', fr: 'manifest.fr.webmanifest', ru: 'manifest.ru.webmanifest', es: 'manifest.es.webmanifest', zh: 'manifest.zh.webmanifest' }[language] || 'manifest.webmanifest';
    document.querySelector('link[rel="manifest"]')?.setAttribute('href', manifestSource);
  }

  function applyLanguage({ announce = true } = {}) {
    language = resolveLanguage();
    document.documentElement.lang = language;
    localizeTree(document.documentElement);
    updateLanguageControl();
    if (announce) {
      document.dispatchEvent(new CustomEvent('pyro:languagechange', {
        detail: { preference, language }
      }));
    }
  }

  function setPreference(nextPreference, { persist = true, announce = true } = {}) {
    preference = PREFERENCES.has(nextPreference) ? nextPreference : 'auto';
    if (persist) {
      try { root.localStorage?.setItem(STORAGE_KEY, preference); } catch { /* Die Sprache funktioniert auch ohne Browserspeicher. */ }
    }
    applyLanguage({ announce });
    return language;
  }

  function restorePreference() {
    let stored = 'auto';
    try { stored = root.localStorage?.getItem(STORAGE_KEY) || 'auto'; } catch { /* Browserspeicher ist optional. */ }
    preference = PREFERENCES.has(stored) ? stored : 'auto';
  }

  function start() {
    if (started || typeof document === 'undefined') return;
    started = true;
    restorePreference();
    applyLanguage({ announce: false });
    document.getElementById('languageSelect')?.addEventListener('change', (event) => setPreference(event.currentTarget.value));
    const languageControl = document.querySelector('.language-control');
    const languageButton = document.getElementById('languageMenuButton');
    const languageMenu = document.getElementById('languageMenu');
    const closeLanguageMenu = ({ focus = false } = {}) => {
      if (!languageMenu || !languageButton) return;
      languageMenu.hidden = true;
      languageButton.setAttribute('aria-expanded', 'false');
      if (focus) languageButton.focus();
    };
    const openLanguageMenu = () => {
      if (!languageMenu || !languageButton) return;
      languageMenu.hidden = false;
      languageButton.setAttribute('aria-expanded', 'true');
      languageMenu.querySelector(`[data-language="${preference}"]`)?.focus();
    };
    languageButton?.addEventListener('click', () => languageMenu?.hidden ? openLanguageMenu() : closeLanguageMenu());
    languageButton?.addEventListener('keydown', (event) => {
      if (!['ArrowDown', 'ArrowUp'].includes(event.key)) return;
      event.preventDefault();
      openLanguageMenu();
    });
    languageMenu?.addEventListener('click', (event) => {
      const option = event.target.closest('[data-language]');
      if (!option) return;
      setPreference(option.dataset.language);
      closeLanguageMenu({ focus: true });
    });
    languageMenu?.addEventListener('keydown', (event) => {
      const options = [...languageMenu.querySelectorAll('[data-language]')];
      const index = Math.max(0, options.indexOf(document.activeElement));
      if (event.key === 'Escape') { event.preventDefault(); closeLanguageMenu({ focus: true }); return; }
      if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
      event.preventDefault();
      const next = event.key === 'Home' ? 0 : event.key === 'End' ? options.length - 1
        : event.key === 'ArrowDown' ? (index + 1) % options.length : (index - 1 + options.length) % options.length;
      options[next]?.focus();
    });
    document.addEventListener('pointerdown', (event) => {
      if (!languageMenu?.hidden && !languageControl?.contains(event.target)) closeLanguageMenu();
    });
    languageControl?.addEventListener('focusout', () => root.requestAnimationFrame?.(() => {
      if (!languageMenu?.hidden && !languageControl.contains(document.activeElement)) closeLanguageMenu();
    }));
    observer = new MutationObserver((mutations) => {
      if (applying) return;
      mutations.forEach((mutation) => {
        if (mutation.type === 'characterData') localizeTextNode(mutation.target);
        else if (mutation.type === 'attributes') localizeAttribute(mutation.target, mutation.attributeName);
        else mutation.addedNodes.forEach((node) => localizeTree(node));
      });
    });
    observer.observe(document.documentElement, {
      subtree: true,
      childList: true,
      characterData: true,
      attributes: true,
      attributeFilter: [...LOCALIZED_ATTRIBUTES, 'content']
    });
    root.addEventListener?.('languagechange', () => {
      if (preference === 'auto') applyLanguage();
    });
  }

  function register(code, messages) {
    const normalized = normalizeLanguage(code);
    if (!normalized || !messages || typeof messages !== 'object') throw new TypeError('Ungültiger PYRO-Sprachkatalog');
    catalogs.set(normalized, Object.freeze({ ...(catalogs.get(normalized) || {}), ...messages }));
    rebuildSourceIndex();
    if (started && (normalized === language || normalized === FALLBACK_LANGUAGE)) applyLanguage({ announce: false });
  }

  const api = Object.freeze({
    register,
    start,
    t,
    catalog,
    keyForSource,
    translateSource,
    setPreference,
    localizeTree,
    get language() { return language; },
    get preference() { return preference; },
    get fallbackLanguage() { return FALLBACK_LANGUAGE; },
    get availableLanguages() { return Object.freeze([...catalogs.keys()]); }
  });

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