(() => {
  'use strict';

  const MAX_SNAPSHOT_ITEMS = 120;
  const MAX_HASH_BLOCKS = 5000;
  const MAX_RESULT_VALUE_BYTES = 60000;
  const MAX_HASH_BYTES = 10000000;
  const HASHED_POST_FIELDS = [
    'title', 'excerpt', 'status', 'slug', 'date', 'date_gmt', 'template',
    'featured_media', 'parent', 'menu_order', 'comment_status', 'ping_status', 'meta',
  ];
  const SAFE_BLOCK_NAMESPACES = new Set(['core', 'kadence', 'generateblocks', 'uagb']);
  const DENIED_BLOCKS = new Set(['core/html', 'core/freeform', 'core/shortcode']);
  const DANGEROUS_TEXT = /<\s*(?:script|iframe|object|embed|link|meta|style)\b|(?:javascript|vbscript)\s*:|data\s*:\s*(?:text\/html|image\/svg\+xml)|\bsrcdoc\s*=|<[^>]*[\s/]on[a-z]+\s*=/i;
  const SENSITIVE_KEY = /(?:^|_)(?:secret|password|passwd|api_key|private_key|credential|authorization|cookie|access_token|refresh_token|bearer_token|csrf_token|license_key|consumer_key)(?:$|_)/;
  const SENSITIVE_EXACT_KEYS = new Set([
    'token', 'apikey', 'api_token', 'auth', 'auth_token', 'session_token', 'id_token', 'nonce', 'jwt',
  ]);

  function isSensitiveKey(key) {
    const normalized = String(key || '')
      .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
      .replace(/[^A-Za-z0-9]+/g, '_')
      .replace(/^_+|_+$/g, '')
      .toLowerCase();
    return SENSITIVE_EXACT_KEYS.has(normalized) || SENSITIVE_KEY.test(normalized);
  }

  function normalizeSpace(value, limit = 500) {
    return String(value || '').replace(/\s+/g, ' ').trim().slice(0, limit);
  }

  function richTextHtml(value) {
    if (!value || typeof value !== 'object' || typeof value.toHTMLString !== 'function') return null;
    try {
      return String(value.toHTMLString());
    } catch {
      throw new Error('Ein Rich-Text-Attribut konnte nicht sicher serialisiert werden.');
    }
  }

  function canonicalize(value) {
    const richText = richTextHtml(value);
    if (richText !== null) return richText;
    if (Array.isArray(value)) return value.map(canonicalize);
    if (!value || typeof value !== 'object') return value;
    const result = Object.create(null);
    for (const key of Object.keys(value).sort()) result[key] = canonicalize(value[key]);
    return result;
  }

  async function sha256(value) {
    const bytes = new TextEncoder().encode(JSON.stringify(canonicalize(value)));
    if (bytes.byteLength > MAX_HASH_BYTES) throw new Error('Der Editorzustand ist größer als 10 MB. Der Konflikthash wird deshalb nicht unvollständig berechnet.');
    const digest = await crypto.subtle.digest('SHA-256', bytes);
    return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
  }

  function editor(frame) {
    const view = frame.contentWindow;
    const document = frame.contentDocument;
    if (!view || !document?.documentElement) throw new Error('Editor-Dokument nicht verfügbar.');
    return { view, document };
  }

  function gutenberg(frame) {
    const current = editor(frame);
    const wp = current.view.wp;
    const select = wp?.data?.select;
    const dispatch = wp?.data?.dispatch;
    if (typeof select !== 'function' || typeof dispatch !== 'function' || typeof wp?.blocks?.createBlock !== 'function') {
      throw new Error('Der geöffnete Browser-Tab ist kein unterstützter Gutenberg-Editor.');
    }
    const blockEditor = select('core/block-editor');
    const blockDispatch = dispatch('core/block-editor');
    const postEditor = select('core/editor');
    const postDispatch = dispatch('core/editor');
    if (!blockEditor || !blockDispatch || !postEditor || !postDispatch || typeof blockEditor.getBlocks !== 'function') {
      throw new Error('Die Gutenberg-Datenspeicher sind noch nicht bereit.');
    }
    return { ...current, wp, blockEditor, blockDispatch, postEditor, postDispatch };
  }

  function truncateUtf8(value, maxBytes) {
    const input = String(value);
    const parts = [];
    let bytes = 0;
    let consumed = 0;
    let sanitized = false;
    for (const character of input) {
      const codePoint = character.codePointAt(0);
      const malformedSurrogate = character.length === 1 && codePoint >= 0xd800 && codePoint <= 0xdfff;
      const safeCharacter = malformedSurrogate ? '\ufffd' : character;
      const safeCodePoint = malformedSurrogate ? 0xfffd : codePoint;
      const characterBytes = safeCodePoint <= 0x7f ? 1 : (safeCodePoint <= 0x7ff ? 2 : (safeCodePoint <= 0xffff ? 3 : 4));
      if (bytes + characterBytes > maxBytes) break;
      parts.push(safeCharacter);
      bytes += characterBytes;
      consumed += character.length;
      sanitized = sanitized || malformedSurrogate;
    }
    return { value: parts.join(''), truncated: consumed < input.length || sanitized };
  }

  function jsonByteLength(value) {
    if (typeof value === 'string') {
      let bytes = 2;
      for (let index = 0; index < value.length; index += 1) {
        const codeUnit = value.charCodeAt(index);
        if (value[index] === '"' || value[index] === '\\' || value[index] === '/') bytes += 2;
        else if (codeUnit === 0x08 || codeUnit === 0x09 || codeUnit === 0x0a || codeUnit === 0x0c || codeUnit === 0x0d) bytes += 2;
        else if (codeUnit <= 0x1f) bytes += 6;
        else if (codeUnit <= 0x7f) bytes += 1;
        else bytes += 6;
      }
      return bytes;
    }
    const encoded = JSON.stringify(value);
    return new TextEncoder().encode(encoded === undefined ? 'null' : encoded).byteLength;
  }

  const OMITTED_VALUE = Symbol('wpagently-omitted-value');

  function takeJsonValue(value, budget) {
    const used = jsonByteLength(value);
    if (used > budget.remaining) {
      budget.truncated = true;
      return OMITTED_VALUE;
    }
    budget.remaining -= used;
    return value;
  }

  function boundedString(value, budget, rawByteLimit = 5000) {
    const input = String(value);
    const raw = truncateUtf8(input, rawByteLimit);
    const rawBounded = raw.value;
    const rawEncodedBytes = jsonByteLength(rawBounded);
    if (rawEncodedBytes <= budget.remaining) {
      budget.remaining -= rawEncodedBytes;
      if (raw.truncated) budget.truncated = true;
      return rawBounded;
    }
    const characters = Array.from(rawBounded);
    let low = 0;
    let high = characters.length;
    while (low < high) {
      const middle = Math.ceil((low + high) / 2);
      if (jsonByteLength(characters.slice(0, middle).join('')) <= budget.remaining) low = middle;
      else high = middle - 1;
    }
    const result = characters.slice(0, low).join('');
    const used = jsonByteLength(result);
    if (used > budget.remaining) {
      budget.truncated = true;
      return OMITTED_VALUE;
    }
    budget.remaining -= used;
    budget.truncated = true;
    return result;
  }

  function boundedValue(value, depth = 0, budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false }) {
    if (budget.remaining <= 0) {
      budget.truncated = true;
      return OMITTED_VALUE;
    }
    if (depth > 8) {
      budget.truncated = true;
      return takeJsonValue('[Tiefe begrenzt]', budget);
    }
    const richText = richTextHtml(value);
    if (richText !== null) return boundedValue(richText, depth, budget);
    if (typeof value === 'function') {
      budget.truncated = true;
      return boundedString('[redacted]', budget, 100);
    }
    if (typeof value === 'string') {
      return boundedString(value, budget);
    }
    if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
      return takeJsonValue(value, budget);
    }
    if (Array.isArray(value)) {
      if (value.length > 200) budget.truncated = true;
      if (budget.remaining < 2) {
        budget.truncated = true;
        return OMITTED_VALUE;
      }
      budget.remaining -= 2;
      const result = [];
      for (const item of value.slice(0, 200)) {
        const separatorBytes = result.length > 0 ? 1 : 0;
        if (budget.remaining <= separatorBytes) {
          budget.truncated = true;
          break;
        }
        budget.remaining -= separatorBytes;
        const bounded = boundedValue(item, depth + 1, budget);
        if (bounded === OMITTED_VALUE) break;
        result.push(bounded);
      }
      return result;
    }
    if (!value || typeof value !== 'object') return boundedValue(String(value ?? '').slice(0, 500), depth, budget);
    const result = Object.create(null);
    const keys = Object.keys(value);
    if (keys.length > 200) budget.truncated = true;
    if (budget.remaining < 2) {
      budget.truncated = true;
      return OMITTED_VALUE;
    }
    budget.remaining -= 2;
    let outputCount = 0;
    for (const key of keys.slice(0, 200)) {
      const boundedKey = truncateUtf8(key, 1000);
      if (boundedKey.truncated || Object.hasOwn(result, boundedKey.value)) {
        budget.truncated = true;
        continue;
      }
      const separatorBytes = outputCount > 0 ? 1 : 0;
      const keyBytes = jsonByteLength(boundedKey.value) + 1;
      if (budget.remaining <= separatorBytes + keyBytes) {
        budget.truncated = true;
        break;
      }
      budget.remaining -= separatorBytes + keyBytes;
      const bounded = boundedValue(isSensitiveKey(key) ? '[redacted]' : value[key], depth + 1, budget);
      if (bounded === OMITTED_VALUE) break;
      result[boundedKey.value] = bounded;
      outputCount += 1;
    }
    return result;
  }

  function boundedPublicValue(value, budget) {
    const bounded = boundedValue(value, 0, budget);
    if (bounded !== OMITTED_VALUE) return bounded;
    budget.truncated = true;
    if (Array.isArray(value)) return [];
    if (value && typeof value === 'object') return Object.create(null);
    return null;
  }

  function blockState(block, depth = 0, counter = { value: 0, truncated: false }) {
    if (!block) return null;
    if (counter.value >= MAX_HASH_BLOCKS || depth > 100) {
      counter.truncated = true;
      return null;
    }
    counter.value += 1;
    return {
      client_id: String(block.clientId || ''),
      name: String(block.name || ''),
      attributes: block.attributes || {},
      inner_blocks: (block.innerBlocks || []).map((item) => blockState(item, depth + 1, counter)).filter(Boolean),
    };
  }

  async function editorState(frame) {
    const current = gutenberg(frame);
    const counter = { value: 0, truncated: false };
    const blocks = current.blockEditor.getBlocks().map((block) => blockState(block, 0, counter)).filter(Boolean);
    if (counter.truncated) throw new Error('Der Editor enthält mehr als 5.000 Blöcke oder über 100 Verschachtelungsebenen. Der Konflikthash wird deshalb nicht unvollständig berechnet.');
    const postId = Number(current.postEditor.getCurrentPostId?.() || 0);
    const postType = String(current.postEditor.getCurrentPostType?.() || '');
    const postState = {};
    for (const field of HASHED_POST_FIELDS) {
      postState[field] = current.postEditor.getEditedPostAttribute?.(field)
        ?? current.postEditor.getCurrentPostAttribute?.(field)
        ?? null;
    }
    const stateHash = await sha256({ post_id: postId, post_type: postType, post: postState, blocks });
    return {
      post_id: postId,
      post_type: postType,
      post: {
        title: postState.title,
        excerpt: postState.excerpt,
        status: postState.status,
        slug: postState.slug,
        template: postState.template,
        featured_media: postState.featured_media,
        parent: postState.parent,
      },
      state_hash: stateHash,
      dirty: Boolean(current.postEditor.isEditedPostDirty?.()),
      block_count: counter.value,
      blocks,
    };
  }

  function elementVisible(element) {
    if (!(element instanceof element.ownerDocument.defaultView.Element) || element.hidden || element.getAttribute('aria-hidden') === 'true') return false;
    const style = element.ownerDocument.defaultView.getComputedStyle(element);
    return style.display !== 'none' && style.visibility !== 'hidden' && style.visibility !== 'collapse' && style.opacity !== '0';
  }

  function elementSelector(element) {
    if (element.id && /^[A-Za-z_][A-Za-z0-9_-]{0,79}$/.test(element.id)) return `#${element.id}`;
    const tag = element.tagName.toLowerCase();
    const classes = Array.from(element.classList || []).filter((name) => /^[A-Za-z_][A-Za-z0-9_-]{0,79}$/.test(name)).slice(0, 2);
    if (classes.length) return `${tag}${classes.map((name) => `.${name}`).join('')}`;
    if (!element.parentElement) return tag;
    const siblings = Array.from(element.parentElement.children).filter((item) => item.tagName === element.tagName);
    return siblings.length > 1 ? `${tag}:nth-of-type(${siblings.indexOf(element) + 1})` : tag;
  }

  function accessibleName(element) {
    return normalizeSpace(
      element.getAttribute('aria-label')
        || element.getAttribute('alt')
        || element.getAttribute('title')
        || element.textContent,
      300,
    );
  }

  async function inspectPage(frame, args = {}) {
    const { document, view } = editor(frame);
    const items = [];
    const candidates = document.querySelectorAll('main, h1, h2, h3, h4, a[href], button, input, textarea, select, [role]');
    for (const element of candidates) {
      if (items.length >= MAX_SNAPSHOT_ITEMS || !elementVisible(element)) continue;
      const tag = element.tagName.toLowerCase();
      const type = tag === 'input' ? String(element.getAttribute('type') || 'text').toLowerCase() : '';
      items.push({
        ref: `e${items.length + 1}`,
        tag,
        role: normalizeSpace(element.getAttribute('role') || '', 60),
        name: accessibleName(element),
        selector: elementSelector(element),
        type: ['password', 'hidden'].includes(type) ? 'redacted' : type,
        disabled: Boolean(element.disabled),
      });
    }
    const url = new URL(view.location.href);
    const result = {
      title: normalizeSpace(document.title, 300),
      path: url.pathname,
      item_count: items.length,
      truncated: candidates.length > items.length,
      items,
    };
    if (args.include_text) result.text = normalizeSpace(document.body?.innerText || '', Number(args.text_limit) || 3000);
    const budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false };
    const bounded = boundedPublicValue(result, budget);
    if (!bounded || typeof bounded !== 'object' || Array.isArray(bounded)) {
      throw new Error('Die Seiteninspektion konnte nicht innerhalb der Ausgabegrenze dargestellt werden.');
    }
    bounded.truncated = result.truncated || budget.truncated;
    if (!Array.isArray(bounded.items)) bounded.items = [];
    return bounded;
  }

  function errorMessage(error, maxBytes = 8000) {
    let message = '';
    try {
      message = error && typeof error === 'object' && 'message' in error ? error.message : error;
    } catch {
      message = '';
    }
    return truncateUtf8(String(message || ''), maxBytes).value;
  }

  function blockTypePublic(type, budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false }) {
    const requiredShapeReserve = 32;
    if (budget.remaining <= requiredShapeReserve) {
      budget.truncated = true;
      return null;
    }
    budget.remaining -= requiredShapeReserve;
    const result = boundedPublicValue({
      name: String(type.name || ''),
      title: normalizeSpace(type.title, 200),
      category: String(type.category || ''),
      parent: Array.isArray(type.parent) ? type.parent.slice(0, 50).map(String) : [],
      ancestor: Array.isArray(type.ancestor) ? type.ancestor.slice(0, 50).map(String) : [],
      attributes: type.attributes || {},
      supports: type.supports || {},
    }, budget);
    if (!result || typeof result !== 'object' || Array.isArray(result)) return null;
    if (!result.attributes || typeof result.attributes !== 'object' || Array.isArray(result.attributes)) {
      result.attributes = Object.create(null);
    }
    if (!result.supports || typeof result.supports !== 'object' || Array.isArray(result.supports)) {
      result.supports = Object.create(null);
    }
    return result;
  }

  function findBlock(frame, clientId) {
    const current = gutenberg(frame);
    const block = current.blockEditor.getBlock(clientId);
    if (!block) throw new Error(`Block ${clientId} wurde im geöffneten Editor nicht gefunden.`);
    return { ...current, block };
  }

  function assertSafeValue(value, depth = 0, rejectSensitiveKeys = true) {
    if (depth > 12) throw new Error('Blockdaten überschreiten die erlaubte Verschachtelungstiefe.');
    const richText = richTextHtml(value);
    if (richText !== null) {
      assertSafeValue(richText, depth, rejectSensitiveKeys);
      return;
    }
    if (typeof value === 'string' && DANGEROUS_TEXT.test(value)) throw new Error('Blockdaten enthalten ausführbaren oder aktiven HTML-Inhalt.');
    if (Array.isArray(value)) {
      if (value.length > 500) throw new Error('Ein Blockdaten-Array ist zu groß.');
      for (const item of value) assertSafeValue(item, depth + 1, rejectSensitiveKeys);
    } else if (value && typeof value === 'object') {
      for (const [key, item] of Object.entries(value)) {
        if (/^on[a-z]+$/i.test(key) || key === '__proto__' || key === 'constructor' || key === 'prototype') {
          throw new Error(`Unsicherer Blockattribut-Schlüssel: ${key}`);
        }
        if (rejectSensitiveKeys && isSensitiveKey(key)) {
          throw new Error(`Das Attribut ${key} ist sensibel und darf im Live-Editor nicht geschrieben werden.`);
        }
        assertSafeValue(item, depth + 1, rejectSensitiveKeys);
      }
    }
  }

  function assertAllowedBlock(blockName) {
    const namespace = String(blockName || '').split('/')[0];
    if (!SAFE_BLOCK_NAMESPACES.has(namespace)) {
      throw new Error(`Der Block-Namespace ${namespace || '(leer)'} ist im Live-Editor nicht freigegeben.`);
    }
  }

  function valueMatchesType(value, type) {
    const richText = richTextHtml(value);
    const normalized = richText !== null ? richText : value;
    if (type === 'string') return typeof normalized === 'string';
    if (type === 'number') return typeof normalized === 'number' && Number.isFinite(normalized);
    if (type === 'integer') return Number.isInteger(normalized);
    if (type === 'boolean') return typeof normalized === 'boolean';
    if (type === 'array') return Array.isArray(normalized);
    if (type === 'object') return Boolean(normalized) && typeof normalized === 'object' && !Array.isArray(normalized);
    if (type === 'null') return normalized === null;
    return false;
  }

  function validateAttributeValue(key, value, definition) {
    if (!definition || typeof definition !== 'object' || Array.isArray(definition)) return;
    const types = Array.isArray(definition.type)
      ? definition.type.filter((type) => typeof type === 'string')
      : (typeof definition.type === 'string' ? [definition.type] : []);
    if (types.length && !types.some((type) => valueMatchesType(value, type))) {
      throw new Error(`Das Attribut ${key} muss dem registrierten Typ ${types.join(' oder ')} entsprechen.`);
    }
    if (Array.isArray(definition.enum) && !definition.enum.some((candidate) => JSON.stringify(canonicalize(candidate)) === JSON.stringify(canonicalize(value)))) {
      throw new Error(`Das Attribut ${key} gehört nicht zu den erlaubten Werten.`);
    }
    if (Array.isArray(value) && definition.items) {
      value.forEach((item, index) => validateAttributeValue(`${key}[${index}]`, item, definition.items));
    }
    if (value && typeof value === 'object' && !Array.isArray(value) && definition.properties && typeof definition.properties === 'object') {
      for (const [childKey, childValue] of Object.entries(value)) {
        if (Object.hasOwn(definition.properties, childKey)) {
          validateAttributeValue(`${key}.${childKey}`, childValue, definition.properties[childKey]);
        } else if (definition.additionalProperties === false) {
          throw new Error(`Das Attribut ${key}.${childKey} gehört nicht zum registrierten Schema.`);
        }
      }
    }
  }

  function validateAttributes(wp, blockName, attributes, validateAgentMutation = true) {
    const type = wp.blocks.getBlockType(blockName);
    if (validateAgentMutation && !type) throw new Error(`Der Blocktyp ${blockName} ist im geöffneten Editor nicht registriert.`);
    if (validateAgentMutation) assertAllowedBlock(blockName);
    if (DENIED_BLOCKS.has(blockName)) throw new Error(`Der aktive Code-Blocktyp ${blockName} ist im Live-Editor gesperrt.`);
    const schema = type?.attributes || {};
    for (const key of Object.keys(attributes || {})) {
      if (validateAgentMutation) {
        if (!Object.hasOwn(schema, key)) throw new Error(`Das Attribut ${key} gehört nicht zum registrierten Schema von ${blockName}.`);
        if (isSensitiveKey(key)) throw new Error(`Das Attribut ${key} ist sensibel und darf im Live-Editor nicht geschrieben werden.`);
        validateAttributeValue(key, attributes[key], schema[key]);
      }
    }
    assertSafeValue(attributes || {}, 0, validateAgentMutation);
  }

  function createBlockTree(wp, descriptor, counter = { value: 0 }, depth = 0) {
    if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor) || depth > 10 || counter.value >= 200) {
      throw new Error('Ungültige oder zu große innere Blockstruktur.');
    }
    const name = String(descriptor.name || '');
    const attributes = descriptor.attributes && typeof descriptor.attributes === 'object' && !Array.isArray(descriptor.attributes)
      ? descriptor.attributes
      : {};
    validateAttributes(wp, name, attributes);
    counter.value += 1;
    const inner = Array.isArray(descriptor.inner_blocks)
      ? descriptor.inner_blocks.map((item) => createBlockTree(wp, item, counter, depth + 1))
      : [];
    return wp.blocks.createBlock(name, attributes, inner);
  }

  function assertSafeBlockTree(wp, blocks, counter = { value: 0 }, depth = 0) {
    if (!Array.isArray(blocks)) throw new Error('Die Blockstruktur ist ungültig.');
    if (blocks.length === 0) return;
    if (depth > 100) throw new Error('Die Blockstruktur ist zu tief verschachtelt.');
    for (const block of blocks) {
      if (!block || counter.value >= MAX_HASH_BLOCKS) throw new Error('Die Blockstruktur ist zu groß, um sie sicher zu speichern.');
      counter.value += 1;
      validateAttributes(wp, String(block.name || ''), block.attributes || {}, false);
      assertSafeBlockTree(wp, block.innerBlocks || [], counter, depth + 1);
    }
  }

  async function assertState(frame, expected) {
    const state = await editorState(frame);
    if (state.state_hash !== expected) {
      throw new Error(`Editor-Konflikt: erwartet ${expected}, aktuell ${state.state_hash}. Lies die Struktur neu.`);
    }
    return state;
  }

  function assertExpectedPost(frame, args) {
    const expectedPostId = Number(args.expected_post_id || 0);
    const current = gutenberg(frame);
    const actualPostId = Number(current.postEditor.getCurrentPostId?.() || 0);
    if (!Number.isInteger(expectedPostId) || expectedPostId <= 0 || actualPostId !== expectedPostId) {
      throw new Error('Der sichtbare Editor zeigt nicht mehr den serverseitig geprüften Beitrag. Lies den Editorzustand neu.');
    }
    return current;
  }

  function afterPaint(view) {
    return new Promise((resolve) => view.requestAnimationFrame(() => view.requestAnimationFrame(resolve)));
  }

  async function executeGutenberg(frame, action, args) {
    const before = await assertState(frame, args.expected_state_hash);
    if (before.post_id !== Number(args.expected_post_id || 0)) {
      throw new Error('Der sichtbare Editor zeigt nicht mehr den serverseitig geprüften Beitrag. Lies den Editorzustand neu.');
    }
    const current = assertExpectedPost(frame, args);
    let affectedClientId = String(args.client_id || '');

    if (action === 'create-block') {
      validateAttributes(current.wp, args.block_name, args.attributes || {});
      const block = current.wp.blocks.createBlock(args.block_name, args.attributes || {});
      current.blockDispatch.insertBlocks(block, Number(args.index) || 0, args.parent_client_id || undefined);
      affectedClientId = block.clientId;
    } else if (action === 'update-block') {
      const target = findBlock(frame, args.client_id);
      validateAttributes(target.wp, target.block.name, args.attributes || {});
      target.blockDispatch.updateBlockAttributes(args.client_id, args.attributes || {});
    } else if (action === 'delete-block') {
      findBlock(frame, args.client_id);
      current.blockDispatch.removeBlock(args.client_id, false);
    } else if (action === 'move-block') {
      const target = findBlock(frame, args.client_id);
      const sourceRoot = target.blockEditor.getBlockRootClientId(args.client_id) || '';
      current.blockDispatch.moveBlocksToPosition([args.client_id], sourceRoot, args.parent_client_id || '', Number(args.index) || 0);
    } else if (action === 'replace-inner-blocks') {
      findBlock(frame, args.client_id);
      const counter = { value: 0 };
      const blocks = (args.blocks || []).map((item) => createBlockTree(current.wp, item, counter));
      current.blockDispatch.replaceInnerBlocks(args.client_id, blocks, false);
    } else if (action === 'undo') {
      if (typeof current.blockDispatch.undo !== 'function') throw new Error('Rückgängig ist in diesem Editor nicht verfügbar.');
      current.blockDispatch.undo();
    } else if (action === 'redo') {
      if (typeof current.blockDispatch.redo !== 'function') throw new Error('Wiederholen ist in diesem Editor nicht verfügbar.');
      current.blockDispatch.redo();
    } else if (action === 'save-post') {
      assertSafeBlockTree(current.wp, current.blockEditor.getBlocks());
      const saved = current.postDispatch.savePost();
      if (saved && typeof saved.then === 'function') await saved;
      const deadline = Date.now() + 15000;
      while (current.postEditor.isSavingPost?.() && Date.now() < deadline) {
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
      if (current.postEditor.isSavingPost?.() || current.postEditor.isEditedPostDirty?.()) {
        throw new Error('WordPress hat den sichtbaren Editorzustand nicht vollständig gespeichert.');
      }
    }

    await afterPaint(current.view);
    const after = await editorState(frame);
    if (after.post_id !== Number(args.expected_post_id || 0)) {
      throw new Error('Der sichtbare Editor zeigt nicht mehr den serverseitig geprüften Beitrag. Lies den Editorzustand neu.');
    }
    if (action !== 'save-post' && after.state_hash === before.state_hash) {
      throw new Error('Die bestätigte Editoraktion hat den Zustands-Hash nicht verändert.');
    }
    return {
      action,
      post_id: after.post_id,
      affected_client_id: affectedClientId,
      before_state_hash: before.state_hash,
      state_hash: after.state_hash,
      dirty: after.dirty,
      persisted: action === 'save-post',
    };
  }

  function waitForFrame(frame, timeout = 15000) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => reject(new Error('Der WordPress-Editor hat nicht rechtzeitig geladen.')), timeout);
      const loaded = () => {
        clearTimeout(timer);
        frame.removeEventListener('load', loaded);
        resolve();
      };
      frame.addEventListener('load', loaded);
    });
  }

  async function waitForGutenberg(frame, postId, timeout = 15000) {
    const deadline = Date.now() + timeout;
    let lastError = null;
    while (Date.now() < deadline) {
      try {
        const state = await editorState(frame);
        if (state.post_id === Number(postId)) return state;
        lastError = new Error('Der geöffnete Gutenberg-Beitrag stimmt nicht mit der angeforderten ID überein.');
      } catch (error) {
        lastError = error;
      }
      await new Promise((resolve) => setTimeout(resolve, 100));
    }
    throw new Error(lastError?.message || 'Die Gutenberg-Datenspeicher sind nicht rechtzeitig bereit geworden.');
  }

  async function openPost(frame, postId, config) {
    const target = new URL('post.php', config.adminUrl);
    target.searchParams.set('post', String(postId));
    target.searchParams.set('action', 'edit');
    const loaded = waitForFrame(frame);
    frame.src = target.href;
    await loaded;
    const actual = new URL(frame.contentWindow.location.href);
    if (actual.origin !== target.origin || actual.pathname !== target.pathname || actual.searchParams.get('post') !== String(postId)) {
      throw new Error('WordPress hat nicht den bestätigten Beitrag geöffnet.');
    }
    const state = await waitForGutenberg(frame, postId);
    const current = gutenberg(frame);
    if (typeof current.postDispatch.lockPostAutosaving !== 'function') {
      throw new Error('Dieser Gutenberg-Editor kann automatische Speicherungen nicht sicher sperren.');
    }
    current.postDispatch.lockPostAutosaving('wpagently-live-editor');
    if (typeof current.postEditor.isPostAutosavingLocked === 'function' && !current.postEditor.isPostAutosavingLocked()) {
      throw new Error('WordPress hat die Autosave-Sperre des Live-Editors nicht bestätigt.');
    }
    return { action: 'open-post', post_id: state.post_id, state_hash: state.state_hash, dirty: state.dirty, autosave_locked: true };
  }

  async function pageState(frame) {
    try {
      const { document, view } = editor(frame);
      if (view.location.href === 'about:blank') return { kind: 'empty', title: '', path: '', post_id: 0, ready: false, dirty: false, state_hash: '' };
      const url = new URL(view.location.href);
      try {
        const state = await editorState(frame);
        return { kind: 'gutenberg', title: normalizeSpace(document.title, 200), path: url.pathname, post_id: state.post_id, ready: true, dirty: state.dirty, state_hash: state.state_hash };
      } catch {
        return { kind: 'page', title: normalizeSpace(document.title, 200), path: url.pathname, post_id: 0, ready: true, dirty: false, state_hash: '' };
      }
    } catch {
      return { kind: 'error', title: '', path: '', post_id: 0, ready: false, dirty: false, state_hash: '' };
    }
  }

  async function activeContent(frame, command) {
    const action = String(command?.action || '');
    if (action === 'open-post') return { state_hash: '', blocks: [] };
    const state = await editorState(frame);
    const content = { state_hash: state.state_hash, blocks: canonicalize(state.blocks) };
    if (action === 'update-block') {
      content.target_block_name = findBlock(frame, command?.args?.client_id).block.name;
    }
    return content;
  }

  async function execute(frame, command, config) {
    const action = String(command.action || '');
    const args = command.args && typeof command.args === 'object' ? command.args : {};
    if (action !== 'open-post') assertExpectedPost(frame, args);
    if (action === 'inspect-page') return inspectPage(frame, args);
    if (action === 'gutenberg-structure') {
      const state = await editorState(frame);
      if (state.post_id !== Number(args.expected_post_id || 0)) {
        throw new Error('Der sichtbare Editor zeigt nicht mehr den serverseitig geprüften Beitrag. Lies den Editorzustand neu.');
      }
      const budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false };
      const blocks = boundedPublicValue(state.blocks, budget);
      return {
        post_id: state.post_id,
        post_type: state.post_type,
        post: boundedPublicValue(state.post, budget),
        state_hash: state.state_hash,
        dirty: state.dirty,
        block_count: state.block_count,
        truncated: budget.truncated,
        blocks,
      };
    }
    if (action === 'list-block-types') {
      const current = gutenberg(frame);
      const search = normalizeSpace(args.search, 100).toLowerCase();
      const filtered = current.wp.blocks.getBlockTypes()
        .filter((type) => !search || String(type.name).toLowerCase().includes(search) || String(type.title).toLowerCase().includes(search));
      const budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false };
      const types = [];
      for (const type of filtered.slice(0, 500)) {
        const publicType = blockTypePublic(type, budget);
        if (!publicType) break;
        types.push(publicType);
      }
      return { count: types.length, total: filtered.length, truncated: filtered.length > types.length || budget.truncated, block_types: types };
    }
    if (action === 'get-block-schema') {
      const current = gutenberg(frame);
      const type = current.wp.blocks.getBlockType(args.block_name);
      if (!type) throw new Error(`Der Blocktyp ${args.block_name} ist nicht registriert.`);
      const budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false };
      const publicType = blockTypePublic(type, budget);
      if (!publicType) throw new Error('Das registrierte Blockschema konnte nicht innerhalb der Ausgabegrenze dargestellt werden.');
      return { ...publicType, truncated: budget.truncated };
    }
    if (action === 'get-block-attributes') {
      const target = findBlock(frame, args.client_id);
      const state = await editorState(frame);
      if (state.post_id !== Number(args.expected_post_id || 0)) {
        throw new Error('Der sichtbare Editor zeigt nicht mehr den serverseitig geprüften Beitrag. Lies den Editorzustand neu.');
      }
      const budget = { remaining: MAX_RESULT_VALUE_BYTES, truncated: false };
      return { client_id: target.block.clientId, name: target.block.name, attributes: boundedPublicValue(target.block.attributes || {}, budget), attributes_truncated: budget.truncated, state_hash: state.state_hash };
    }
    if (action === 'open-post') return openPost(frame, Number(args.post_id), config);
    if (['create-block', 'update-block', 'delete-block', 'move-block', 'replace-inner-blocks', 'undo', 'redo', 'save-post'].includes(action)) {
      return executeGutenberg(frame, action, args);
    }
    throw new Error(`Unbekannter Live-Editor-Befehl: ${action}`);
  }

  window.wpagentlyLiveEditorRuntime = { execute, pageState, activeContent, editorState, canonicalize, errorMessage };
})();
