/* global window, wp */

(function (wp) {
  if (!wp || !wp.plugins || !wp.editPost || !wp.data || !wp.components || !wp.apiFetch || !wp.i18n) {
    return;
  }

  var el = wp.element.createElement;
  var Fragment = wp.element.Fragment;
  var useEffect = wp.element.useEffect;
  var useState = wp.element.useState;
  var useRef = wp.element.useRef;

  var registerPlugin = wp.plugins.registerPlugin;
  var PluginSidebar = wp.editPost.PluginSidebar;
  var PluginSidebarMoreMenuItem = wp.editPost.PluginSidebarMoreMenuItem;

  var PanelBody = wp.components.PanelBody;
  var TextControl = wp.components.TextControl;
  var TextareaControl = wp.components.TextareaControl;
  var SelectControl = wp.components.SelectControl;
  var Button = wp.components.Button;
  var Spinner = wp.components.Spinner;
  var Notice = wp.components.Notice;

  var useSelect = wp.data.useSelect;
  var useDispatch = wp.data.useDispatch;

  var apiFetch = wp.apiFetch;
  var __ = wp.i18n.__;

  var CONFIG = window.WPSN_EDITOR || {};
  var META = CONFIG.metaKeys || {};
  var MODULES = CONFIG.modules || {};
  var AI = CONFIG.ai || {};
  var SITEMAP = CONFIG.sitemap || {};

  var UI = CONFIG.ui || {};
  var UI_MODE = UI.mode || 'simple';
  var UI_IS_PRO = (UI_MODE === 'pro') || (UI.isPro === true);


  function charCount(str) {
    if (!str) return 0;
    return String(str).length;
  }

  // Small non-cryptographic hash to compare large strings cheaply.
  // Used for internal linking "outgoing" safety so we can tell whether the editor is dirty
  // because the user typed something, or because we just synced server content into blocks.
  function hashStr(str) {
    str = String(str || '');
    var h = 5381;
    for (var i = 0; i < str.length; i++) {
      // h * 33 + c
      h = ((h << 5) + h) + str.charCodeAt(i);
      // Force 32-bit
      h = h & 0xffffffff;
    }
    return h;
  }

  function lengthBadge(current, max, min) {
    var n = current;
    var label = n + '/' + max;
    var status = 'neutral';
    if (n === 0) {
      status = 'neutral';
    } else if (n > max) {
      status = 'error';
    } else if (min && n < min) {
      status = 'warning';
    } else {
      status = 'success';
    }

    var style = {
      display: 'inline-block',
      padding: '2px 8px',
      borderRadius: '999px',
      fontSize: '12px',
      lineHeight: '18px',
      border: '1px solid rgba(0,0,0,.1)',
      marginLeft: '8px'
    };

    if (status === 'success') {
      style.borderColor = 'rgba(0,128,0,.25)';
    }
    if (status === 'warning') {
      style.borderColor = 'rgba(230,150,0,.35)';
    }
    if (status === 'error') {
      style.borderColor = 'rgba(200,0,0,.35)';
    }

    return el('span', { style: style, title: __('Length', 'wp-seo-next') }, label);
  }

  function statusDot(status) {
    var style = {
      display: 'inline-block',
      width: '10px',
      height: '10px',
      borderRadius: '999px',
      marginRight: '8px',
      border: '1px solid rgba(0,0,0,.12)',
      background: 'rgba(0,0,0,.08)'
    };

    if (status === 'good') {
      style.background = 'rgba(0,128,0,.25)';
      style.borderColor = 'rgba(0,128,0,.35)';
    }
    if (status === 'warn') {
      style.background = 'rgba(230,150,0,.25)';
      style.borderColor = 'rgba(230,150,0,.35)';
    }
    if (status === 'bad') {
      style.background = 'rgba(200,0,0,.20)';
      style.borderColor = 'rgba(200,0,0,.35)';
    }
    if (status === 'info') {
      style.background = 'rgba(0,0,0,.08)';
      style.borderColor = 'rgba(0,0,0,.12)';
    }
    return el('span', { style: style });
  }

  function SmallMuted(text) {
    return el('div', { style: { fontSize: '12px', opacity: 0.75, marginTop: '6px' } }, text);
  }

  function LinkRow(text, href) {
    return el(
      'div',
      { style: { marginTop: '8px' } },
      el('a', { href: href, target: '_blank', rel: 'noopener' }, text)
    );
  }

  function SidebarApp() {
    var postId = useSelect(function (select) {
      return select('core/editor').getCurrentPostId();
    }, []);

    var postTitle = useSelect(function (select) {
      return select('core/editor').getEditedPostAttribute('title') || '';
    }, []);

    var postContent = useSelect(function (select) {
      return select('core/editor').getEditedPostAttribute('content') || '';
    }, []);

    var postExcerpt = useSelect(function (select) {
      return select('core/editor').getEditedPostAttribute('excerpt') || '';
    }, []);

    var postStatus = useSelect(function (select) {
      return select('core/editor').getEditedPostAttribute('status') || '';
    }, []);

    var meta = useSelect(function (select) {
      return select('core/editor').getEditedPostAttribute('meta') || {};
    }, []);

    var isDirty = useSelect(function (select) {
      var ed = select('core/editor');
      if (!ed) return false;
      if (typeof ed.isEditedPostDirty === 'function') {
        return !!ed.isEditedPostDirty();
      }
      return false;
    }, []);

    var editPost = useDispatch('core/editor').editPost;

    var [preview, setPreview] = useState(null);
    var [loading, setLoading] = useState(false);
    var [error, setError] = useState('');

    // Local suggestions
    var [suggestions, setSuggestions] = useState(null);
    var [suggestLoading, setSuggestLoading] = useState(false);
    var [suggestError, setSuggestError] = useState('');
    var [suggestNotice, setSuggestNotice] = useState('');

    // External AI
    var [aiLoadingField, setAiLoadingField] = useState('');
    var [aiError, setAiError] = useState('');
    var [aiNotice, setAiNotice] = useState('');
    var [aiResult, setAiResult] = useState({ title: '', description: '', usage: null });

    // On-page analysis (rule-based)
    var [analysis, setAnalysis] = useState(null);
    var [analysisLoading, setAnalysisLoading] = useState(false);
    var [analysisError, setAnalysisError] = useState('');
    var [analysisOpened, setAnalysisOpened] = useState(false);

    // Internal linking (Search Console)
    var [ilItems, setIlItems] = useState([]);
    var [ilLoading, setIlLoading] = useState(false);
    var [ilError, setIlError] = useState('');
    var [ilNotice, setIlNotice] = useState('');
    var [ilWorkingId, setIlWorkingId] = useState(0);
    var [ilDirection, setIlDirection] = useState('incoming');
    var [ilLastSyncedHash, setIlLastSyncedHash] = useState(0);
    var [ilTotal, setIlTotal] = useState(0);
    var [ilLastDiff, setIlLastDiff] = useState(null);

    var [ilBulkRunning, setIlBulkRunning] = useState(false);
    var [ilBulkTotal, setIlBulkTotal] = useState(0);
    var [ilBulkDone, setIlBulkDone] = useState(0);
    var [ilBulkApplied, setIlBulkApplied] = useState(0);
    var [ilBulkSkipped, setIlBulkSkipped] = useState(0);
    var [ilBulkFailed, setIlBulkFailed] = useState(0);
    var ilBulkCancelRef = useRef(false);

    // Search Console (page-level metrics + top queries)
    var [gscData, setGscData] = useState(null);
    var [gscLoading, setGscLoading] = useState(false);
    var [gscError, setGscError] = useState('');
    var [gscNotice, setGscNotice] = useState('');
    var [gscOpened, setGscOpened] = useState(false);
    var [gscInspectLoading, setGscInspectLoading] = useState(false);

    // Content brief / Query optimizer
    var [briefOpened, setBriefOpened] = useState(false);
    var [briefLoading, setBriefLoading] = useState(false);
    var [briefData, setBriefData] = useState(null);
    var [briefStored, setBriefStored] = useState(null);
    var [briefError, setBriefError] = useState('');
    var [briefNotice, setBriefNotice] = useState('');
    var [briefQuery, setBriefQuery] = useState('');
    var [briefPreset, setBriefPreset] = useState('auto'); // auto|blog_post|service_page|local_landing
    var [briefMode, setBriefMode] = useState('local');
    var [briefPersist, setBriefPersist] = useState(true);

    // Outline draft (editable in UI; can add missing queries as H2).
    var [briefOutlineDraft, setBriefOutlineDraft] = useState(null);
    var briefOutlineBaseRef = useRef(null);

    // Visual feedback for where an outline item was inserted (mini-diff + highlight/scroll).
    var [briefOutlineFlash, setBriefOutlineFlash] = useState(null); // {type:'h2'|'h3', sectionIdx:number, h2:string, inserted:string, before:string, after:string, ts:number}
    var briefOutlineRefs = useRef({});

    // Brief insert options
    var [briefInsertLocation, setBriefInsertLocation] = useState('end'); // end|after_first|after_toc
    var [briefInsertEmptyParagraphs, setBriefInsertEmptyParagraphs] = useState(true);

    // Auto-clear outline flash + scroll to the changed section.
    useEffect(function () {
      if (!briefOutlineFlash || typeof briefOutlineFlash.sectionIdx !== 'number') return;

      try {
        var node = briefOutlineRefs.current && briefOutlineRefs.current[briefOutlineFlash.sectionIdx];
        if (node && typeof node.scrollIntoView === 'function') {
          node.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      } catch (e) {
        // ignore
      }

      var t = window.setTimeout(function () {
        setBriefOutlineFlash(null);
      }, 2600);

      return function () {
        window.clearTimeout(t);
      };
    }, [briefOutlineFlash ? briefOutlineFlash.ts : 0]);

    function normalizeText(s) {
      s = (s == null) ? '' : String(s);
      s = s.toLowerCase();
      // Keep unicode letters; remove common punctuation.
      s = s.replace(/[\.,!?;:\"\(\)\[\]\{\}<>]/g, ' ');
      s = s.replace(/\s+/g, ' ').trim();
      return s;
    }

    function toHeadingFromQuery(q) {
      q = (q == null) ? '' : String(q);
      q = q.trim();
      if (!q) return '';
      q = q.replace(/[\s\-–—]+$/g, '');
      q = q.replace(/[\?\!\.,:;]+$/g, '');
      if (!q) return '';
      return q.charAt(0).toUpperCase() + q.slice(1);
    }

    function cloneOutline(outline) {
      if (!Array.isArray(outline)) return [];
      return outline
        .map(function (sec) {
          if (!sec || !sec.title) return null;
          var t = String(sec.title || '').trim();
          if (!t) return null;
          var h3 = Array.isArray(sec.h3) ? sec.h3.map(function (x) { return String(x || '').trim(); }).filter(Boolean) : [];
          return { title: t, h3: h3 };
        })
        .filter(Boolean);
    }

    function getBriefPayload() {
      if (briefData && briefData.brief) return briefData.brief;
      if (briefData && briefData.data && briefData.data.brief) return briefData.data.brief;
      return null;
    }

    // Reset outline draft whenever a new brief is loaded/generated.
    useEffect(function () {
      try {
        var payload = getBriefPayload();
        if (payload && payload.outline && Array.isArray(payload.outline)) {
          var base = cloneOutline(payload.outline);
          briefOutlineBaseRef.current = base;
          setBriefOutlineDraft(base);
        } else {
          briefOutlineBaseRef.current = null;
          setBriefOutlineDraft(null);
        }
      } catch (e) {
        // ignore
      }
    }, [briefData]);

    function isOutlineDirty() {
      try {
        var base = briefOutlineBaseRef.current;
        if (!base || !Array.isArray(base)) return false;
        if (!briefOutlineDraft || !Array.isArray(briefOutlineDraft)) return false;
        var a = JSON.stringify(base);
        var b = JSON.stringify(briefOutlineDraft);
        return a !== b;
      } catch (e) {
        return false;
      }
    }

    function resetOutlineDraft() {
      var base = briefOutlineBaseRef.current;
      if (!base || !Array.isArray(base)) return;
      setBriefOutlineDraft(cloneOutline(base));
      setBriefNotice(__('Outline draft reset.', 'wp-seo-next'));
    }

    function removeOutlineSection(idx) {
      idx = Number(idx || 0);
      setBriefOutlineDraft(function (prev) {
        if (!Array.isArray(prev)) return prev;
        return prev.filter(function (_sec, i) { return i !== idx; });
      });
    }

    function addMissingQueryAsH2(q) {
      var h2 = toHeadingFromQuery(q);
      if (!h2) return;
      var n = normalizeText(h2);

      var newIdx = (briefOutlineDraft && Array.isArray(briefOutlineDraft)) ? briefOutlineDraft.length : 0;

      setBriefOutlineDraft(function (prev) {
        var out = Array.isArray(prev) ? prev.slice() : [];
        for (var i = 0; i < out.length; i++) {
          if (out[i] && normalizeText(out[i].title) === n) {
            return prev;
          }
        }
        out.push({ title: h2, h3: [], __wpsn_added: true });
        return out;
      });

      setBriefOutlineFlash({
        type: 'h2',
        sectionIdx: newIdx,
        h2: h2,
        inserted: h2,
        before: '',
        ts: Date.now(),
      });

      setBriefNotice(__('Added to outline draft.', 'wp-seo-next') + ' ' + h2);
    }

    function tokensFromText(s) {
      s = normalizeText(s);
      if (!s) return [];
      return s.split(' ').map(function (t) { return String(t || '').trim(); }).filter(function (t) { return t && t.length > 1; });
    }

    function tokenOverlap(aTokens, bTokens) {
      if (!Array.isArray(aTokens) || !Array.isArray(bTokens) || !aTokens.length || !bTokens.length) return 0;
      var setA = {};
      aTokens.forEach(function (t) { setA[String(t)] = 1; });
      var ov = 0;
      bTokens.forEach(function (t) {
        t = String(t);
        if (setA[t]) ov++;
      });
      return ov;
    }

    function bestInsertIndexForH3(h3List, newH3) {
      if (!Array.isArray(h3List) || !h3List.length) return 0;
      var nt = tokensFromText(newH3);
      if (!nt.length) return h3List.length;

      var bestIdx = -1;
      var bestScore = 0;

      for (var i = 0; i < h3List.length; i++) {
        var cur = h3List[i] ? String(h3List[i]) : '';
        if (!cur) continue;
        var ct = tokensFromText(cur);
        if (!ct.length) continue;

        var ov = tokenOverlap(ct, nt);
        if (ov <= 0) continue;

        // Blend overlap + ratios so short queries don't always win.
        var rNew = ov / Math.max(1, nt.length);
        var rCur = ov / Math.max(1, ct.length);
        var score = (ov * 10) + Math.round(rNew * 10) + Math.round(rCur * 6);

        if (score > bestScore) {
          bestScore = score;
          bestIdx = i;
        }
      }

      if (bestIdx >= 0 && bestScore > 0) {
        // Insert right after the most similar heading.
        return Math.min(h3List.length, bestIdx + 1);
      }

      // No good match → append.
      return h3List.length;
    }

    function findBestOutlineSectionIndexInOutline(outline, bestHeading) {
      if (!outline || !Array.isArray(outline) || !outline.length) return -1;
      bestHeading = (bestHeading == null) ? '' : String(bestHeading);
      bestHeading = bestHeading.trim();
      if (!bestHeading) return -1;

      var bn = normalizeText(bestHeading);

      // 1) Exact match against H2 titles.
      if (bn) {
        for (var i = 0; i < outline.length; i++) {
          var sec = outline[i];
          if (sec && sec.title && normalizeText(sec.title) === bn) {
            return i;
          }
        }
      }

      // 2) Exact match against existing H3 items (attach to that section).
      if (bn) {
        for (var j = 0; j < outline.length; j++) {
          var sec2 = outline[j];
          var h3 = sec2 && sec2.h3 && Array.isArray(sec2.h3) ? sec2.h3 : [];
          for (var k = 0; k < h3.length; k++) {
            if (h3[k] && normalizeText(h3[k]) === bn) {
              return j;
            }
          }
        }
      }

      // 3) Best-effort token overlap against H2 titles.
      var bt = tokensFromText(bestHeading);
      if (!bt.length) return -1;

      var bestIdx = -1;
      var bestScore = 0;
      for (var x = 0; x < outline.length; x++) {
        var sec3 = outline[x];
        if (!sec3 || !sec3.title) continue;
        var st = tokensFromText(sec3.title);
        if (!st.length) continue;
        var ov = tokenOverlap(st, bt);
        var ratio = ov / Math.max(1, bt.length);
        var score = (ov * 10) + Math.round(ratio * 10);
        if (score > bestScore) {
          bestScore = score;
          bestIdx = x;
        }
      }

      if (bestIdx >= 0 && bestScore > 0) {
        return bestIdx;
      }
      return -1;
    }

    function findBestOutlineSectionIndex(bestHeading) {
      return findBestOutlineSectionIndexInOutline(briefOutlineDraft, bestHeading);
    }

    function addMissingQueryAsH3(q, bestHeading) {
      var h3 = toHeadingFromQuery(q);
      if (!h3) return;

      var idx = findBestOutlineSectionIndex(bestHeading);
      if (idx < 0) {
        // Fallback: add as H2 at the end.
        addMissingQueryAsH2(q);
        return;
      }

      var h2Title = '';
      var beforeH3 = '';
      var afterH3 = '';
      var insertAt = 0;
      try {
        if (briefOutlineDraft && Array.isArray(briefOutlineDraft) && briefOutlineDraft[idx] && briefOutlineDraft[idx].title) {
          h2Title = String(briefOutlineDraft[idx].title);
          var list = briefOutlineDraft[idx].h3 && Array.isArray(briefOutlineDraft[idx].h3) ? briefOutlineDraft[idx].h3 : [];
          insertAt = bestInsertIndexForH3(list, h3);
          if (insertAt > 0 && list && list.length) {
            beforeH3 = String(list[Math.max(0, insertAt - 1)] || '');
          }
          if (list && list.length && insertAt < list.length) {
            afterH3 = String(list[Math.max(0, insertAt)] || '');
          }
        }
      } catch (e) {
        // ignore
      }

      var n = normalizeText(h3);
      setBriefOutlineDraft(function (prev) {
        if (!Array.isArray(prev)) return prev;
        if (!prev[idx] || !prev[idx].title) return prev;

        var out = prev.slice();
        var sec = Object.assign({}, out[idx]);
        var h3List = sec.h3 && Array.isArray(sec.h3) ? sec.h3.slice() : [];

        // Avoid duplicates (H2 title or any H3 within this section).
        if (normalizeText(sec.title) === n) {
          return prev;
        }
        for (var i = 0; i < h3List.length; i++) {
          if (h3List[i] && normalizeText(h3List[i]) === n) {
            return prev;
          }
        }

        // Insert near the closest existing H3 (token overlap), otherwise append.
        var pos = bestInsertIndexForH3(h3List, h3);
        pos = Math.max(0, Math.min(h3List.length, pos));
        h3List.splice(pos, 0, h3);
        sec.h3 = h3List;
        out[idx] = sec;
        return out;
      });

      setBriefOutlineFlash({
        type: 'h3',
        sectionIdx: idx,
        h2: h2Title,
        inserted: h3,
        before: beforeH3,
        after: afterH3,
        ts: Date.now(),
      });

      setBriefNotice(__('Inserted as H3 under:', 'wp-seo-next') + ' ' + (h2Title || bestHeading || '–'));
    }

    function removeOutlineH3(sectionIdx, h3Idx) {
      sectionIdx = Number(sectionIdx || 0);
      h3Idx = Number(h3Idx || 0);
      setBriefOutlineDraft(function (prev) {
        if (!Array.isArray(prev)) return prev;
        if (!prev[sectionIdx] || !Array.isArray(prev[sectionIdx].h3)) return prev;
        var out = prev.slice();
        var sec = Object.assign({}, out[sectionIdx]);
        sec.h3 = sec.h3.filter(function (_h, i) { return i !== h3Idx; });
        out[sectionIdx] = sec;
        return out;
      });
    }

    function setMetaPatch(patch) {
      editPost({ meta: Object.assign({}, meta, patch) });
    }

    function buildPreviewPayload() {
      var payload = {};
      payload[META.title] = meta[META.title] || '';
      payload[META.description] = meta[META.description] || '';
      payload[META.canonical] = meta[META.canonical] || '';
      payload[META.robotsIndex] = meta[META.robotsIndex] || 'default';
      payload[META.robotsFollow] = meta[META.robotsFollow] || 'default';
      payload[META.schemaType] = meta[META.schemaType] || 'default';

      // Social overrides (OpenGraph)
      if (META.ogTitle) {
        payload[META.ogTitle] = meta[META.ogTitle] || '';
      }
      if (META.ogDescription) {
        payload[META.ogDescription] = meta[META.ogDescription] || '';
      }
      if (META.ogImage) {
        payload[META.ogImage] = meta[META.ogImage] || '';
      }

      // Twitter overrides
      if (META.twitterTitle) {
        payload[META.twitterTitle] = meta[META.twitterTitle] || '';
      }
      if (META.twitterDescription) {
        payload[META.twitterDescription] = meta[META.twitterDescription] || '';
      }
      if (META.twitterImage) {
        payload[META.twitterImage] = meta[META.twitterImage] || '';
      }

      return payload;
    }

    function fetchPreview() {
      if (!postId || !CONFIG.restPreviewPath) {
        return;
      }

      setLoading(true);
      setError('');

      apiFetch({
        path: CONFIG.restPreviewPath,
        method: 'POST',
        data: {
          post_id: postId,
          meta: buildPreviewPayload(),
        },
      })
        .then(function (res) {
          setPreview(res);
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Preview error', 'wp-seo-next');
          setError(msg);
        })
        .finally(function () {
          setLoading(false);
        });
    }

    function buildAnalysisPayload() {
      var metaPayload = {};
      // Use the same keys as preview, but keep the payload small.
      if (META.title) metaPayload[META.title] = meta[META.title] || '';
      if (META.description) metaPayload[META.description] = meta[META.description] || '';
      if (META.robotsIndex) metaPayload[META.robotsIndex] = meta[META.robotsIndex] || 'default';
      if (META.robotsFollow) metaPayload[META.robotsFollow] = meta[META.robotsFollow] || 'default';
      if (META.ogImage) metaPayload[META.ogImage] = meta[META.ogImage] || '';

      return {
        post_id: postId,
        title: postTitle,
        content: postContent,
        excerpt: postExcerpt,
        focus_keyword: META.focusKeyword ? (meta[META.focusKeyword] || '') : '',
        meta: metaPayload,
      };
    }

    function fetchAnalysis() {
      if (!postId || !CONFIG.restAnalysisPath) {
        return;
      }
      if (MODULES && MODULES.analysis === false) {
        return;
      }

      setAnalysisLoading(true);
      setAnalysisError('');

      apiFetch({
        path: CONFIG.restAnalysisPath,
        method: 'POST',
        data: buildAnalysisPayload(),
      })
        .then(function (res) {
          setAnalysis(res);
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Analysis error', 'wp-seo-next');
          setAnalysisError(msg);
        })
        .finally(function () {
          setAnalysisLoading(false);
        });
    }

    function fetchSuggestions() {
      if (!postId || !CONFIG.restSuggestPath) {
        return;
      }

      setSuggestLoading(true);
      setSuggestError('');
      setSuggestNotice('');

      apiFetch({
        path: CONFIG.restSuggestPath,
        method: 'POST',
        data: {
          post_id: postId,
          title: postTitle,
          content: postContent,
          excerpt: postExcerpt,
        },
      })
        .then(function (res) {
          setSuggestions(res);
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Suggestions error', 'wp-seo-next');
          setSuggestError(msg);
        })
        .finally(function () {
          setSuggestLoading(false);
        });
    }

    function applySuggestion(which) {
      if (!suggestions) return;

      if (which === 'title') {
        var p1 = {}; p1[META.title] = suggestions.title || '';
        setMetaPatch(p1);
        setSuggestNotice(__('Suggested title applied.', 'wp-seo-next'));
        return;
      }

      if (which === 'description') {
        var p2 = {}; p2[META.description] = suggestions.description || '';
        setMetaPatch(p2);
        setSuggestNotice(__('Suggested description applied.', 'wp-seo-next'));
      }
    }

    function applySuggestionAll() {
      if (!suggestions) return;

      var patch = {};
      var t = suggestions.title ? String(suggestions.title) : '';
      var d = suggestions.description ? String(suggestions.description) : '';

      if (t) patch[META.title] = t;
      if (d) patch[META.description] = d;

      if (Object.keys(patch).length === 0) return;

      setMetaPatch(patch);
      setSuggestNotice(__('All suggestions applied.', 'wp-seo-next'));
    }

    function generateAI(field) {
      if (!postId || !CONFIG.restAIGeneratePath) {
        return;
      }

      setAiError('');
      setAiNotice('');

      // UI pre-checks.
      if (!MODULES.ai) {
        setAiError(__('AI module is disabled. Enable it in Settings → Modules.', 'wp-seo-next'));
        return;
      }
      if (!AI.externalEnabled) {
        setAiError(__('External AI is disabled in Settings.', 'wp-seo-next'));
        return;
      }
      if (!AI.providerConfigured) {
        setAiError(__('OpenAI API key is not configured in Settings.', 'wp-seo-next'));
        return;
      }
      if (AI.canUseExternal === false) {
        setAiError(__('You do not have permission to use external AI. Ask an admin to grant access (capability: wpsn_use_ai).', 'wp-seo-next'));
        return;
      }
      if (AI.noDrafts && postStatus && postStatus !== 'publish' && postStatus !== 'private') {
        setAiError(__('Generation is blocked for drafts by privacy setting. Publish the post or disable the restriction in Settings.', 'wp-seo-next'));
        return;
      }

      setAiLoadingField(field);

      apiFetch({
        path: CONFIG.restAIGeneratePath,
        method: 'POST',
        data: {
          post_id: postId,
          field: field,
          title: postTitle,
          content: postContent,
          excerpt: postExcerpt,
          meta: buildPreviewPayload(),
        },
      })
        .then(function (res) {
          var txt = (res && res.text) ? String(res.text) : '';
          var usage = (res && res.usage) ? res.usage : null;
          if (field === 'title') {
            setAiResult(function (prev) {
              return { title: txt, description: prev.description, usage: usage };
            });
          } else {
            setAiResult(function (prev) {
              return { title: prev.title, description: txt, usage: usage };
            });
          }

          if (usage && usage.tokens_total) {
            var cost = usage.cost_usd ? usage.cost_usd : 0;
            setAiNotice(__('Generated. Tokens:', 'wp-seo-next') + ' ' + usage.tokens_total + ' · ' + __('Cost:', 'wp-seo-next') + ' $' + Number(cost).toFixed(4));
          } else {
            setAiNotice(__('Generated.', 'wp-seo-next'));
          }
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('AI generation error', 'wp-seo-next');
          setAiError(msg);
        })
        .finally(function () {
          setAiLoadingField('');
        });
    }

    function applyAI(field) {
      if (field === 'title') {
        var t = aiResult.title || '';
        var pp = {}; pp[META.title] = t;
        setMetaPatch(pp);
        setAiNotice(__('AI title applied.', 'wp-seo-next'));
      } else {
        var d = aiResult.description || '';
        var pp2 = {}; pp2[META.description] = d;
        setMetaPatch(pp2);
        setAiNotice(__('AI description applied.', 'wp-seo-next'));
      }
    }

    function applyAIAll() {
      var patch = {};
      var t = aiResult && aiResult.title ? String(aiResult.title) : '';
      var d = aiResult && aiResult.description ? String(aiResult.description) : '';

      if (t) patch[META.title] = t;
      if (d) patch[META.description] = d;

      if (Object.keys(patch).length === 0) return;

      setMetaPatch(patch);
      setAiNotice(__('All AI results applied.', 'wp-seo-next'));
    }

    function fetchGscData() {
      if (!postId || !CONFIG.restGscPagePath) {
        return;
      }
      if (!MODULES || !MODULES.searchConsole) {
        setGscError(__('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next'));
        return;
      }

      setGscLoading(true);
      setGscError('');
      setGscNotice('');

      var path = CONFIG.restGscPagePath + '?post_id=' + encodeURIComponent(postId) + '&limit=12';
      apiFetch({ path: path })
        .then(function (res) {
          setGscData(res);
          if (res && res.ok === false && res.message) {
            setGscError(String(res.message));
          }
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to load Search Console data.', 'wp-seo-next');
          setGscError(msg);
        })
        .finally(function () {
          setGscLoading(false);
        });
    }

    function openGscInspect() {
      // Direct-first: open admin-post handler which redirects to the GSC URL Inspection deep link.
      // This avoids admin-ajax.php (often rate-limited / HTTP 503 on shared hosting).
      if (!CONFIG.gsc || !CONFIG.gsc.inspectUrlBase) {
        setGscError(__('Search Console is not configured for inspection.', 'wp-seo-next'));
        return;
      }

      var url = (gscData && gscData.page_url) ? String(gscData.page_url) : '';
      if (!url) {
        setGscError(__('No page URL available for inspection.', 'wp-seo-next'));
        return;
      }

      setGscInspectLoading(true);
      setGscError('');
      setGscNotice('');

      try {
        var base = String(CONFIG.gsc.inspectUrlBase);
        var sep = base.indexOf('?') === -1 ? '?' : '&';
        var href = base + sep + 'url=' + encodeURIComponent(url);

        // If we know the active property (from REST payload), pass it too.
        var prop = (gscData && gscData.external && gscData.external.property) ? String(gscData.external.property) : '';
        if (prop) {
          href += '&property=' + encodeURIComponent(prop);
        }

        window.open(href, '_blank', 'noopener');
        setGscInspectLoading(false);
      } catch (e) {
        setGscInspectLoading(false);
        setGscError(__('Failed to open URL Inspection.', 'wp-seo-next'));
      }
    }

    function fetchBriefStored() {
      if (!postId || !CONFIG.restBriefPath) {
        return;
      }
      if (!MODULES || !MODULES.searchConsole) {
        setBriefError(__('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next'));
        return;
      }

      setBriefLoading(true);
      setBriefError('');
      setBriefNotice('');

      var path = CONFIG.restBriefPath + '?post_id=' + encodeURIComponent(postId);
      apiFetch({ path: path })
        .then(function (res) {
          if (res && res.ok && res.brief) {
            setBriefStored(res.brief);
            if (res.brief && res.brief.data) {
              setBriefData(res.brief.data);
              if (res.brief.data.meta && res.brief.data.meta.preset) {
                setBriefPreset(String(res.brief.data.meta.preset));
              }
            }
            if ((!briefQuery || !String(briefQuery).trim()) && res.brief && res.brief.query) {
              setBriefQuery(String(res.brief.query));
            }
          }
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to load saved brief.', 'wp-seo-next');
          setBriefError(msg);
        })
        .finally(function () {
          setBriefLoading(false);
        });
    }

    function generateBrief() {
      if (!postId || !CONFIG.restBriefGeneratePath) {
        return;
      }
      if (!MODULES || !MODULES.searchConsole) {
        setBriefError(__('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next'));
        return;
      }

      var q = String(briefQuery || '').trim();
      if (!q) {
        setBriefError(__('Target query is required.', 'wp-seo-next'));
        return;
      }

      setBriefLoading(true);
      setBriefError('');
      setBriefNotice('');

      apiFetch({
        path: CONFIG.restBriefGeneratePath,
        method: 'POST',
        data: {
          post_id: postId,
          query: q,
          preset: String(briefPreset || 'auto'),
          mode: String(briefMode || 'local'),
          persist: !!briefPersist,
        },
      })
        .then(function (res) {
          if (res && res.ok) {
            setBriefData({ brief: res.brief, meta: res.meta });
            setBriefStored({
              query: q,
              source: (res.meta && res.meta.source) ? res.meta.source : String(briefMode || 'local'),
              updated_at: (res.meta && res.meta.generated_at) ? res.meta.generated_at : '',
              data: { brief: res.brief, meta: res.meta },
            });
            setBriefNotice(__('Brief generated.', 'wp-seo-next'));
          } else {
            setBriefError(__('Failed to generate brief.', 'wp-seo-next'));
          }
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to generate brief.', 'wp-seo-next');
          if (e && e.data && e.data.retry_after) {
            msg = msg + ' (' + __('retry after', 'wp-seo-next') + ' ' + e.data.retry_after + 's)';
          }
          setBriefError(msg);
        })
        .finally(function () {
          setBriefLoading(false);
        });
    }

    function insertBriefBlocks(includeFaq) {
      try {
        var payload = getBriefPayload();
        if (!payload) {
          return;
        }
        if (!wp || !wp.blocks || typeof wp.blocks.createBlock !== 'function') {
          return;
        }
        var beDispatch = wp.data && wp.data.dispatch ? wp.data.dispatch('core/block-editor') : null;
        var beSelect = wp.data && wp.data.select ? wp.data.select('core/block-editor') : null;
        if (!beDispatch || typeof beDispatch.insertBlocks !== 'function' || !beSelect || typeof beSelect.getBlockCount !== 'function') {
          return;
        }

        var blocks = [];
        var insertEmpty = !!briefInsertEmptyParagraphs;

        function maybeParagraph(content) {
          var c = (content == null) ? '' : String(content);
          if (!insertEmpty && !c.trim()) {
            return;
          }
          blocks.push(wp.blocks.createBlock('core/paragraph', { content: c }));
        }

        function isTocBlock(block) {
          if (!block || !block.name) return false;
          var name = String(block.name);
          if (name === 'core/table-of-contents') return true;
          if (name.indexOf('table-of-contents') !== -1) return true;
          if (name.indexOf('/toc') !== -1) return true;
          if (name.indexOf('toc-') !== -1) return true;
          if (name.indexOf('toc_') !== -1) return true;
          if (name.indexOf('toc') !== -1 && name.length <= 32) return true;
          return false;
        }
        var outline = (briefOutlineDraft && Array.isArray(briefOutlineDraft))
          ? briefOutlineDraft
          : (payload.outline && Array.isArray(payload.outline) ? payload.outline : []);
        outline.forEach(function (sec) {
          if (!sec || !sec.title) return;
          blocks.push(wp.blocks.createBlock('core/heading', { level: 2, content: String(sec.title) }));
          maybeParagraph('');
          var h3 = sec.h3 && Array.isArray(sec.h3) ? sec.h3 : [];
          h3.forEach(function (h) {
            if (!h) return;
            blocks.push(wp.blocks.createBlock('core/heading', { level: 3, content: String(h) }));
            maybeParagraph('');
          });
        });

        if (includeFaq) {
          var faq = payload.faq && Array.isArray(payload.faq) ? payload.faq : [];
          if (faq.length) {
            blocks.push(wp.blocks.createBlock('core/heading', { level: 2, content: 'FAQ' }));
            maybeParagraph('');
            faq.forEach(function (item) {
              if (!item || !item.q) return;
              blocks.push(wp.blocks.createBlock('core/heading', { level: 3, content: String(item.q) }));
              maybeParagraph(item.a ? String(item.a) : '');
            });
          }
        }

        if (!blocks.length) {
          return;
        }

        var idx = beSelect.getBlockCount();
        var loc = String(briefInsertLocation || 'end');
        if (loc === 'after_first') {
          idx = Math.min(1, idx);
        } else if (loc === 'after_toc' && typeof beSelect.getBlocks === 'function') {
          var all = beSelect.getBlocks();
          if (Array.isArray(all)) {
            for (var i = 0; i < all.length; i++) {
              if (isTocBlock(all[i])) {
                idx = i + 1;
                break;
              }
            }
          }
        }

        beDispatch.insertBlocks(blocks, idx);
        setBriefNotice(__('Inserted brief blocks into the editor.', 'wp-seo-next'));
      } catch (e) {
        // ignore
      }
    }

    function fetchInternalLinks() {
      if (!postId || !CONFIG.restInternalLinksPath) {
        return;
      }
      if (!MODULES || !MODULES.searchConsole) {
        setIlError(__('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next'));
        return;
      }

      setIlLoading(true);
      setIlError('');
      setIlNotice('');

      var path = CONFIG.restInternalLinksPath + '?post_id=' + encodeURIComponent(postId) + '&limit=20&direction=' + encodeURIComponent(ilDirection || 'incoming');
      apiFetch({ path: path })
        .then(function (res) {
          var items = res && res.items ? res.items : [];
          setIlItems(items);
          var total = (res && typeof res.total === 'number') ? res.total : items.length;
          setIlTotal(total);
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to load internal links.', 'wp-seo-next');
          setIlError(msg);
        })
        .finally(function () {
          setIlLoading(false);
        });
    }

    function syncOutgoingEditorContent(newContent) {
      try {
        newContent = String(newContent || '');
        if (!newContent) {
          return false;
        }

        // Update blocks without a full reload.
        if (wp && wp.blocks && typeof wp.blocks.parse === 'function') {
          var blocks = wp.blocks.parse(newContent);
          var be = wp.data && wp.data.dispatch ? wp.data.dispatch('core/block-editor') : null;
          if (be && typeof be.resetBlocks === 'function') {
            be.resetBlocks(blocks);
          }
        }

        // Ensure core/editor sees the updated content.
        editPost({ content: newContent });

        // Update our baseline so we can apply multiple outgoing suggestions without forcing a manual save.
        setIlLastSyncedHash(hashStr(newContent));

        // If the editor supports refreshPost(), re-fetch saved post to keep dirty state in sync.
        var ed = wp.data && wp.data.dispatch ? wp.data.dispatch('core/editor') : null;
        if (ed && typeof ed.refreshPost === 'function') {
          ed.refreshPost();
        }

        return true;
      } catch (e) {
        return false;
      }
    }

    function applyInternalLink(id) {
      if (!id || !CONFIG.restInternalLinksApplyPath) return;

      // Safety: outgoing suggestions modify THIS post content.
      // Avoid applying while the editor has unsaved changes (would overwrite later and lose the inserted link).
      if (ilDirection === 'outgoing' && isDirty) {
        // Allow if the only reason we are "dirty" is that we just synced server content into the editor.
        // If the user actually typed changes, content hash will differ from our last synced baseline.
        var allow = false;
        try {
          if (ilLastSyncedHash) {
            var curr = hashStr(postContent || '');
            if (curr === ilLastSyncedHash) {
              allow = true;
            }
          }
        } catch (e) {
          // ignore
        }
        if (!allow) {
          setIlError(__('Please save/update this post before applying outgoing internal link suggestions.', 'wp-seo-next'));
          return;
        }
      }

      setIlWorkingId(id);
      setIlError('');
      setIlNotice('');
      apiFetch({
        path: CONFIG.restInternalLinksApplyPath,
        method: 'POST',
        data: { suggestion_id: id, return_content: ilDirection === 'outgoing' ? true : false },
      })
        .then(function (res) {
          setIlItems(function (prev) { return (prev || []).filter(function (x) { return x.id !== id; }); });

          if (ilDirection === 'outgoing') {
            // Smooth sync: update editor blocks/content without a full reload.
            try {
              var newContent = res && res.new_content ? String(res.new_content) : '';
              var diff = res && res.diff ? res.diff : null;
              if (diff) {
                setIlLastDiff(diff);
              } else {
                setIlLastDiff(null);
              }
              if (newContent && syncOutgoingEditorContent(newContent)) {
                setIlNotice(__('Link applied to this post (editor updated).', 'wp-seo-next'));
              } else {
                setIlNotice(__('Link applied to this post.', 'wp-seo-next'));
              }
            } catch (e) {
              setIlNotice(__('Link applied to this post.', 'wp-seo-next'));
            }
          } else {
            setIlNotice(__('Link applied (source post updated).', 'wp-seo-next'));
          }
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to apply link.', 'wp-seo-next');
          setIlError(msg);
        })
        .finally(function () {
          setIlWorkingId(0);
        });
    }

    function cancelApplyAllOutgoing() {
      ilBulkCancelRef.current = true;
    }

    function resetIlBulkProgress() {
      setIlBulkTotal(0);
      setIlBulkDone(0);
      setIlBulkApplied(0);
      setIlBulkSkipped(0);
      setIlBulkFailed(0);
    }

    function applyAllOutgoing() {
      if (!postId || !CONFIG.restInternalLinksApplyPath || !CONFIG.restInternalLinksPath) {
        return;
      }
      if (ilDirection !== 'outgoing') {
        setIlDirection('outgoing');
        // Will auto-refresh and user can click again.
        return;
      }

      // Same safety rule as single apply: don't run if user has unsaved manual edits.
      if (isDirty) {
        var allow = false;
        try {
          if (ilLastSyncedHash) {
            var curr = hashStr(postContent || '');
            if (curr === ilLastSyncedHash) {
              allow = true;
            }
          }
        } catch (e) {
          // ignore
        }
        if (!allow) {
          setIlError(__('Please save/update this post before applying outgoing internal link suggestions.', 'wp-seo-next'));
          return;
        }
      }

      setIlError('');
      setIlNotice('');
      setIlLastDiff(null);
      resetIlBulkProgress();
      ilBulkCancelRef.current = false;
      setIlBulkRunning(true);

      var seen = {};
      var applied = 0;
      var skipped = 0;
      var failed = 0;
      var total = 0;

      function updateProgress() {
        var done = applied + skipped + failed;
        setIlBulkApplied(applied);
        setIlBulkSkipped(skipped);
        setIlBulkFailed(failed);
        setIlBulkDone(done);
        if (total) {
          setIlBulkTotal(total);
        }
      }

      function finish() {
        setIlBulkRunning(false);
        setIlWorkingId(0);

        var done = applied + skipped + failed;
        if (done > 0) {
          var msg = __('Outgoing apply finished.', 'wp-seo-next') + ' ' +
            __('Applied', 'wp-seo-next') + ': ' + applied +
            ' · ' + __('Skipped', 'wp-seo-next') + ': ' + skipped +
            ' · ' + __('Failed', 'wp-seo-next') + ': ' + failed;
          setIlNotice(msg);
        } else {
          setIlNotice(__('No outgoing suggestions to apply.', 'wp-seo-next'));
        }

        // Refresh the list.
        fetchInternalLinks();
      }

      function fetchBatch() {
        if (ilBulkCancelRef.current) {
          finish();
          return;
        }

        var path = CONFIG.restInternalLinksPath + '?post_id=' + encodeURIComponent(postId) + '&limit=100&direction=outgoing';
        apiFetch({ path: path })
          .then(function (res) {
            var items = res && res.items ? res.items : [];
            if (!total) {
              total = (res && typeof res.total === 'number') ? res.total : items.length;
              setIlBulkTotal(total);
            }

            // Collect unseen IDs to process.
            var ids = [];
            for (var i = 0; i < items.length; i++) {
              var sid = items[i] && items[i].id ? Number(items[i].id) : 0;
              if (!sid) continue;
              if (!seen[sid]) {
                seen[sid] = true;
                ids.push(sid);
              }
            }

            if (!ids.length) {
              finish();
              return;
            }

            processOne(ids, 0);
          })
          .catch(function (e) {
            var msg = (e && e.message) ? e.message : __('Failed to load internal links.', 'wp-seo-next');
            setIlError(msg);
            finish();
          });
      }

      function processOne(ids, idx) {
        if (ilBulkCancelRef.current) {
          finish();
          return;
        }
        if (idx >= ids.length) {
          // Fetch next batch (in case there are more than 100).
          fetchBatch();
          return;
        }

        var id = ids[idx];
        setIlWorkingId(id);

        apiFetch({
          path: CONFIG.restInternalLinksApplyPath,
          method: 'POST',
          data: { suggestion_id: id, return_content: true },
        })
          .then(function (res) {
            applied++;
            updateProgress();

            // Remove from current UI list (best effort).
            setIlItems(function (prev) { return (prev || []).filter(function (x) { return x.id !== id; }); });

            // Sync editor content and show mini-diff.
            try {
              var newContent = res && res.new_content ? String(res.new_content) : '';
              var diff = res && res.diff ? res.diff : null;
              if (diff) {
                setIlLastDiff(diff);
              }
              if (newContent) {
                syncOutgoingEditorContent(newContent);
              }
            } catch (e) {
              // ignore
            }

            // Continue.
            setTimeout(function () { processOne(ids, idx + 1); }, 60);
          })
          .catch(function (e) {
            // 409 = noop (already linked / anchor not found).
            var code = e && e.code ? String(e.code) : '';
            if (code === 'apply_noop') {
              skipped++;
            } else {
              failed++;
            }
            updateProgress();
            setTimeout(function () { processOne(ids, idx + 1); }, 60);
          });
      }

      // Kick off.
      fetchBatch();
    }

    function dismissInternalLink(id) {
      if (!id || !CONFIG.restInternalLinksDismissPath) return;
      setIlWorkingId(id);
      setIlError('');
      setIlNotice('');
      apiFetch({
        path: CONFIG.restInternalLinksDismissPath,
        method: 'POST',
        data: { suggestion_id: id },
      })
        .then(function () {
          setIlItems(function (prev) { return (prev || []).filter(function (x) { return x.id !== id; }); });
          setIlNotice(__('Suggestion dismissed.', 'wp-seo-next'));
        })
        .catch(function (e) {
          var msg = (e && e.message) ? e.message : __('Failed to dismiss suggestion.', 'wp-seo-next');
          setIlError(msg);
        })
        .finally(function () {
          setIlWorkingId(0);
        });
    }

    // Fetch internal links on load / post switch.
    useEffect(function () {
      if (!MODULES || !MODULES.searchConsole) return;
      if (!postId) return;
      // Lazy-ish: don’t block editor, but show content quickly.
      var t = setTimeout(fetchInternalLinks, 200);
      return function () { clearTimeout(t); };
    }, [postId, ilDirection]);

    // Track a "clean" baseline of the editor content for outgoing safety.
    // When the post is not dirty, we assume the editor matches the saved state.
    useEffect(function () {
      if (!postId) return;
      if (!MODULES || !MODULES.searchConsole) return;
      if (!isDirty) {
        setIlLastSyncedHash(hashStr(postContent || ''));
      }
    }, [postId, isDirty, postContent]);

// Auto-refresh preview (debounced).
    useEffect(
      function () {
        var t = setTimeout(fetchPreview, 350);
        return function () {
          clearTimeout(t);
        };
      },
      [
        postId,
        meta[META.title],
        meta[META.description],
        meta[META.canonical],
        meta[META.robotsIndex],
        meta[META.robotsFollow],
        meta[META.schemaType],
        meta[META.ogTitle],
        meta[META.ogDescription],
        meta[META.ogImage],
        meta[META.twitterTitle],
        meta[META.twitterDescription],
        meta[META.twitterImage],
      ]
    );

    // Auto-refresh analysis only when the panel was opened (debounced).
    useEffect(
      function () {
        if (!analysisOpened) return;
        var t = setTimeout(fetchAnalysis, 800);
        return function () { clearTimeout(t); };
      },
      [
        analysisOpened,
        postId,
        postTitle,
        postExcerpt,
        postContent,
        meta[META.title],
        meta[META.description],
        meta[META.robotsIndex],
        meta[META.robotsFollow],
        meta[META.ogImage],
        META.focusKeyword ? meta[META.focusKeyword] : '',
      ]
    );

    var robotsIndexVal = meta[META.robotsIndex] || 'default';
    var robotsFollowVal = meta[META.robotsFollow] || 'default';
    var schemaTypeVal = meta[META.schemaType] || 'default';

    var schemaPanel = null;
    if (MODULES && MODULES.schema && META.schemaType) {
      schemaPanel = el(
        PanelBody,
        { title: __('Schema', 'wp-seo-next'), initialOpen: false },
        el(SelectControl, {
          label: __('Schema type', 'wp-seo-next'),
          value: schemaTypeVal,
          options: [
            { label: __('Default (auto)', 'wp-seo-next'), value: 'default' },
            { label: __('Article (BlogPosting)', 'wp-seo-next'), value: 'article' },
            { label: __('WebPage', 'wp-seo-next'), value: 'webpage' },
            { label: __('Disable schema output for this page', 'wp-seo-next'), value: 'none' },
          ],
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.schemaType] = v; return p;
            })());
          },
          help: __('Controls JSON-LD structured data output.', 'wp-seo-next'),
        })
      );
    }


    function openOgMediaPicker() {
      if (!wp || !wp.media || typeof wp.media !== 'function') {
        setError(__('Media library is not available in this editor context.', 'wp-seo-next'));
        return;
      }

      var frame = wp.media({
        title: __('Select OpenGraph image', 'wp-seo-next'),
        button: { text: __('Use this image', 'wp-seo-next') },
        multiple: false,
        library: { type: 'image' },
      });

      frame.on('select', function () {
        var attachment = frame.state().get('selection').first();
        if (!attachment) {
          return;
        }
        var json = attachment.toJSON ? attachment.toJSON() : null;
        var url = json && json.url ? String(json.url) : '';
        if (url) {
          setMetaPatch((function () {
            var p = {}; p[META.ogImage] = url; return p;
          })());
        }
      });

      frame.open();
    }

    function openTwitterMediaPicker() {
      if (!wp || !wp.media || typeof wp.media !== 'function') {
        setError(__('Media library is not available in this editor context.', 'wp-seo-next'));
        return;
      }

      var frame = wp.media({
        title: __('Select Twitter image', 'wp-seo-next'),
        button: { text: __('Use this image', 'wp-seo-next') },
        multiple: false,
        library: { type: 'image' },
      });

      frame.on('select', function () {
        var attachment = frame.state().get('selection').first();
        if (!attachment) {
          return;
        }
        var json = attachment.toJSON ? attachment.toJSON() : null;
        var url = json && json.url ? String(json.url) : '';
        if (url) {
          setMetaPatch((function () {
            var p = {}; p[META.twitterImage] = url; return p;
          })());
        }
      });

      frame.open();
    }

    function openVideoMediaPicker() {
      if (!wp || !wp.media || typeof wp.media !== 'function') {
        setError(__('Media library is not available in this editor context.', 'wp-seo-next'));
        return;
      }

      var frame = wp.media({
        title: __('Select video', 'wp-seo-next'),
        button: { text: __('Use this video', 'wp-seo-next') },
        multiple: false,
        library: { type: 'video' },
      });

      frame.on('select', function () {
        var attachment = frame.state().get('selection').first();
        if (!attachment) {
          return;
        }
        var json = attachment.toJSON ? attachment.toJSON() : null;
        var url = json && json.url ? String(json.url) : '';
        if (url) {
          setMetaPatch((function () {
            var p = {}; p[META.videoUrl] = url; return p;
          })());
        }
      });

      frame.open();
    }

    function openVideoThumbnailPicker() {
      if (!wp || !wp.media || typeof wp.media !== 'function') {
        setError(__('Media library is not available in this editor context.', 'wp-seo-next'));
        return;
      }

      var frame = wp.media({
        title: __('Select video thumbnail', 'wp-seo-next'),
        button: { text: __('Use this image', 'wp-seo-next') },
        multiple: false,
        library: { type: 'image' },
      });

      frame.on('select', function () {
        var attachment = frame.state().get('selection').first();
        if (!attachment) {
          return;
        }
        var json = attachment.toJSON ? attachment.toJSON() : null;
        var url = json && json.url ? String(json.url) : '';
        if (url) {
          setMetaPatch((function () {
            var p = {}; p[META.videoThumbnail] = url; return p;
          })());
        }
      });

      frame.open();
    }


    var socialPanel = null;
    var twitterPanel = null;
    if (MODULES && MODULES.social && META.ogTitle && META.ogDescription && META.ogImage) {
      var ogTitleVal = meta[META.ogTitle] || '';
      var ogDescVal = meta[META.ogDescription] || '';
      var ogImgVal = meta[META.ogImage] || '';

      var prevOgTitle = preview && preview.og_title ? String(preview.og_title) : '';
      var prevOgDesc = preview && preview.og_description ? String(preview.og_description) : '';
      var prevOgImg = preview && preview.og_image ? String(preview.og_image) : '';

      // Resolve preview values with sensible fallbacks.
      var resolvedOgTitle = prevOgTitle || ogTitleVal || (preview && preview.title ? String(preview.title) : '');
      var resolvedOgDesc = prevOgDesc || ogDescVal || (preview && preview.description ? String(preview.description) : '');
      var resolvedOgImg = prevOgImg || ogImgVal;

      socialPanel = el(
        PanelBody,
        { title: __('Social (OpenGraph)', 'wp-seo-next'), initialOpen: false },

        el(TextControl, {
          label: __('OG Title', 'wp-seo-next'),
          value: ogTitleVal,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.ogTitle] = v; return p;
            })());
          },
          help: __('Leave empty to use SEO title.', 'wp-seo-next'),
        }),

        el(TextareaControl, {
          label: __('OG Description', 'wp-seo-next'),
          value: ogDescVal,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.ogDescription] = v; return p;
            })());
          },
          help: __('Leave empty to use meta description.', 'wp-seo-next'),
        }),

        el(TextControl, {
          label: __('OG Image URL', 'wp-seo-next'),
          type: 'url',
          value: ogImgVal,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.ogImage] = v; return p;
            })());
          },
          help: __('Leave empty to use Featured Image → Default OG image → Site Icon.', 'wp-seo-next'),
        }),

        el(
          'div',
          { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
          el(
            Button,
            { isSecondary: true, onClick: openOgMediaPicker, disabled: !wp || !wp.media || typeof wp.media !== 'function' },
            __('Select from media library', 'wp-seo-next')
          ),
          el(
            Button,
            {
              isSecondary: true,
              onClick: function () {
                setMetaPatch((function () {
                  var p = {}; p[META.ogImage] = ''; return p;
                })());
              },
              disabled: !ogImgVal,
            },
            __('Clear', 'wp-seo-next')
          )
        ),

        resolvedOgImg ? el(
          'div',
          { style: { marginTop: '10px' } },
          el('img', {
            src: resolvedOgImg,
            style: { width: '100%', height: 'auto', borderRadius: '6px', border: '1px solid rgba(0,0,0,.12)' },
            alt: '',
          })
        ) : SmallMuted(__('No image selected. A fallback may still be used if configured.', 'wp-seo-next')),

        el('hr', { style: { margin: '14px 0' } }),

        el('div', null,
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('Social preview', 'wp-seo-next')),
          resolvedOgTitle ? el('div', { style: { fontSize: '14px', fontWeight: 600, marginBottom: '4px' } }, resolvedOgTitle) : null,
          resolvedOgDesc ? el('div', { style: { fontSize: '13px', lineHeight: 1.4 } }, resolvedOgDesc) : null,
          SmallMuted(__('This is a simplified OpenGraph preview. Networks may render differently.', 'wp-seo-next'))
        )
      );

      // Twitter panel (separate overrides, fallback to OG)
      if (META.twitterTitle && META.twitterDescription && META.twitterImage) {
        var twTitleVal = meta[META.twitterTitle] || '';
        var twDescVal = meta[META.twitterDescription] || '';
        var twImgVal = meta[META.twitterImage] || '';

        var prevTwTitle = preview && preview.twitter_title ? String(preview.twitter_title) : '';
        var prevTwDesc = preview && preview.twitter_description ? String(preview.twitter_description) : '';
        var prevTwImg = preview && preview.twitter_image ? String(preview.twitter_image) : '';

        var resolvedTwTitle = prevTwTitle || twTitleVal || resolvedOgTitle;
        var resolvedTwDesc = prevTwDesc || twDescVal || resolvedOgDesc;
        var resolvedTwImg = prevTwImg || twImgVal || resolvedOgImg;

        twitterPanel = el(
          PanelBody,
          { title: __('Social (Twitter)', 'wp-seo-next'), initialOpen: false },

          el(TextControl, {
            label: __('Twitter Title', 'wp-seo-next'),
            value: twTitleVal,
            onChange: function (v) {
              setMetaPatch((function () {
                var p = {}; p[META.twitterTitle] = v; return p;
              })());
            },
            help: __('Leave empty to use OpenGraph title.', 'wp-seo-next'),
          }),

          el(TextareaControl, {
            label: __('Twitter Description', 'wp-seo-next'),
            value: twDescVal,
            onChange: function (v) {
              setMetaPatch((function () {
                var p = {}; p[META.twitterDescription] = v; return p;
              })());
            },
            help: __('Leave empty to use OpenGraph description.', 'wp-seo-next'),
          }),

          el(TextControl, {
            label: __('Twitter Image URL', 'wp-seo-next'),
            type: 'url',
            value: twImgVal,
            onChange: function (v) {
              setMetaPatch((function () {
                var p = {}; p[META.twitterImage] = v; return p;
              })());
            },
            help: __('Leave empty to use OpenGraph image.', 'wp-seo-next'),
          }),

          el(
            'div',
            { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
            el(
              Button,
              { isSecondary: true, onClick: openTwitterMediaPicker, disabled: !wp || !wp.media || typeof wp.media !== 'function' },
              __('Select from media library', 'wp-seo-next')
            ),
            el(
              Button,
              {
                isSecondary: true,
                onClick: function () {
                  setMetaPatch((function () {
                    var p = {}; p[META.twitterImage] = ''; return p;
                  })());
                },
                disabled: !twImgVal,
              },
              __('Clear', 'wp-seo-next')
            )
          ),

          resolvedTwImg ? el(
            'div',
            { style: { marginTop: '10px' } },
            el('img', {
              src: resolvedTwImg,
              style: { width: '100%', height: 'auto', borderRadius: '6px', border: '1px solid rgba(0,0,0,.12)' },
              alt: '',
            })
          ) : SmallMuted(__('No image selected. A fallback may still be used if configured.', 'wp-seo-next')),

          el('hr', { style: { margin: '14px 0' } }),

          el('div', null,
            el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('Twitter preview', 'wp-seo-next')),
            resolvedTwTitle ? el('div', { style: { fontSize: '14px', fontWeight: 600, marginBottom: '4px' } }, resolvedTwTitle) : null,
            resolvedTwDesc ? el('div', { style: { fontSize: '13px', lineHeight: 1.4 } }, resolvedTwDesc) : null,
            SmallMuted(__('This is a simplified Twitter Cards preview. Twitter may render differently.', 'wp-seo-next'))
          )
        );
      }
    }



    var videoPanel = null;
    if (META && META.videoUrl && meta && Object.prototype.hasOwnProperty.call(meta, META.videoUrl)) {
      var vUrl = meta[META.videoUrl] || '';
      var vThumb = META.videoThumbnail ? (meta[META.videoThumbnail] || '') : '';
      var vTitle = META.videoTitle ? (meta[META.videoTitle] || '') : '';
      var vDesc = META.videoDescription ? (meta[META.videoDescription] || '') : '';
      var vDur = META.videoDuration ? (meta[META.videoDuration] || '') : '';

      var videoEnabled = !!(SITEMAP && SITEMAP.videoEnabled);
      var videoMode = (SITEMAP && SITEMAP.videoMode) ? String(SITEMAP.videoMode) : 'meta';
      var videoIncludeFeatured = !!(SITEMAP && SITEMAP.videoIncludeFeaturedThumbnail);

      var thumbHelp = __('Required for Video sitemap entries.', 'wp-seo-next');
      if (videoMode === 'youtube_auto') {
        thumbHelp = __('Required. In YouTube Auto mode, a thumbnail can be detected from the YouTube URL if left empty.', 'wp-seo-next');
      } else if (videoIncludeFeatured) {
        thumbHelp = __('Required. If empty, Featured Image can be used as a fallback (if enabled in Video sitemap settings).', 'wp-seo-next');
      }

      videoPanel = el(
        PanelBody,
        { title: __('Video (sitemap)', 'wp-seo-next'), initialOpen: false },

        !videoEnabled ? el(
          Notice,
          { status: 'warning', isDismissible: false },
          __('Video sitemap is disabled. These fields will be ignored until you enable it in Settings → Sitemap.', 'wp-seo-next')
        ) : null,

        el(TextControl, {
          label: __('Video URL', 'wp-seo-next'),
          type: 'url',
          value: vUrl,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.videoUrl] = v; return p;
            })());
          },
          help: __('Set this to include the page in the Video sitemap.', 'wp-seo-next'),
        }),

        el(
          'div',
          { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
          el(
            Button,
            { isSecondary: true, onClick: openVideoMediaPicker, disabled: !wp || !wp.media || typeof wp.media !== 'function' },
            __('Select video from media library', 'wp-seo-next')
          ),
          el(
            Button,
            {
              isSecondary: true,
              onClick: function () {
                setMetaPatch((function () {
                  var p = {}; p[META.videoUrl] = ''; return p;
                })());
              },
              disabled: !vUrl,
            },
            __('Clear', 'wp-seo-next')
          )
        ),

        el('hr', { style: { margin: '14px 0' } }),

        META.videoThumbnail ? el(TextControl, {
          label: __('Thumbnail URL', 'wp-seo-next'),
          type: 'url',
          value: vThumb,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.videoThumbnail] = v; return p;
            })());
          },
          help: thumbHelp,
        }) : null,

        META.videoThumbnail ? el(
          'div',
          { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
          el(
            Button,
            { isSecondary: true, onClick: openVideoThumbnailPicker, disabled: !wp || !wp.media || typeof wp.media !== 'function' },
            __('Select image', 'wp-seo-next')
          ),
          el(
            Button,
            {
              isSecondary: true,
              onClick: function () {
                setMetaPatch((function () {
                  var p = {}; p[META.videoThumbnail] = ''; return p;
                })());
              },
              disabled: !vThumb,
            },
            __('Clear', 'wp-seo-next')
          )
        ) : null,

        (META.videoThumbnail && vThumb) ? el(
          'div',
          { style: { marginTop: '10px' } },
          el('img', {
            src: String(vThumb),
            style: { width: '100%', height: 'auto', borderRadius: '6px', border: '1px solid rgba(0,0,0,.12)' },
            alt: '',
          })
        ) : null,

        META.videoTitle ? el(TextControl, {
          label: __('Video Title', 'wp-seo-next'),
          value: vTitle,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.videoTitle] = v; return p;
            })());
          },
          help: __('Optional. If empty, post title may be used.', 'wp-seo-next'),
        }) : null,

        META.videoDescription ? el(TextareaControl, {
          label: __('Video Description', 'wp-seo-next'),
          value: vDesc,
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.videoDescription] = v; return p;
            })());
          },
          help: __('Optional. If empty, excerpt/content may be used.', 'wp-seo-next'),
        }) : null,

        META.videoDuration ? el(TextControl, {
          label: __('Duration (seconds)', 'wp-seo-next'),
          type: 'number',
          value: String(vDur || ''),
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.videoDuration] = v; return p;
            })());
          },
          help: __('Optional. Google supports 0–28800 seconds.', 'wp-seo-next'),
        }) : null,

        SmallMuted(__('These fields are used by the Video sitemap module and can also help with rich previews.', 'wp-seo-next'))
      );
    }

    var suggestionsPanel = null;
    if (AI && (AI.localEnabled || MODULES.ai)) {
      var titleS = suggestions && suggestions.title ? String(suggestions.title) : '';
      var descS = suggestions && suggestions.description ? String(suggestions.description) : '';
      var kw = suggestions && suggestions.keywords ? suggestions.keywords : [];

      suggestionsPanel = el(
        PanelBody,
        { title: __('Suggestions', 'wp-seo-next'), initialOpen: false },

        !MODULES.ai ? el(Notice, { status: 'warning', isDismissible: false },
          __('AI module is disabled. Enable it in Settings → Modules to use suggestions.', 'wp-seo-next')
        ) : null,

        MODULES.ai && !AI.localEnabled ? el(Notice, { status: 'warning', isDismissible: false },
          __('Local suggestions are disabled in Settings.', 'wp-seo-next')
        ) : null,

        suggestError ? el(Notice, { status: 'error', isDismissible: false }, suggestError) : null,
        suggestNotice ? el(Notice, { status: 'success', isDismissible: true, onRemove: function () { setSuggestNotice(''); } }, suggestNotice) : null,

        el(
          'div',
          { style: { marginTop: '8px' } },
          el(
            Button,
            { isSecondary: true, onClick: fetchSuggestions, disabled: suggestLoading || !MODULES.ai || !AI.localEnabled },
            suggestLoading ? el(Spinner, null) : null,
            suggestLoading ? ' ' : null,
            __('Refresh suggestions', 'wp-seo-next')
          ),
          CONFIG.settingsUrl ? el('span', { style: { marginLeft: '10px' } }, el('a', { href: CONFIG.settingsUrl, target: '_blank', rel: 'noopener' }, __('Settings', 'wp-seo-next'))) : null
        ),

        el('hr', { style: { margin: '14px 0' } }),

        el('div', null,
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('Suggested title', 'wp-seo-next'), lengthBadge(charCount(titleS), AI.titleMaxLength || 60, 20)),
          titleS ? el('div', { style: { padding: '8px 10px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: 'rgba(0,0,0,.02)' } }, titleS) : SmallMuted(__('No suggestion yet.', 'wp-seo-next')),
          el('div', { style: { marginTop: '8px' } },
            el(Button, { isPrimary: true, isSmall: true, disabled: !titleS, onClick: function () { applySuggestion('title'); } }, __('Apply title', 'wp-seo-next'))
          )
        ),

        el('div', { style: { marginTop: '14px' } },
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('Suggested description', 'wp-seo-next'), lengthBadge(charCount(descS), AI.descriptionMaxLength || 155, 50)),
          descS ? el('div', { style: { padding: '8px 10px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: 'rgba(0,0,0,.02)' } }, descS) : SmallMuted(__('No suggestion yet.', 'wp-seo-next')),
          el('div', { style: { marginTop: '8px' } },
            el(Button, { isPrimary: true, isSmall: true, disabled: !descS, onClick: function () { applySuggestion('description'); } }, __('Apply description', 'wp-seo-next'))
          )
        ),

                el('div', { style: { marginTop: '10px' } },
          el(Button, { isSecondary: true, isSmall: true, disabled: (!titleS && !descS), onClick: function () { applySuggestionAll(); } }, __('Apply all suggestions', 'wp-seo-next'))
        ),

el('div', { style: { marginTop: '14px' } },
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('Keywords', 'wp-seo-next')),
          kw && kw.length ? el('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '6px' } },
            kw.slice(0, AI.keywordsLimit || 12).map(function (k, idx) {
              return el('span', {
                key: idx,
                style: {
                  fontSize: '12px',
                  padding: '3px 8px',
                  borderRadius: '999px',
                  border: '1px solid rgba(0,0,0,.12)',
                  background: 'white'
                }
              }, String(k));
            })
          ) : SmallMuted(__('No keywords yet.', 'wp-seo-next')),
          SmallMuted(__('Tip: use these for internal linking / headings, not as a "meta keywords" tag.', 'wp-seo-next'))
        )
      );
    }

    var aiPanel = null;
    if (AI && (AI.externalEnabled || MODULES.ai)) {
      var titleR = aiResult && aiResult.title ? String(aiResult.title) : '';
      var descR = aiResult && aiResult.description ? String(aiResult.description) : '';

      var draftBlocked = AI.noDrafts && postStatus && postStatus !== 'publish' && postStatus !== 'private';

      aiPanel = el(
        PanelBody,
        { title: __('AI Generate (OpenAI)', 'wp-seo-next'), initialOpen: false },

        !MODULES.ai ? el(Notice, { status: 'warning', isDismissible: false },
          __('AI module is disabled. Enable it in Settings → Modules.', 'wp-seo-next')
        ) : null,

        MODULES.ai && !AI.externalEnabled ? el(Notice, { status: 'warning', isDismissible: false },
          __('External AI is disabled in Settings.', 'wp-seo-next')
        ) : null,

        MODULES.ai && AI.externalEnabled && !AI.providerConfigured ? el(Notice, { status: 'warning', isDismissible: false },
          __('OpenAI API key is not configured in Settings.', 'wp-seo-next')
        ) : null,

        MODULES.ai && AI.externalEnabled && AI.providerConfigured && AI.canUseExternal === false ? el(Notice, { status: 'warning', isDismissible: false },
          __('You do not have permission to use external AI. Ask an admin to grant access.', 'wp-seo-next')
        ) : null,

        draftBlocked ? el(Notice, { status: 'warning', isDismissible: false },
          __('Draft protection is ON: generation is blocked until the post is published (or disable the restriction in Settings).', 'wp-seo-next')
        ) : null,

        aiError ? el(Notice, { status: 'error', isDismissible: false }, aiError) : null,
        aiNotice ? el(Notice, { status: 'success', isDismissible: true, onRemove: function () { setAiNotice(''); } }, aiNotice) : null,

        el(
          'div',
          { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
          el(
            Button,
            {
              isPrimary: true,
              onClick: function () { generateAI('title'); },
              disabled: aiLoadingField !== '' || !MODULES.ai || !AI.externalEnabled || !AI.providerConfigured || !AI.canUseExternal || draftBlocked,
            },
            aiLoadingField === 'title' ? el(Spinner, null) : null,
            aiLoadingField === 'title' ? ' ' : null,
            __('Generate title', 'wp-seo-next')
          ),
          el(
            Button,
            {
              isPrimary: true,
              onClick: function () { generateAI('description'); },
              disabled: aiLoadingField !== '' || !MODULES.ai || !AI.externalEnabled || !AI.providerConfigured || !AI.canUseExternal || draftBlocked,
            },
            aiLoadingField === 'description' ? el(Spinner, null) : null,
            aiLoadingField === 'description' ? ' ' : null,
            __('Generate description', 'wp-seo-next')
          )
        ),

        CONFIG.settingsUrl ? LinkRow(__('Open AI settings', 'wp-seo-next'), CONFIG.settingsUrl) : null,
        CONFIG.toolsAiUrl ? LinkRow(__('View usage & logs', 'wp-seo-next'), CONFIG.toolsAiUrl) : null,

        el('hr', { style: { margin: '14px 0' } }),

        el('div', null,
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('AI title result', 'wp-seo-next'), lengthBadge(charCount(titleR), AI.titleMaxLength || 60, 20)),
          titleR ? el('div', { style: { padding: '8px 10px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: 'rgba(0,0,0,.02)' } }, titleR) : SmallMuted(__('No result yet.', 'wp-seo-next')),
          el('div', { style: { marginTop: '8px' } },
            el(Button, { isSecondary: true, isSmall: true, disabled: !titleR, onClick: function () { applyAI('title'); } }, __('Apply to SEO title', 'wp-seo-next'))
          )
        ),

        el('div', { style: { marginTop: '14px' } },
          el('div', { style: { fontWeight: 600, marginBottom: '6px' } }, __('AI description result', 'wp-seo-next'), lengthBadge(charCount(descR), AI.descriptionMaxLength || 155, 50)),
          descR ? el('div', { style: { padding: '8px 10px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: 'rgba(0,0,0,.02)' } }, descR) : SmallMuted(__('No result yet.', 'wp-seo-next')),
          el('div', { style: { marginTop: '8px' } },
            el(Button, { isSecondary: true, isSmall: true, disabled: !descR, onClick: function () { applyAI('description'); } }, __('Apply to meta description', 'wp-seo-next'))
          )
        ),

        el('div', { style: { marginTop: '10px' } },
          el(Button, { isSecondary: true, isSmall: true, disabled: (!titleR && !descR), onClick: function () { applyAIAll(); } }, __('Apply all results', 'wp-seo-next'))
        )
      );
    }

    var ilBulkPercent = (ilBulkTotal && ilBulkTotal > 0) ? Math.min(100, Math.round((ilBulkDone || 0) * 100 / ilBulkTotal)) : 0;

    var internalLinksPanel = null;
    if (MODULES && MODULES.searchConsole) {
      internalLinksPanel = el(
        PanelBody,
        { title: __('Internal links', 'wp-seo-next'), initialOpen: false },
        ilError ? el(Notice, { status: 'error', isDismissible: false }, ilError) : null,
        ilNotice ? el(Notice, { status: 'success', isDismissible: true, onRemove: function () { setIlNotice(''); } }, ilNotice) : null,

        el(SelectControl, {
          label: __('Direction', 'wp-seo-next'),
          value: ilDirection,
          options: [
            { label: __('Incoming (to this page)', 'wp-seo-next'), value: 'incoming' },
            { label: __('Outgoing (from this page)', 'wp-seo-next'), value: 'outgoing' },
          ],
          onChange: function (v) {
            setIlDirection(v);
          },
        }),

        el('div', { style: { marginTop: '8px' } },
          el(Button, { isSecondary: true, isSmall: true, onClick: fetchInternalLinks, disabled: ilLoading || ilBulkRunning },
            ilLoading ? el(Spinner, null) : null,
            ilLoading ? ' ' : null,
            __('Refresh', 'wp-seo-next')
          ),
          CONFIG.toolsLinksHubUrl ? el('span', { style: { marginLeft: '10px' } }, el('a', { href: CONFIG.toolsLinksHubUrl, target: '_blank', rel: 'noopener' }, __('Open links hub', 'wp-seo-next'))) : null
        ),

        ilDirection === 'outgoing'
          ? SmallMuted(__('Suggestions below add links from this page to other pages. Applying will update this post content (save first) and the editor will sync automatically.', 'wp-seo-next'))
          : SmallMuted(__('Suggestions below add links from other posts to this page. Applying will update the source post content.', 'wp-seo-next')),

        ilDirection === 'outgoing' ? el('div', { style: { marginTop: '10px' } },
          el('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' } },
            el(Button, {
              isPrimary: true,
              isSmall: true,
              onClick: applyAllOutgoing,
              disabled: ilBulkRunning || ilLoading || ilWorkingId > 0,
            },
              ilBulkRunning ? el(Spinner, null) : null,
              ilBulkRunning ? ' ' : null,
              __('Apply all outgoing for this post', 'wp-seo-next')
            ),
            ilBulkRunning ? el(Button, { isSecondary: true, isSmall: true, onClick: cancelApplyAllOutgoing }, __('Stop', 'wp-seo-next')) : null,
            (ilTotal ? el('span', { style: { fontSize: '12px', opacity: 0.75 } }, __('Drafts:', 'wp-seo-next') + ' ' + ilTotal) : null)
          ),

          ilBulkRunning ? el('div', { style: { marginTop: '8px' } },
            el('div', { style: { fontSize: '12px', opacity: 0.85 } },
              __('Progress', 'wp-seo-next') + ': ' + ilBulkDone + '/' + (ilBulkTotal || ilTotal || 0)
            ),
            el('div', { style: { height: '8px', background: 'rgba(0,0,0,.08)', borderRadius: '999px', overflow: 'hidden', marginTop: '6px' } },
              el('div', { style: { width: ilBulkPercent + '%', height: '100%', background: 'rgba(0,0,0,.35)' } })
            ),
            el('div', { style: { fontSize: '12px', opacity: 0.85, marginTop: '4px' } },
              __('Applied', 'wp-seo-next') + ': ' + ilBulkApplied +
              ' · ' + __('Skipped', 'wp-seo-next') + ': ' + ilBulkSkipped +
              ' · ' + __('Failed', 'wp-seo-next') + ': ' + ilBulkFailed
            )
          ) : null,

          (ilLastDiff && (ilLastDiff.after || ilLastDiff.before || ilLastDiff.inserted)) ? el('div', { style: { marginTop: '10px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '8px', padding: '10px', background: 'rgba(0,0,0,.01)' } },
            el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
              el('div', { style: { fontWeight: 600 } }, __('Last insertion', 'wp-seo-next')),
              el(Button, { isSecondary: true, isSmall: true, onClick: function () { setIlLastDiff(null); } }, __('Clear', 'wp-seo-next'))
            ),
            ilLastDiff.inserted ? el('div', { style: { marginTop: '6px', fontSize: '12px' } },
              __('Inserted', 'wp-seo-next') + ': ',
              el('code', null, ilLastDiff.inserted)
            ) : null,
            ilLastDiff.before ? el('div', { style: { marginTop: '6px' } },
              el('div', { style: { fontSize: '11px', opacity: 0.7, marginBottom: '2px' } }, __('Before', 'wp-seo-next')),
              el('pre', { style: { margin: 0, whiteSpace: 'pre-wrap', fontSize: '11px', padding: '6px 8px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: '#fff' } }, ilLastDiff.before)
            ) : null,
            ilLastDiff.after ? el('div', { style: { marginTop: '6px' } },
              el('div', { style: { fontSize: '11px', opacity: 0.7, marginBottom: '2px' } }, __('After', 'wp-seo-next')),
              el('pre', { style: { margin: 0, whiteSpace: 'pre-wrap', fontSize: '11px', padding: '6px 8px', border: '1px solid rgba(0,0,0,.08)', borderRadius: '6px', background: '#fff' } }, ilLastDiff.after)
            ) : null
          ) : null

        ) : null,

        el('hr', { style: { margin: '14px 0' } }),
        ilItems && ilItems.length ? el(
          'div',
          null,
          ilItems.map(function (item) {
            var otherTitle = ilDirection === 'outgoing'
              ? (item.target_title || ('#' + item.target_post_id))
              : (item.source_title || ('#' + item.source_post_id));
            var otherEdit = ilDirection === 'outgoing' ? item.target_edit_url : item.source_edit_url;
            var otherView = ilDirection === 'outgoing' ? (item.target_view_url || item.target_url) : item.source_view_url;
            return el(
              'div',
              {
                key: item.id,
                style: { border: '1px solid rgba(0,0,0,.08)', borderRadius: '8px', padding: '10px', marginBottom: '10px', background: 'rgba(0,0,0,.01)' }
              },
              el('div', { style: { fontWeight: 600, marginBottom: '6px' } },
                otherEdit ? el('a', { href: otherEdit, target: '_blank', rel: 'noopener' }, otherTitle) : otherTitle
              ),
              otherView ? el('div', { style: { fontSize: '12px', opacity: 0.8, marginBottom: '6px' } }, el('a', { href: otherView, target: '_blank', rel: 'noopener' }, el('code', null, otherView))) : null,
              item.anchor_text ? el('div', null, el('span', { style: { fontSize: '12px', opacity: 0.75 } }, __('Anchor', 'wp-seo-next') + ': '), el('code', null, item.anchor_text)) : null,
              item.query_text ? el('div', { style: { marginTop: '4px' } }, el('span', { style: { fontSize: '12px', opacity: 0.75 } }, __('Phrase', 'wp-seo-next') + ': '), el('code', null, item.query_text)) : null,
              el('div', { style: { marginTop: '6px', fontSize: '12px', opacity: 0.85 } },
                __('Impressions', 'wp-seo-next') + ': ' + (item.gsc_impressions || 0) +
                ' · ' + __('Pos', 'wp-seo-next') + ': ' + (item.gsc_position ? Number(item.gsc_position).toFixed(1) : '–') +
                ' · ' + __('Conf', 'wp-seo-next') + ': ' + (item.confidence ? Math.round(item.confidence * 100) + '%' : '–')
              ),
              el('div', { style: { display: 'flex', gap: '8px', marginTop: '8px' } },
                el(Button, {
                  isPrimary: true,
                  isSmall: true,
                  onClick: function () { applyInternalLink(item.id); },
                  disabled: ilBulkRunning || ilWorkingId === item.id,
                },
                  ilWorkingId === item.id ? el(Spinner, null) : null,
                  ilWorkingId === item.id ? ' ' : null,
                  __('Apply', 'wp-seo-next')
                ),
                el(Button, {
                  isSecondary: true,
                  isSmall: true,
                  onClick: function () { dismissInternalLink(item.id); },
                  disabled: ilBulkRunning || ilWorkingId === item.id,
                }, __('Dismiss', 'wp-seo-next'))
              )
            );
          })
        ) : SmallMuted(__('No internal link suggestions for this page (yet).', 'wp-seo-next'))
      );
    } else {
      internalLinksPanel = el(
        PanelBody,
        { title: __('Internal links', 'wp-seo-next'), initialOpen: false },
        el(Notice, { status: 'warning', isDismissible: false }, __('Search Console module is disabled. Enable it in Settings → Modules to use internal linking suggestions.', 'wp-seo-next')),
        CONFIG.settingsUrl ? LinkRow(__('Open settings', 'wp-seo-next'), CONFIG.settingsUrl) : null
      );
    }

    var analysisPanel = null;
    if (MODULES && MODULES.analysis) {
      analysisPanel = el(
        PanelBody,
        {
          title: __('Analysis', 'wp-seo-next'),
          initialOpen: false,
          onToggle: function () {
            if (!analysisOpened) {
              setAnalysisOpened(true);
              // Initial fetch.
              setTimeout(fetchAnalysis, 50);
            }
          }
        },

        analysisLoading ? el(Spinner, null) : null,
        analysisError ? el(Notice, { status: 'error', isDismissible: false }, analysisError) : null,

        analysis ? el(
          'div',
          { style: { marginTop: '8px' } },
          el('div', { style: { display: 'flex', alignItems: 'baseline', gap: '10px' } },
            el('div', { style: { fontSize: '28px', fontWeight: 700, lineHeight: 1 } }, String(analysis.score || 0)),
            el('div', { style: { fontSize: '12px', opacity: 0.8 } }, __('SEO score', 'wp-seo-next'))
          ),
          analysis.stats ? el(
            'div',
            { style: { marginTop: '8px', fontSize: '12px', opacity: 0.8 } },
            __('Words', 'wp-seo-next') + ': ' + (analysis.stats.word_count || 0) +
              ' · ' + __('H2/H3', 'wp-seo-next') + ': ' + ((analysis.stats.h2 || 0) + (analysis.stats.h3 || 0)) +
              ' · ' + __('Out links', 'wp-seo-next') + ': ' + (analysis.stats.internal_links || 0) +
              (analysis.stats.inbound_internal_links !== undefined ? (' · ' + __('In links', 'wp-seo-next') + ': ' + (analysis.stats.inbound_internal_links || 0)) : '')
          ) : null,
          analysis.checks && analysis.checks.length ? el(
            'div',
            { style: { marginTop: '12px' } },
            analysis.checks.map(function (c) {
              var st = c.status || 'info';
              var msg = c.message ? String(c.message) : '';
              return el(
                'div',
                { key: c.id || Math.random(), style: { display: 'flex', alignItems: 'flex-start', gap: '10px', marginBottom: '10px' } },
                statusDot(st),
                el('div', null,
                  el('div', { style: { fontWeight: 600 } }, c.label || ''),
                  msg ? el('div', { style: { fontSize: '12px', opacity: 0.8, marginTop: '2px' } }, msg) : null
                )
              );
            })
          ) : SmallMuted(__('No checks yet.', 'wp-seo-next'))
        ) : SmallMuted(__('Open this panel to run analysis.', 'wp-seo-next')),

        el(
          'div',
          { style: { marginTop: '10px' } },
          el(Button, { isSecondary: true, onClick: fetchAnalysis, disabled: analysisLoading }, __('Refresh analysis', 'wp-seo-next'))
        )
      );
    } else {
      analysisPanel = el(
        PanelBody,
        { title: __('Analysis', 'wp-seo-next'), initialOpen: false },
        el(Notice, { status: 'warning', isDismissible: false }, __('Analysis module is disabled. Enable it in Settings → Modules.', 'wp-seo-next')),
        CONFIG.settingsUrl ? LinkRow(__('Open settings', 'wp-seo-next'), CONFIG.settingsUrl) : null
      );
    }

    var gscPanel = null;
    if (MODULES && MODULES.searchConsole) {
      gscPanel = el(
        PanelBody,
        {
          title: __('Search Console', 'wp-seo-next'),
          initialOpen: false,
          onToggle: function () {
            if (!gscOpened) {
              setGscOpened(true);
              setTimeout(fetchGscData, 50);
            }
          }
        },

        gscLoading ? el(Spinner, null) : null,
        gscError ? el(Notice, { status: 'warning', isDismissible: false }, gscError) : null,
        gscNotice ? el(Notice, { status: 'success', isDismissible: false }, gscNotice) : null,

        (!gscLoading && gscData && gscData.ok) ? (function () {
          var m = gscData.metrics || {};
          var clicks = (typeof m.clicks === 'number') ? m.clicks : 0;
          var impr = (typeof m.impressions === 'number') ? m.impressions : 0;
          var ctrPct = (typeof m.ctr === 'number') ? (m.ctr * 100) : 0;
          var pos = (typeof m.position === 'number') ? m.position : 0;

          var canInspect = CONFIG.gsc && CONFIG.gsc.canManage && gscData.external && gscData.external.inspect_available;
          var inspectDisabled = !canInspect;
          var inspectTitle = '';
          if (CONFIG.gsc && CONFIG.gsc.canManage) {
            if (gscData.external && gscData.external.local_only) {
              inspectTitle = __('External services are disabled by privacy setting (Local-only).', 'wp-seo-next');
            } else if (gscData.external && !gscData.external.connected) {
              inspectTitle = __('Search Console is not connected. Connect it in Tools → Search Console.', 'wp-seo-next');
            }
          }

          return el(
            'div',
            { style: { marginTop: '8px' } },
            el('div', { style: { fontSize: '12px', opacity: 0.75, marginBottom: '6px' } },
              __('Range', 'wp-seo-next') + ': ' + (gscData.start_date || '') + ' → ' + (gscData.end_date || '') +
              (gscData.last_fetched_at ? (' · ' + __('Fetched', 'wp-seo-next') + ': ' + gscData.last_fetched_at) : '')
            ),
            el('div', { style: { display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'baseline' } },
              el('div', null, el('div', { style: { fontSize: '22px', fontWeight: 700, lineHeight: 1 } }, String(Math.round(clicks))), el('div', { style: { fontSize: '12px', opacity: 0.8 } }, __('Clicks', 'wp-seo-next'))),
              el('div', null, el('div', { style: { fontSize: '22px', fontWeight: 700, lineHeight: 1 } }, String(Math.round(impr))), el('div', { style: { fontSize: '12px', opacity: 0.8 } }, __('Impressions', 'wp-seo-next'))),
              el('div', null, el('div', { style: { fontSize: '22px', fontWeight: 700, lineHeight: 1 } }, String(Number(ctrPct).toFixed(2)) + '%'), el('div', { style: { fontSize: '12px', opacity: 0.8 } }, __('CTR', 'wp-seo-next'))),
              el('div', null, el('div', { style: { fontSize: '22px', fontWeight: 700, lineHeight: 1 } }, pos ? String(Number(pos).toFixed(1)) : '–'), el('div', { style: { fontSize: '12px', opacity: 0.8 } }, __('Position', 'wp-seo-next')))
            ),
            el('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', marginTop: '10px' } },
              el(Button, { isSecondary: true, isSmall: true, onClick: fetchGscData, disabled: gscLoading }, __('Refresh', 'wp-seo-next')),
              (gscData.links && gscData.links.query_report) ? el(Button, { isSecondary: true, isSmall: true, href: String(gscData.links.query_report), target: '_blank', rel: 'noopener' }, __('Query report', 'wp-seo-next')) : null,
              (CONFIG.toolsGscUrl) ? el(Button, { isSecondary: true, isSmall: true, href: String(CONFIG.toolsGscUrl) }, __('Open hub', 'wp-seo-next')) : null,
              (CONFIG.gsc && CONFIG.gsc.canManage) ? el(Button, { isSecondary: true, isSmall: true, onClick: openGscInspect, disabled: inspectDisabled || gscInspectLoading, title: inspectTitle },
                gscInspectLoading ? el(Spinner, null) : null,
                gscInspectLoading ? ' ' : null,
                __('Inspect URL', 'wp-seo-next')
              ) : null
            )
          );
        })() : SmallMuted(__('Open this panel to load Search Console metrics for this page.', 'wp-seo-next')),

        (!gscLoading && gscData && gscData.ok) ? (function () {
          var qs = gscData.queries || [];
          if (!qs.length) {
            return SmallMuted(__('No queries found for this page (yet).', 'wp-seo-next'));
          }

          return el(
            'div',
            { style: { marginTop: '12px' } },
            el('div', { style: { fontWeight: 600, marginBottom: '8px' } }, __('Top queries', 'wp-seo-next')),
            qs.map(function (q) {
              var qt = q.query_text ? String(q.query_text) : '';
              if (!qt) return null;

              var badge = null;
              if (q.is_opportunity) {
                badge = el('span', { style: { marginLeft: '8px', fontSize: '11px', padding: '2px 6px', borderRadius: '999px', background: 'rgba(230,150,0,.18)', border: '1px solid rgba(230,150,0,.30)' } }, __('Opportunity', 'wp-seo-next'));
              }

              return el(
                'div',
                { key: qt, style: { border: '1px solid rgba(0,0,0,.08)', borderRadius: '8px', padding: '10px', marginBottom: '10px', background: 'rgba(0,0,0,.01)' } },
                el('div', { style: { fontWeight: 600, marginBottom: '6px', display: 'flex', alignItems: 'center', flexWrap: 'wrap' } },
                  el('code', null, qt),
                  badge
                ),
                el('div', { style: { fontSize: '12px', opacity: 0.85 } },
                  __('Impressions', 'wp-seo-next') + ': ' + Math.round(q.impressions || 0) +
                  ' · ' + __('Clicks', 'wp-seo-next') + ': ' + Math.round(q.clicks || 0) +
                  ' · ' + __('CTR', 'wp-seo-next') + ': ' + (q.ctr ? (Number(q.ctr) * 100).toFixed(2) : '0.00') + '%' +
                  ' · ' + __('Pos', 'wp-seo-next') + ': ' + (q.position ? Number(q.position).toFixed(1) : '–')
                ),
                el('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', marginTop: '8px' } },
                  q.query_url ? el(Button, { isSecondary: true, isSmall: true, href: String(q.query_url), target: '_blank', rel: 'noopener' }, __('Open query', 'wp-seo-next')) : null,
                  META.focusKeyword ? el(Button, {
                    isSecondary: true,
                    isSmall: true,
                    onClick: function () {
                      setMetaPatch((function () { var p = {}; p[META.focusKeyword] = qt; return p; })());
                      setGscNotice(__('Focus keyword set from query.', 'wp-seo-next'));
                    }
                  }, __('Use as focus keyword', 'wp-seo-next')) : null
                )
              );
            })
          );
        })() : null
      );
    } else {
      gscPanel = el(
        PanelBody,
        { title: __('Search Console', 'wp-seo-next'), initialOpen: false },
        el(Notice, { status: 'warning', isDismissible: false }, __('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next')),
        CONFIG.settingsUrl ? LinkRow(__('Open settings', 'wp-seo-next'), CONFIG.settingsUrl) : null
      );
    }

    var briefPanel = null;
    if (MODULES && MODULES.searchConsole) {
      var aiAvailableForBrief = !!(
        AI &&
        AI.externalEnabled &&
        AI.providerConfigured &&
        AI.canUseExternal &&
        !(AI.noDrafts && postStatus && postStatus !== 'publish' && postStatus !== 'private')
      );

      briefPanel = el(
        PanelBody,
        {
          title: __('Content brief', 'wp-seo-next'),
          initialOpen: false,
          onToggle: function () {
            setBriefOpened(!briefOpened);
            if (!briefOpened) {
              // Default query from focus keyword (if empty).
              if (!String(briefQuery || '').trim() && meta && META && META.focusKeyword && meta[META.focusKeyword]) {
                setBriefQuery(String(meta[META.focusKeyword] || ''));
              }
              // Force local if OpenAI unavailable.
              if (!aiAvailableForBrief && String(briefMode) === 'openai') {
                setBriefMode('local');
              }
              fetchGscData();
              fetchBriefStored();
            }
          },
        },

        CONFIG.toolsBriefUrl ? LinkRow(__('Open in hub', 'wp-seo-next'), CONFIG.toolsBriefUrl) : null,

        el(TextControl, {
          label: __('Target query', 'wp-seo-next'),
          value: briefQuery,
          onChange: function (v) {
            setBriefQuery(v);
          },
          help: __('Generate an H2/H3 plan + FAQ + recommendations based on your GSC queries.', 'wp-seo-next'),
        }),

        gscData && gscData.top_queries && gscData.top_queries.length
          ? el(
              'div',
              { style: { marginTop: '6px' } },
              el('div', { style: { fontSize: '12px', opacity: 0.8, marginBottom: '6px' } }, __('Suggestions from GSC:', 'wp-seo-next')),
              gscData.top_queries.slice(0, 8).map(function (row) {
                return el(
                  Button,
                  {
                    key: row.query,
                    isSecondary: true,
                    isSmall: true,
                    style: { marginRight: '6px', marginBottom: '6px' },
                    onClick: function () {
                      setBriefQuery(row.query);
                    },
                  },
                  row.query
                );
              })
            )
          : null,

        el(SelectControl, {
          label: __('Preset', 'wp-seo-next'),
          value: briefPreset,
          options: [
            { label: __('Auto (recommended)', 'wp-seo-next'), value: 'auto' },
            { label: __('Blog post', 'wp-seo-next'), value: 'blog_post' },
            { label: __('Service page', 'wp-seo-next'), value: 'service_page' },
            { label: __('Local landing', 'wp-seo-next'), value: 'local_landing' },
          ],
          onChange: function (v) {
            setBriefPreset(v);
          },
          help: __('Presets change the suggested H2/H3/FAQ structure for different page types.', 'wp-seo-next'),
        }),

        el(SelectControl, {
          label: __('Mode', 'wp-seo-next'),
          value: briefMode,
          options: aiAvailableForBrief
            ? [
                { label: __('Local (free)', 'wp-seo-next'), value: 'local' },
                { label: __('OpenAI', 'wp-seo-next'), value: 'openai' },
              ]
            : [{ label: __('Local (free)', 'wp-seo-next'), value: 'local' }],
          onChange: function (v) {
            setBriefMode(v);
          },
          help: !aiAvailableForBrief ? __('OpenAI mode is unavailable (check AI settings, permissions, and Local-only mode).', 'wp-seo-next') : '',
        }),

        el(
          'label',
          { style: { display: 'block', marginTop: '6px' } },
          el('input', {
            type: 'checkbox',
            checked: !!briefPersist,
            onChange: function (ev) {
              setBriefPersist(!!(ev && ev.target && ev.target.checked));
            },
          }),
          ' ',
          __('Save to post meta', 'wp-seo-next')
        ),

        el(
          'div',
          { style: { marginTop: '10px', display: 'flex', alignItems: 'center', gap: '8px' } },
          el(
            Button,
            { isPrimary: true, onClick: generateBrief, disabled: briefLoading },
            briefMode === 'openai' ? __('Generate with OpenAI', 'wp-seo-next') : __('Generate locally', 'wp-seo-next')
          ),
          briefLoading ? el(Spinner, null) : null
        ),

        briefError ? el(Notice, { status: 'error', isDismissible: false }, briefError) : null,
        briefNotice
          ? el(
              Notice,
              {
                status: 'success',
                isDismissible: true,
                onRemove: function () {
                  setBriefNotice('');
                },
              },
              briefNotice
            )
          : null,

        briefStored && briefStored.updated_at
          ? el('p', { className: 'description' }, __('Saved:', 'wp-seo-next') + ' ' + briefStored.updated_at)
          : null,

        briefData && briefData.brief
          ? el(
              'div',
              { style: { marginTop: '10px' } },
              briefData.meta && (briefData.meta.source || briefData.meta.generated_at)
                ? el(
                    'p',
                    { className: 'description' },
                    __('Source:', 'wp-seo-next') + ' ' + (briefData.meta.source || '-') + ' · ' + __('Generated:', 'wp-seo-next') + ' ' + (briefData.meta.generated_at || '-')
                  )
                : null,

              briefData.meta && (briefData.meta.preset || briefData.meta.intent)
                ? el(
                    'p',
                    { className: 'description' },
                    (briefData.meta.preset ? (__('Preset:', 'wp-seo-next') + ' ' + String(briefData.meta.preset)) : '') +
                      ((briefData.meta.preset && briefData.meta.intent) ? ' · ' : '') +
                      (briefData.meta.intent ? (__('Intent:', 'wp-seo-next') + ' ' + String(briefData.meta.intent) + (briefData.meta.intent_local ? ' · ' + __('Local', 'wp-seo-next') : '')) : '')
                  )
                : null,

              briefData.meta && briefData.meta.coverage && briefData.meta.coverage.total
                ? (function () {
                    var cov = briefData.meta.coverage;
                    var total = Number(cov.total || 0);
                    var ch = Number(cov.covered_heading || 0);
                    var cc = Number(cov.covered_content || 0);
                    var items = Array.isArray(cov.items) ? cov.items : [];
                    var missing = items.filter(function (it) { return it && it.query && !it.covered_heading; }).slice(0, 6);
                    return el(
                      'div',
                      { style: { marginTop: '8px' } },
                      el('p', { className: 'description' }, __('Coverage (top queries):', 'wp-seo-next') + ' ' + __('H2/H3', 'wp-seo-next') + ' ' + ch + '/' + total + ' · ' + __('Content', 'wp-seo-next') + ' ' + cc + '/' + total),
                      missing && missing.length
                        ? el(
                            'div',
                            { style: { marginTop: '6px' } },
                            el('strong', null, __('Missing (not covered by headings):', 'wp-seo-next')),
                            el(
                              'ul',
                              { style: { margin: '6px 0 0 18px' } },
                              missing.map(function (it, mi) {
                                var q = it && it.query ? String(it.query) : '';
                                var h2 = toHeadingFromQuery(q);
                                var n = normalizeText(h2);
                                var already = false;
                                var bestH2 = it && it.best_h2 ? String(it.best_h2) : '';
                                bestH2 = bestH2 ? bestH2.trim() : '';

                                // Back-compat fallback (older saved briefs only had best_heading).
                                var bestH = it && it.best_heading ? String(it.best_heading) : '';
                                bestH = bestH ? bestH.trim() : '';

                                var bestTarget = bestH2 || bestH;
                                var targetIdx = -1;
                                var alreadyH3 = false;
                                if (briefOutlineDraft && Array.isArray(briefOutlineDraft)) {
                                  for (var i = 0; i < briefOutlineDraft.length; i++) {
                                    if (briefOutlineDraft[i] && normalizeText(briefOutlineDraft[i].title) === n) {
                                      already = true;
                                      break;
                                    }
                                  }

                                  if (bestTarget) {
                                    targetIdx = findBestOutlineSectionIndexInOutline(briefOutlineDraft, bestTarget);
                                    if (targetIdx >= 0 && briefOutlineDraft[targetIdx] && Array.isArray(briefOutlineDraft[targetIdx].h3)) {
                                      var h3List = briefOutlineDraft[targetIdx].h3;
                                      for (var j = 0; j < h3List.length; j++) {
                                        if (h3List[j] && normalizeText(h3List[j]) === n) {
                                          alreadyH3 = true;
                                          break;
                                        }
                                      }
                                    }
                                  }
                                }

                                var canH3 = !!bestTarget && targetIdx >= 0 && !already && !alreadyH3;

                                return el(
                                  'li',
                                  { key: mi, style: { marginBottom: '6px' } },
                                  el(
                                    'div',
                                    { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } },
                                    el(
                                      'div',
                                      null,
                                      el(
                                        'div',
                                        null,
                                        q + ' (' + Math.round(it.impressions || 0) + ' ' + __('imp', 'wp-seo-next') + ', ' + __('pos', 'wp-seo-next') + ' ' + (it.position ? Number(it.position).toFixed(1) : '–') + ')'
                                      ),
                                      bestTarget
                                        ? el(
                                            'div',
                                            { style: { fontSize: '11px', opacity: 0.7, marginTop: '2px' } },
                                            (bestH2 ? __('Best H2:', 'wp-seo-next') : __('Best match:', 'wp-seo-next')) + ' ' + bestTarget
                                          )
                                        : null
                                    ),
                                    el(
                                      'div',
                                      { style: { display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' } },
                                      bestTarget
                                        ? el(
                                            Button,
                                            {
                                              isSecondary: true,
                                              isSmall: true,
                                              disabled: !canH3,
                                              title: bestTarget ? (__('Best match:', 'wp-seo-next') + ' ' + bestTarget) : '',
                                              onClick: function () {
                                                addMissingQueryAsH3(q, bestTarget);
                                              },
                                            },
                                            (already || alreadyH3) ? __('Added', 'wp-seo-next') : __('Add H3', 'wp-seo-next')
                                          )
                                        : null,
                                      el(
                                        Button,
                                        {
                                          isSecondary: true,
                                          isSmall: true,
                                          disabled: already,
                                          onClick: function () {
                                            addMissingQueryAsH2(q);
                                          },
                                        },
                                        already ? __('Added', 'wp-seo-next') : __('Add H2', 'wp-seo-next')
                                      )
                                    )
                                  )
                                );
                              })
                            )
                          )
                        : null
                    );
                  })()
                : null,

              briefOutlineDraft && Array.isArray(briefOutlineDraft) && briefOutlineDraft.length
                ? el(
                    'div',
                    null,
                    el(
                      'div',
                      { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } },
                      el('h4', { style: { margin: 0 } }, __('Outline (H2/H3)', 'wp-seo-next')),
                      isOutlineDirty()
                        ? el(Button, { isSecondary: true, isSmall: true, onClick: resetOutlineDraft }, __('Reset draft', 'wp-seo-next'))
                        : null
                    ),
                    el(
                      'ol',
                      { style: { marginLeft: '18px' } },
                      briefOutlineDraft.map(function (sec, idx) {
                        if (!sec || !sec.title) return null;
                        var isFlash = briefOutlineFlash && Number(briefOutlineFlash.sectionIdx) === Number(idx);
                        var liStyle = { padding: '6px 8px', borderRadius: '6px', marginBottom: '6px' };
                        if (isFlash) {
                          liStyle.background = 'rgba(0,0,0,.06)';
                          liStyle.border = '1px solid rgba(0,0,0,.12)';
                        }
                        return el(
                          'li',
                          {
                            key: idx,
                            style: liStyle,
                            ref: function (node) {
                              try {
                                if (!briefOutlineRefs || !briefOutlineRefs.current) return;
                                if (node) {
                                  briefOutlineRefs.current[idx] = node;
                                } else if (briefOutlineRefs.current[idx]) {
                                  delete briefOutlineRefs.current[idx];
                                }
                              } catch (e) {
                                // ignore
                              }
                            },
                          },
                          el(
                            'div',
                            { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } },
                            el(
                              'div',
                              null,
                              el('strong', null, sec.title),
                              sec.__wpsn_added ? el('span', { style: { marginLeft: '6px', fontSize: '11px', opacity: 0.7 } }, __('Added', 'wp-seo-next')) : null
                              ,
                              isFlash && briefOutlineFlash && briefOutlineFlash.type === 'h3'
                                ? el(
                                    'div',
                                    { style: { fontSize: '11px', opacity: 0.75, marginTop: '4px' } },
                                    briefOutlineFlash.before
                                      ? el('div', null, '… ' + String(briefOutlineFlash.before))
                                      : null,
                                    el('div', null, '+ ' + String(briefOutlineFlash.inserted || ''))
                                    ,
                                    briefOutlineFlash.after
                                      ? el('div', null, '… ' + String(briefOutlineFlash.after))
                                      : null
                                  )
                                : null,
                              isFlash && briefOutlineFlash && briefOutlineFlash.type === 'h2'
                                ? el('div', { style: { fontSize: '11px', opacity: 0.75, marginTop: '4px' } }, __('Inserted section:', 'wp-seo-next') + ' ' + String(briefOutlineFlash.inserted || sec.title))
                                : null
                            ),
                            el(
                              Button,
                              { isLink: true, isSmall: true, isDestructive: true, onClick: function () { removeOutlineSection(idx); } },
                              __('Remove', 'wp-seo-next')
                            )
                          ),
                          sec.h3 && sec.h3.length
                            ? el(
                                'ul',
                                { style: { margin: '6px 0 0 18px' } },
                                sec.h3.map(function (h, j) {
                                  return h
                                    ? el(
                                        'li',
                                        { key: j, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } },
                                        el('span', null, h),
                                        el(
                                          Button,
                                          { isLink: true, isSmall: true, isDestructive: true, onClick: function () { removeOutlineH3(idx, j); } },
                                          __('Remove', 'wp-seo-next')
                                        )
                                      )
                                    : null;
                                })
                              )
                            : null
                        );
                      })
                    )
                  )
                : null,

              briefData.brief.faq && briefData.brief.faq.length
                ? el(
                    'div',
                    { style: { marginTop: '10px' } },
                    el('h4', null, __('FAQ', 'wp-seo-next')),
                    el(
                      'ol',
                      { style: { marginLeft: '18px' } },
                      briefData.brief.faq.map(function (item, idx) {
                        if (!item || !item.q) return null;
                        return el(
                          'li',
                          { key: idx },
                          el('strong', null, item.q),
                          item.a ? el('div', { style: { marginTop: '4px' } }, item.a) : null
                        );
                      })
                    )
                  )
                : null,

              briefData.brief.recommendations && briefData.brief.recommendations.length
                ? el(
                    'div',
                    { style: { marginTop: '10px' } },
                    el('h4', null, __('Recommendations', 'wp-seo-next')),
                    el(
                      'ul',
                      { style: { marginLeft: '18px' } },
                      briefData.brief.recommendations.map(function (r, idx) {
                        return r ? el('li', { key: idx }, r) : null;
                      })
                    )
                  )
                : null,

              briefData.brief.terms && briefData.brief.terms.length
                ? el(
                    'div',
                    { style: { marginTop: '10px' } },
                    el('h4', null, __('Terms to include', 'wp-seo-next')),
                    el('p', null, briefData.brief.terms.join(', '))
                  )
                : null,

              el(SelectControl, {
                label: __('Insert location', 'wp-seo-next'),
                value: briefInsertLocation,
                options: [
                  { label: __('End of content', 'wp-seo-next'), value: 'end' },
                  { label: __('After first block', 'wp-seo-next'), value: 'after_first' },
                  { label: __('After Table of Contents', 'wp-seo-next'), value: 'after_toc' },
                ],
                onChange: function (v) {
                  setBriefInsertLocation(v);
                },
              }),

              el(
                'label',
                { style: { display: 'block', marginTop: '6px' } },
                el('input', {
                  type: 'checkbox',
                  checked: !!briefInsertEmptyParagraphs,
                  onChange: function (ev) {
                    setBriefInsertEmptyParagraphs(!!(ev && ev.target && ev.target.checked));
                  },
                }),
                ' ',
                __('Insert empty paragraphs under headings', 'wp-seo-next')
              ),

              el(
                'div',
                { style: { marginTop: '12px', display: 'flex', gap: '8px' } },
                el(Button, { isSecondary: true, onClick: function () { insertBriefBlocks(false); } }, __('Insert outline', 'wp-seo-next')),
                el(Button, { isSecondary: true, onClick: function () { insertBriefBlocks(true); } }, __('Insert outline + FAQ', 'wp-seo-next'))
              )
            )
          : el('p', { className: 'description', style: { marginTop: '10px' } }, __('Generate a brief to see results.', 'wp-seo-next'))
      );
    } else {
      briefPanel = el(
        PanelBody,
        { title: __('Content brief', 'wp-seo-next'), initialOpen: false },
        el(Notice, { status: 'warning', isDismissible: false }, __('Search Console module is disabled. Enable it in Settings → Modules.', 'wp-seo-next')),
        CONFIG.settingsUrl ? LinkRow(__('Open settings', 'wp-seo-next'), CONFIG.settingsUrl) : null
      );
    }

    return el(
      'div',
      { className: 'wpsn-editor-sidebar' },

      el(
        PanelBody,
        { title: __('SEO Meta', 'wp-seo-next'), initialOpen: true },

        el(TextControl, {
          label: __('SEO Title', 'wp-seo-next'),
          value: meta[META.title] || '',
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.title] = v; return p;
            })());
          },
          help: __('Leave empty to use global template.', 'wp-seo-next'),
        }),

        el(TextareaControl, {
          label: __('Meta Description', 'wp-seo-next'),
          value: meta[META.description] || '',
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.description] = v; return p;
            })());
          },
          help: __('Leave empty to use template / excerpt.', 'wp-seo-next'),
        }),

        META.focusKeyword ? el(TextControl, {
          label: __('Focus keyword', 'wp-seo-next'),
          value: meta[META.focusKeyword] || '',
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.focusKeyword] = v; return p;
            })());
          },
          help: __('Used for on-page analysis checks (optional).', 'wp-seo-next'),
        }) : null,

        el(TextControl, {
          label: __('Canonical URL', 'wp-seo-next'),
          type: 'url',
          value: meta[META.canonical] || '',
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.canonical] = v; return p;
            })());
          },
          help: __('Leave empty for automatic canonical.', 'wp-seo-next'),
        }),

        el(SelectControl, {
          label: __('Robots: Indexing', 'wp-seo-next'),
          value: robotsIndexVal,
          options: [
            { label: __('Inherit defaults', 'wp-seo-next'), value: 'default' },
            { label: __('Index', 'wp-seo-next'), value: 'index' },
            { label: __('Noindex', 'wp-seo-next'), value: 'noindex' },
          ],
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.robotsIndex] = v; return p;
            })());
          },
        }),

        el(SelectControl, {
          label: __('Robots: Following', 'wp-seo-next'),
          value: robotsFollowVal,
          options: [
            { label: __('Inherit defaults', 'wp-seo-next'), value: 'default' },
            { label: __('Follow', 'wp-seo-next'), value: 'follow' },
            { label: __('Nofollow', 'wp-seo-next'), value: 'nofollow' },
          ],
          onChange: function (v) {
            setMetaPatch((function () {
              var p = {}; p[META.robotsFollow] = v; return p;
            })());
          },
        })
      ),

      socialPanel,
      UI_IS_PRO ? twitterPanel : null,
      UI_IS_PRO ? videoPanel : null,
      UI_IS_PRO ? suggestionsPanel : null,
      UI_IS_PRO ? aiPanel : null,
      UI_IS_PRO ? analysisPanel : null,
      UI_IS_PRO ? gscPanel : null,
      UI_IS_PRO ? briefPanel : null,
      UI_IS_PRO ? internalLinksPanel : null,
      UI_IS_PRO ? schemaPanel : null,

      el(
        PanelBody,
        { title: __('Snippet Preview', 'wp-seo-next'), initialOpen: true },

        loading ? el(Spinner, null) : null,

        error ? el(Notice, { status: 'error', isDismissible: false }, error) : null,

        preview ? el(
          'div',
          { style: { marginTop: '8px' } },
          el('div', { style: { fontSize: '16px', fontWeight: 600, marginBottom: '4px' } }, preview.title || ''),
          el('div', { style: { color: '#3c4043', fontSize: '12px', marginBottom: '4px' } }, preview.canonical || ''),
          el('div', { style: { fontSize: '13px', lineHeight: 1.4 } }, preview.description || '')
        ) : el(
          'p',
          { className: 'description' },
          __('Preview will appear here.', 'wp-seo-next')
        ),

        el(
          'div',
          { style: { marginTop: '12px' } },
          el(
            Button,
            { isSecondary: true, onClick: fetchPreview, disabled: loading },
            __('Refresh preview', 'wp-seo-next')
          )
        )
      )
    );
  }

  var SIDEBAR_LABEL = __('SEO', 'wp-seo-next');
  if (window.WPSN_EDITOR && window.WPSN_EDITOR.whiteLabel && window.WPSN_EDITOR.whiteLabel.sidebarLabel) {
    SIDEBAR_LABEL = window.WPSN_EDITOR.whiteLabel.sidebarLabel;
  }

  registerPlugin('wpsn-editor-sidebar', {
    icon: 'chart-area',
    render: function () {
      return el(
        Fragment,
        null,
        el(
          PluginSidebarMoreMenuItem,
          { target: 'wpsn-editor-sidebar' },
          SIDEBAR_LABEL
        ),
        el(
          PluginSidebar,
          { name: 'wpsn-editor-sidebar', title: SIDEBAR_LABEL },
          el(SidebarApp, null)
        )
      );
    },
  });
})(window.wp);
