/**
 * Linking Suggestions Panel Component
 *
 * Displays internal linking suggestions in the editor sidebar.
 * Uses window.wp.* directly to avoid ES module shim timing issues.
 *
 * @package
 */

import { debounce } from 'lodash';

// Import styles
import './linking-suggestions-panel.css';

const getSuggestionKey = (suggestion, index) =>
  `${String(suggestion?.url || '')}::${String(suggestion?.title || '')}::${index}`;

const decodeHtmlEntities = (value) => {
  if (typeof document === 'undefined') {
    return String(value || '');
  }

  try {
    const textarea = document.createElement('textarea');
    textarea.innerHTML = String(value || '');
    return textarea.value;
  } catch (error) {
    return String(value || '');
  }
};

const escapeRegex = (value) => String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

const buildAnchorMatcher = (anchorText) => {
  const normalized = decodeHtmlEntities(anchorText).trim();
  if (!normalized) {
    return null;
  }

  const tokens = normalized
    .split(/\s+/)
    .filter(Boolean)
    .map((token) => escapeRegex(token));

  if (!tokens.length) {
    return null;
  }

  const joiner = "(?:[\\s\\u00A0]+|[\\-–—'\"’“”.,;:!?()]+|<[^>]+>)+";
  return new RegExp(tokens.join(joiner), 'i');
};

const normalizeComparableUrl = (rawUrl) => {
  const value = String(rawUrl || '').trim();
  if (!value) {
    return '';
  }

  try {
    const fallbackOrigin = typeof window !== 'undefined' ? window.location.origin : 'https://example.com';
    const parsed = new URL(value, fallbackOrigin);
    return `${parsed.origin}${parsed.pathname}`.replace(/\/+$/, '').toLowerCase();
  } catch (error) {
    return value.replace(/\/+$/, '').toLowerCase();
  }
};

const extractContentLinks = (html) => {
  const content = String(html || '');
  if (!content) {
    return [];
  }

  if (typeof DOMParser === 'undefined') {
    const links = [];
    const linkRegex = /<a[^>]+href=(["'])([\s\S]*?)\1[^>]*>([\s\S]*?)<\/a>/gi;
    let match;
    while ((match = linkRegex.exec(content)) !== null) {
      links.push({
        href: normalizeComparableUrl(match[2] || ''),
        text: decodeHtmlEntities(String(match[3] || '').replace(/<[^>]*>/g, '')).trim(),
      });
    }
    return links;
  }

  try {
    const parser = new DOMParser();
    const documentNode = parser.parseFromString(`<div>${content}</div>`, 'text/html');
    return Array.from(documentNode.querySelectorAll('a[href]')).map((link) => ({
      href: normalizeComparableUrl(link.getAttribute('href') || ''),
      text: decodeHtmlEntities(link.textContent || '').trim(),
    }));
  } catch (error) {
    return [];
  }
};

const isSuggestionInsertedInLinks = (contentLinks, anchorText, targetUrl) => {
  const normalizedTarget = normalizeComparableUrl(targetUrl);
  if (!Array.isArray(contentLinks) || !contentLinks.length || !normalizedTarget) {
    return false;
  }

  const normalizedAnchor = decodeHtmlEntities(anchorText).trim().toLowerCase();
  const anchorMatcher = buildAnchorMatcher(anchorText);

  for (const link of contentLinks) {
    if (link.href !== normalizedTarget) {
      continue;
    }

    if (!normalizedAnchor) {
      return true;
    }

    if (!link.text) {
      continue;
    }

    if (link.text.toLowerCase().includes(normalizedAnchor)) {
      return true;
    }

    if (anchorMatcher && anchorMatcher.test(link.text)) {
      return true;
    }
  }

  return false;
};

const normalizeRelevance = (value) => {
  if (typeof value !== 'number' || Number.isNaN(value)) {
    return null;
  }

  const normalized = value <= 1 ? value * 100 : value;
  return Math.max(0, Math.min(100, Math.round(normalized)));
};

const getInboundOpportunityCount = (response) => {
  const root = response?.data || response || {};
  const opportunities = Array.isArray(root?.opportunities) ? root.opportunities : [];
  return opportunities.length;
};

const buildQuickEmptyStateMessage = ({
  outboundMessage,
  inboundCount,
  __,
  sprintf,
  textDomain,
}) => {
  if (inboundCount > 0) {
    // The inbound-preview block below states the count — keep this line short
    // so the two don't repeat each other.
    return __('No outbound quick suggestions found for this post.', textDomain);
  }

  return (
    outboundMessage ||
    __('No outbound quick suggestions found for this post yet. Try adding more relevant content or review Full Workflow.', textDomain)
  );
};

const normalizeInboundOpportunities = (response) => {
  const root = response?.data || response || {};
  const opportunities = Array.isArray(root?.opportunities) ? root.opportunities : [];

  return opportunities
    .map((item, index) => ({
      id: String(item?.id || `inbound-${index}`),
      sourceId: Number(item?.source_id || 0),
      sourceTitle: String(item?.source_title || ''),
      sourceUrl: String(item?.source_url || ''),
      targetId: Number(item?.target_id || 0),
      anchor: String(item?.anchor_text || item?.target_title || '').trim(),
    }))
    .filter((item) => item.sourceId > 0 && item.targetId > 0 && item.anchor);
};

const getPlainTextLength = (value) =>
  String(value || '')
    .replace(/<[^>]*>/g, ' ')
    .replace(/&nbsp;/gi, ' ')
    .replace(/\s+/g, ' ')
    .trim().length;

const LinkingSuggestionsPanel = ({ postId }) => {
  // Access WordPress APIs at render time to avoid shim timing issues
  const { useState, useEffect, useCallback, useMemo, useRef } = window.wp.element;
  const { __ = (text) => text, sprintf = (format, ...args) => [format, ...args].join(' ') } = window.wp?.i18n || {};
  const { useSelect, useDispatch } = window.wp.data;
  const apiFetch = window.wp.apiFetch;
  const {
    Button,
    Card,
    CardBody,
    ExternalLink,
    Spinner,
    Notice,
    Modal,
    TextControl,
  } = window.wp.components;
  // State for suggestions, loading, and error
  const [suggestions, setSuggestions] = useState([]);
  const [quickAnchors, setQuickAnchors] = useState({});
  const [isLoading, setIsLoading] = useState(false);
  const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
  const [error, setError] = useState(null);
  const [emptyStateMessage, setEmptyStateMessage] = useState('');
  const [inboundOpportunities, setInboundOpportunities] = useState([]);
  const [inboundOpportunityCount, setInboundOpportunityCount] = useState(0);
  const [linkingSettings, setLinkingSettings] = useState(null);
  const [quickFeedback, setQuickFeedback] = useState(null);
  const [insertedSuggestionKeys, setInsertedSuggestionKeys] = useState({});

  // State for anchor text editing modal
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [editingAnchorText, setEditingAnchorText] = useState('');
  const [pendingSuggestion, setPendingSuggestion] = useState(null);
  const [pendingSuggestionKey, setPendingSuggestionKey] = useState('');
  const maxQuickSuggestions = 5;

  // Get current post content
  const { postContent, postTitle } = useSelect(
    (select) => {
      const { getEditedPostContent, getEditedPostAttribute } = select('core/editor');
      return {
        postContent: getEditedPostContent(),
        postTitle: typeof getEditedPostAttribute === 'function' ? getEditedPostAttribute('title') : '',
      };
    },
    []
  );

  // Get block editor dispatch functions
  const { insertBlocks, updateBlockAttributes } = useDispatch('core/block-editor');

  // Keep the latest content in a ref so callbacks can read it without
  // re-creating themselves (and re-triggering fetch effects) on every keystroke.
  const contentRef = useRef({ postContent: '', postTitle: '' });
  contentRef.current = { postContent, postTitle };

  const hasAnalyzableContent = useCallback(() => {
    const { postContent: currentContent, postTitle: currentTitle } = contentRef.current;
    return getPlainTextLength(currentContent) > 0 || String(currentTitle || '').trim().length > 0;
  }, []);

  useEffect(() => {
    let isMounted = true;
    apiFetch({ path: '/prorank-seo/v1/settings/internal-linking' })
      .then((response) => {
        if (!isMounted) {
          return;
        }
        if (response && response.success && response.data) {
          setLinkingSettings(response.data);
        }
      })
      .catch(() => {
        // Ignore settings fetch errors; fallback to defaults.
      });
    return () => {
      isMounted = false;
    };
  }, []);

  const buildAnchorRegex = useCallback((value) => {
    let decoded = value || '';
    if (typeof document !== 'undefined') {
      try {
        const txt = document.createElement('textarea');
        txt.innerHTML = decoded;
        decoded = txt.value;
      } catch (e) {
        // Ignore decode errors
      }
    }

    const trimmed = String(decoded).trim();
    if (!trimmed) {
      return null;
    }
    const tokens = trimmed
      .split(/\s+/)
      .filter(Boolean)
      .map((token) => token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
    if (!tokens.length) {
      return null;
    }
    const joiner = "(?:[\\s\\u00A0]+|[\\-–—'\"’“”.,;:!?()]+|<[^>]+>)+";
    const pattern = tokens.join(joiner);
    return new RegExp(pattern, 'i');
  }, []);

  const insertLinkInHtml = useCallback((html, anchorText, url) => {
    if (!html || !anchorText) {
      return { html, inserted: false };
    }

    const matcher = buildAnchorRegex(anchorText);
    if (!matcher) {
      return { html, inserted: false };
    }

    const parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
    if (!parser || !window.NodeFilter) {
      const match = matcher.exec(html);
      if (!match) {
        return { html, inserted: false };
      }
      const index = match.index;
      const matchText = match[0];
      const before = html.slice(0, index);
      const after = html.slice(index + matchText.length);
      return {
        html: `${before}<a href="${url}">${matchText}</a>${after}`,
        inserted: true,
      };
    }

    const doc = parser.parseFromString(`<div>${html}</div>`, 'text/html');
    const container = doc.body?.firstChild;
    if (!container) {
      return { html, inserted: false };
    }

    const textNodes = [];
    const walker = doc.createTreeWalker(container, window.NodeFilter.SHOW_TEXT);

    while (walker.nextNode()) {
      const node = walker.currentNode;
      const parent = node?.parentNode;
      if (!node?.nodeValue || !parent) {
        continue;
      }
      if (parent.nodeName && parent.nodeName.toLowerCase() === 'a') {
        continue;
      }
      textNodes.push(node);
    }

    if (textNodes.length) {
      let fullText = '';
      const map = [];
      for (const node of textNodes) {
        const start = fullText.length;
        const value = node.nodeValue;
        fullText += value;
        map.push({ node, start, end: start + value.length });
      }

      const match = matcher.exec(fullText);
      if (match) {
        const matchStart = match.index;
        const matchEnd = matchStart + match[0].length;
        let startNode = null;
        let endNode = null;
        let startOffset = 0;
        let endOffset = 0;

        for (const entry of map) {
          if (!startNode && matchStart >= entry.start && matchStart < entry.end) {
            startNode = entry.node;
            startOffset = matchStart - entry.start;
          }
          if (!endNode && matchEnd > entry.start && matchEnd <= entry.end) {
            endNode = entry.node;
            endOffset = matchEnd - entry.start;
            break;
          }
        }

        if (startNode && endNode) {
          const range = doc.createRange();
          range.setStart(startNode, startOffset);
          range.setEnd(endNode, endOffset);

          const fragment = range.cloneContents();
          if (!fragment.querySelector || !fragment.querySelector('a')) {
            const linkNode = doc.createElement('a');
            linkNode.setAttribute('href', url);
            const contents = range.extractContents();
            linkNode.appendChild(contents);
            range.insertNode(linkNode);
            return { html: container.innerHTML, inserted: true };
          }
        }
      }
    }

    let inserted = false;
    for (const node of textNodes) {
      if (inserted) {
        break;
      }
      const parent = node?.parentNode;
      if (!node?.nodeValue || !parent) {
        continue;
      }
      const text = node.nodeValue;
      const match = matcher.exec(text);
      if (!match) {
        continue;
      }

      const before = text.slice(0, match.index);
      const matchText = match[0];
      const after = text.slice(match.index + matchText.length);

      const linkNode = doc.createElement('a');
      linkNode.setAttribute('href', url);
      linkNode.textContent = matchText;

      const fragment = doc.createDocumentFragment();
      if (before) {
        fragment.appendChild(doc.createTextNode(before));
      }
      fragment.appendChild(linkNode);
      if (after) {
        fragment.appendChild(doc.createTextNode(after));
      }

      parent.replaceChild(fragment, node);
      inserted = true;
    }

    return { html: container.innerHTML, inserted };
  }, [buildAnchorRegex]);

  const syncFetchedSuggestions = useCallback((fetchedSuggestions = []) => {
    const normalizedSuggestions = Array.isArray(fetchedSuggestions) ? fetchedSuggestions : [];

    setSuggestions(normalizedSuggestions);
    setQuickAnchors((previous) => {
      const next = { ...previous };
      const validKeys = new Set();

      normalizedSuggestions.forEach((suggestion, index) => {
        const key = getSuggestionKey(suggestion, index);
        validKeys.add(key);
        if (typeof next[key] === 'string' && next[key].trim() !== '') {
          return;
        }
        next[key] = suggestion?.suggested_anchor || suggestion?.anchor_text || suggestion?.anchor || '';
      });

      Object.keys(next).forEach((key) => {
        if (!validKeys.has(key)) {
          delete next[key];
        }
      });

      return next;
    });

    setInsertedSuggestionKeys((previous) => {
      const next = { ...previous };
      const validKeys = new Set();

      normalizedSuggestions.forEach((suggestion, index) => {
        validKeys.add(getSuggestionKey(suggestion, index));
      });

      Object.keys(next).forEach((key) => {
        if (!validKeys.has(key)) {
          delete next[key];
        }
      });

      return next;
    });

    return normalizedSuggestions.length;
  }, []);

  // Fetch suggestions from API
  const fetchSuggestions = useCallback(async () => {
    if (!postId) {
      return;
    }

    setIsLoading(true);
    setError(null);
    setEmptyStateMessage('');
    setQuickFeedback(null);

    try {
      const response = await apiFetch({
        path: `/prorank-seo/v1/linking/basic-suggestions/${postId}`,
        method: 'GET',
      });

      if (response.success && response.data) {
        const fetchedSuggestions = response.data.suggestions || [];
        const suggestionsCount = syncFetchedSuggestions(fetchedSuggestions);

        const inboundResponse = await apiFetch({
          path: `/prorank-seo/v1/linking/inbound-opportunities?target_post_id=${postId}`,
          method: 'GET',
        });
        const inboundItems = normalizeInboundOpportunities(inboundResponse);
        const inboundCount = inboundItems.length;
        setInboundOpportunities(inboundItems.slice(0, 3));
        setInboundOpportunityCount(inboundCount);

        if (suggestionsCount === 0) {
          const outboundMessage = String(response?.data?.message || '');

          if (inboundCount > 0) {
            setEmptyStateMessage(
              buildQuickEmptyStateMessage({
                outboundMessage,
                inboundCount,
                __,
                sprintf,
                textDomain: 'prorank-seo',
              })
            );
          } else if (outboundMessage === __('No content to analyze for suggestions.', 'prorank-seo')) {
            if (hasAnalyzableContent()) {
              setEmptyStateMessage(
                __('Current draft content is not part of the last saved scan yet. Click "Scan This Post" to analyze what you are writing now.', 'prorank-seo')
              );
            } else {
              setEmptyStateMessage(
                __('Add content to this post, then run a scan to generate quick internal link suggestions.', 'prorank-seo')
              );
            }
          } else {
            setEmptyStateMessage(
              buildQuickEmptyStateMessage({
                outboundMessage,
                inboundCount,
                __,
                sprintf,
                textDomain: 'prorank-seo',
              })
            );
          }
        } else {
          setEmptyStateMessage('');
        }
      } else {
        setEmptyStateMessage('');
        setInboundOpportunities([]);
        setInboundOpportunityCount(0);
        setError(response.message || __('Failed to fetch suggestions.', 'prorank-seo'));
      }
    } catch (err) {
      setEmptyStateMessage('');
      setInboundOpportunities([]);
      setInboundOpportunityCount(0);
      setError(
        __('An error occurred while fetching suggestions. Please try again.', 'prorank-seo')
      );
    } finally {
      setIsLoading(false);
      setHasLoadedOnce(true);
    }
  }, [postId, syncFetchedSuggestions, apiFetch, __, sprintf, hasAnalyzableContent]);

  const handleScanThisPost = useCallback(async () => {
    if (!postId) {
      return;
    }

    if (!hasAnalyzableContent()) {
      setError(null);
      setQuickFeedback({
        status: 'info',
        message: __('Add content to this post, then scan again for internal link suggestions.', 'prorank-seo'),
      });
      setSuggestions([]);
      setInboundOpportunities([]);
      setInboundOpportunityCount(0);
      setEmptyStateMessage(
        __('Add content to this post, then run a scan to generate quick internal link suggestions.', 'prorank-seo')
      );
      return;
    }

    setIsLoading(true);
    setError(null);
    setEmptyStateMessage('');
    setQuickFeedback(null);

    try {
      const analyzeResponse = await apiFetch({
        path: '/prorank-seo/v1/linking/analyze',
        method: 'POST',
        data: {
          post_id: postId,
          content: contentRef.current.postContent || '',
          title: contentRef.current.postTitle || '',
        },
      });

      if (analyzeResponse?.success === false) {
        throw new Error(analyzeResponse?.message || __('Failed to scan this post.', 'prorank-seo'));
      }

      const analyzePayload = analyzeResponse?.data || {};
      const outboundSuggestions = analyzePayload.suggestions || [];
      const outboundCount = syncFetchedSuggestions(outboundSuggestions);

      const inboundResponse = await apiFetch({
        path: `/prorank-seo/v1/linking/inbound-opportunities?target_post_id=${postId}`,
        method: 'GET',
      });
      const inboundItems = normalizeInboundOpportunities(inboundResponse);
      const inboundCount = inboundItems.length;
      setInboundOpportunities(inboundItems.slice(0, 3));
      setInboundOpportunityCount(inboundCount);

      if (outboundCount === 0) {
        const message = buildQuickEmptyStateMessage({
          outboundMessage: String(analyzePayload.message || ''),
          inboundCount,
          __,
          sprintf,
          textDomain: 'prorank-seo',
        });
        setEmptyStateMessage(message);
        setQuickFeedback({
          status: inboundCount > 0 ? 'success' : 'info',
          message,
        });
      } else {
        setEmptyStateMessage('');
        const message = sprintf(
          __('Scan complete. %1$d outbound suggestions and %2$d inbound opportunities found.', 'prorank-seo'),
          outboundCount,
          inboundCount
        );
        setQuickFeedback({
          status: 'success',
          message,
        });
      }
    } catch (error) {
      setEmptyStateMessage('');
      setInboundOpportunities([]);
      setInboundOpportunityCount(0);
      const message = error?.message || __('Failed to scan this post.', 'prorank-seo');
      setError(message);
      setQuickFeedback({
        status: 'error',
        message,
      });
    } finally {
      setIsLoading(false);
      setHasLoadedOnce(true);
    }
  }, [postId, syncFetchedSuggestions, sprintf, __, apiFetch, hasAnalyzableContent]);

  // Initial load — fetch once per post. Content changes never auto-refetch;
  // the manual "Scan This Post" and "Refresh" buttons cover that.
  const fetchSuggestionsRef = useRef(fetchSuggestions);
  fetchSuggestionsRef.current = fetchSuggestions;
  useEffect(() => {
    fetchSuggestionsRef.current();
  }, [postId]);

  // Keep inserted-state synchronized when links are inserted from the metabox
  // workflow. Debounced so typing does not trigger a DOM parse per keystroke,
  // and the content is parsed once and shared across all suggestions.
  const syncInsertedState = useMemo(
    () =>
      debounce((currentSuggestions, currentAnchors, content) => {
        const contentLinks = extractContentLinks(content);
        const next = {};

        currentSuggestions.forEach((suggestion, index) => {
          const key = getSuggestionKey(suggestion, index);
          const anchorText =
            String(
              currentAnchors[key] ||
                suggestion?.suggested_anchor ||
                suggestion?.anchor_text ||
                suggestion?.anchor ||
                ''
            ).trim();
          const targetUrl = String(suggestion?.url || '').trim();

          if (isSuggestionInsertedInLinks(contentLinks, anchorText, targetUrl)) {
            next[key] = true;
          }
        });

        setInsertedSuggestionKeys(next);
      }, 1000),
    []
  );

  useEffect(() => {
    if (!Array.isArray(suggestions) || suggestions.length === 0) {
      syncInsertedState.cancel();
      setInsertedSuggestionKeys({});
      return;
    }

    syncInsertedState(suggestions, quickAnchors, String(postContent || ''));
  }, [suggestions, quickAnchors, postContent, syncInsertedState]);

  // Cancel any pending debounced work on unmount.
  useEffect(() => () => syncInsertedState.cancel(), [syncInsertedState]);

  // Handle insert link button click - opens modal
  const handleInsertLink = useCallback((suggestion, preferredAnchor = '', suggestionKey = '') => {
    const suggestedAnchor =
      String(preferredAnchor || suggestion?.suggested_anchor || suggestion?.anchor_text || suggestion?.anchor || '').trim();

    // Set the pending suggestion and anchor text
    setPendingSuggestion(suggestion);
    setEditingAnchorText(suggestedAnchor);
    setPendingSuggestionKey(String(suggestionKey || ''));

    // Open the modal
    setIsModalOpen(true);
  }, []);

  // Handle the actual link insertion after modal confirmation
  const handleConfirmInsert = useCallback(() => {
    if (!pendingSuggestion || !editingAnchorText.trim()) {
      setQuickFeedback({
        status: 'warning',
        message: __('Enter anchor text before inserting a link.', 'prorank-seo'),
      });
      return;
    }

    try {
      const blockEditor = window.wp?.data?.select('core/block-editor');

      const anchorTextRaw = String(editingAnchorText || '').trim();
      const anchorTextEscaped = anchorTextRaw
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');

      // Sanitize URL to prevent XSS - remove dangerous protocols
      const sanitizedUrl = String(pendingSuggestion.url || '')
        .replace(/javascript:/gi, '')
        .replace(/data:/gi, '')
        .replace(/vbscript:/gi, '')
        .replace(/"/g, '&quot;')
        .trim();

      // Validate URL is a proper http/https URL or relative path
      if (!sanitizedUrl || (!sanitizedUrl.startsWith('http') && !sanitizedUrl.startsWith('/'))) {
        console.warn('Invalid URL rejected:', pendingSuggestion.url);
        setQuickFeedback({
          status: 'error',
          message: __('Invalid target URL for this suggestion.', 'prorank-seo'),
        });
        return;
      }

      const settings = linkingSettings || {};
      const siteOrigin = window.location.origin || '';
      const isExternal =
        sanitizedUrl.startsWith('http') && (!siteOrigin || !sanitizedUrl.startsWith(siteOrigin));
      const openInNewTab = isExternal
        ? settings.open_external_new_tab || settings.open_in_new_tab
        : settings.open_internal_new_tab || settings.open_in_new_tab;

      const relParts = [];
      if (openInNewTab) {
        relParts.push('noopener', 'noreferrer');
      }
      if (settings.add_nofollow && isExternal) {
        relParts.push('nofollow');
      }
      const relAttr = relParts.length ? ` rel="${Array.from(new Set(relParts)).join(' ')}"` : '';
      const targetAttr = openInNewTab ? ' target="_blank"' : '';

      let titleAttr = '';
      if (settings.add_title_attribute) {
        const titleValue = String(pendingSuggestion?.title || anchorTextRaw).replace(/"/g, '&quot;');
        titleAttr = ` title="${titleValue}"`;
      }

      // Create the link HTML with sanitized values
      const linkHTML = `<a href="${sanitizedUrl}"${targetAttr}${relAttr}${titleAttr}>${anchorTextEscaped}</a>`;

      const applyBlockUpdate = (block, key, html) => {
        try {
          const { replaceBlock } = window.wp?.data?.dispatch('core/block-editor') || {};
          const newAttributes = { ...block.attributes, [key]: html };
          const newBlock = window.wp.blocks.createBlock(block.name, newAttributes, block.innerBlocks);
          replaceBlock(block.clientId, newBlock);
        } catch (error) {
          updateBlockAttributes(block.clientId, { [key]: html });
        }
      };

      const insertIntoBlock = (block) => {
        if (!block) {
          return false;
        }
        const attributes = block.attributes || {};
        const matcher = buildAnchorRegex(anchorTextRaw);
        if (matcher) {
          for (const [key, value] of Object.entries(attributes)) {
            if (typeof value !== 'string' || value.length < anchorTextRaw.length) {
              continue;
            }
            if (!matcher.test(value)) {
              continue;
            }
            const result = insertLinkInHtml(value, anchorTextRaw, sanitizedUrl);
            if (result.inserted) {
              applyBlockUpdate(block, key, result.html);
              return true;
            }
          }
        }
        const innerBlocks = block.innerBlocks || [];
        for (const inner of innerBlocks) {
          if (insertIntoBlock(inner)) {
            return true;
          }
        }
        return false;
      };

      const blocks = blockEditor?.getBlocks ? (blockEditor.getBlocks() || []) : [];
      let inserted = false;
      for (const block of blocks) {
        if (insertIntoBlock(block)) {
          inserted = true;
          break;
        }
      }

      if (!inserted && postContent && !/<!--\s+wp:/.test(String(postContent || ''))) {
        const { editPost } = window.wp?.data?.dispatch('core/editor') || {};
        const result = insertLinkInHtml(postContent, anchorTextRaw, sanitizedUrl);
        if (result.inserted && typeof editPost === 'function') {
          editPost({ content: result.html });
          inserted = true;
        }
      }

      if (!inserted && (!postContent || !postContent.trim())) {
        const newBlock = window.wp.blocks.createBlock('core/paragraph', {
          content: linkHTML,
        });
        insertBlocks([newBlock]);
        inserted = true;
      }

      if (!inserted) {
        const failureMessage = __(
          'Anchor text not found in the current content. Edit the anchor text to match your content and try again.',
          'prorank-seo'
        );
        setQuickFeedback({
          status: 'error',
          message: failureMessage,
        });
        window.wp.data.dispatch('core/notices').createErrorNotice(
          failureMessage,
          {
            type: 'snackbar',
            isDismissible: true,
          }
        );
        return;
      }

      // Close the modal
      setIsModalOpen(false);
      setPendingSuggestion(null);
      setEditingAnchorText('');
      setPendingSuggestionKey('');

      if (pendingSuggestionKey) {
        setInsertedSuggestionKeys((previous) => ({
          ...previous,
          [pendingSuggestionKey]: true,
        }));
      }

      const successMessage = sprintf(
        /* translators: %s: post title */
        __('Inserted link to "%s".', 'prorank-seo'),
        pendingSuggestion.title
      );
      setQuickFeedback({
        status: 'success',
        message: successMessage,
      });

      // Show success notification
      window.wp.data.dispatch('core/notices').createSuccessNotice(
        successMessage,
        {
          type: 'snackbar',
          isDismissible: true,
        }
      );
    } catch (err) {
      setQuickFeedback({
        status: 'error',
        message: __('Failed to insert link. Please try again.', 'prorank-seo'),
      });
      // Show error notification
      window.wp.data
        .dispatch('core/notices')
        .createErrorNotice(__('Failed to insert link. Please try again.', 'prorank-seo'), {
          type: 'snackbar',
          isDismissible: true,
        });
    }
  }, [
    insertBlocks,
    pendingSuggestion,
    pendingSuggestionKey,
    editingAnchorText,
    buildAnchorRegex,
    insertLinkInHtml,
    updateBlockAttributes,
    postContent,
  ]);

  // Handle modal cancel
  const handleCancelInsert = useCallback(() => {
    setIsModalOpen(false);
    setPendingSuggestion(null);
    setEditingAnchorText('');
    setPendingSuggestionKey('');
  }, []);

  const openFullWorkflow = useCallback(() => {
    const metaboxRoot = document.getElementById('prorank-seo-metabox-root');
    if (metaboxRoot) {
      // The metabox TabPanel assigns a stable class to each tab button
      // (see MetaBoxShell tabs config), so prefer that over visible text,
      // which breaks under translation.
      let internalLinksTab = metaboxRoot.querySelector(
        '.prorank-metabox-tabs .prorank-tab-internal-links'
      );

      if (!internalLinksTab) {
        const tabCandidates = metaboxRoot.querySelectorAll('button, [role="tab"]');
        internalLinksTab = Array.from(tabCandidates).find((node) =>
          /internal\s*links/i.test(String(node.textContent || ''))
        );
      }

      if (internalLinksTab && typeof internalLinksTab.click === 'function') {
        internalLinksTab.click();
      }

      if (typeof metaboxRoot.scrollIntoView === 'function') {
        metaboxRoot.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
      return;
    }

    const adminBase = window.prorankSettings?.adminUrl || '/wp-admin/';
    const reportUrl = `${adminBase}admin.php?page=prorank-internal-linking#?tab=links-report`;
    window.open(reportUrl, '_blank', 'noopener,noreferrer');
  }, []);

  const quickSuggestions = suggestions.slice(0, maxQuickSuggestions);

  // Render loading state only before the first results arrive. During a
  // refresh the stale list stays mounted so anchor-text inputs keep focus.
  if (isLoading && !hasLoadedOnce) {
    return (
      <div className="prorank-linking-suggestions__loading">
        <Spinner />
        <p>{__('Loading suggestions…', 'prorank-seo')}</p>
      </div>
    );
  }

  // Render suggestions
  return (
    <div className="prorank-linking-suggestions">
      {error && (
        <Notice status="error" isDismissible={true} onRemove={() => setError(null)}>
          {error}
        </Notice>
      )}

      {quickFeedback?.message && (
        <Notice status={quickFeedback.status} isDismissible={true} onRemove={() => setQuickFeedback(null)}>
          {quickFeedback.message}
        </Notice>
      )}

      <div className="prorank-linking-suggestions__header">
        <p className="prorank-linking-suggestions__description">
          {__('Quick insert mode while writing. Use Full Workflow for batch actions and inbound links.', 'prorank-seo')}
        </p>
      </div>

      <div className="prorank-linking-suggestions__workflow">
        <Button
          variant="secondary"
          onClick={openFullWorkflow}
          className="prorank-linking-suggestions__workflow-button"
        >
          {__('Open Full Workflow', 'prorank-seo')}
        </Button>
        <Button
          variant="primary"
          onClick={handleScanThisPost}
          disabled={isLoading === true}
          className="prorank-linking-suggestions__scan"
          aria-label={__('Scan this post and refresh linking opportunities', 'prorank-seo')}
        >
          {__('Scan This Post', 'prorank-seo')}
        </Button>
        <Button
          variant="tertiary"
          onClick={fetchSuggestions}
          disabled={isLoading === true}
          className="prorank-linking-suggestions__refresh"
          aria-label={__('Refresh internal linking suggestions', 'prorank-seo')}
        >
          {__('Refresh', 'prorank-seo')}
        </Button>
        {isLoading && <Spinner />}
      </div>

      {quickSuggestions.length > 0 ? (
        <ul className="prorank-linking-suggestions__list">
          {quickSuggestions.map((suggestion, index) => {
            const anchorText =
              suggestion?.suggested_anchor || suggestion?.anchor_text || suggestion?.anchor || '';
            const suggestionKey = getSuggestionKey(suggestion, index);
            const anchorValue = quickAnchors[suggestionKey] ?? anchorText;
            const isInserted = Boolean(insertedSuggestionKeys[suggestionKey]);
            const relevance = normalizeRelevance(suggestion?.relevance);
            const source = suggestion?.source ? String(suggestion.source).replace(/_/g, ' ') : '';
            return (
            <li key={index} className="prorank-linking-suggestions__item">
              <Card size="small" className="prorank-linking-suggestion-card">
                <CardBody>
                  <div className="prorank-linking-suggestion__content">
                    <h4 className="prorank-linking-suggestion__title">{suggestion.title}</h4>
                    <p className="prorank-linking-suggestion__url">{suggestion.url}</p>
                    <div className="prorank-linking-suggestion__meta">
                      {typeof relevance === 'number' && (
                        <span className="prorank-linking-suggestion__badge">
                          {sprintf(__('Relevance: %d%%', 'prorank-seo'), relevance)}
                        </span>
                      )}
                      {source && (
                        <span className="prorank-linking-suggestion__badge prorank-linking-suggestion__badge--source">
                          {source}
                        </span>
                      )}
                    </div>
                    <TextControl
                      label={__('Anchor Text', 'prorank-seo')}
                      value={anchorValue}
                      onChange={(value) =>
                        setQuickAnchors((previous) => ({
                          ...previous,
                          [suggestionKey]: value,
                        }))
                      }
                      __nextHasNoMarginBottom={true}
                    />
                  </div>
                  <Button
                    variant="primary"
                    size="small"
                    onClick={() => handleInsertLink(suggestion, anchorValue, suggestionKey)}
                    disabled={!String(anchorValue || '').trim() || isInserted}
                    className="prorank-linking-suggestion__insert"
                    aria-label={sprintf(
                      /* translators: %s: post title */
                      isInserted ? __('Link inserted for %s', 'prorank-seo') : __('Insert link to %s', 'prorank-seo'),
                      suggestion.title
                    )}
                  >
                    {isInserted ? __('Inserted', 'prorank-seo') : __('Insert Link', 'prorank-seo')}
                  </Button>
                </CardBody>
              </Card>
            </li>
          );
          })}
        </ul>
      ) : (
        <div className="prorank-linking-suggestions__message">
          <p>
            {emptyStateMessage ||
              __(
                'No suggestions available. Try adding more content or ensure you have other published posts.',
                'prorank-seo'
              )}
          </p>

          {!error && inboundOpportunityCount > 0 && (
            <div className="prorank-linking-suggestions__inbound-preview">
              <strong className="prorank-linking-suggestions__inbound-title">
                {sprintf(
                  __('%d inbound opportunities are ready in Full Workflow.', 'prorank-seo'),
                  inboundOpportunityCount
                )}
              </strong>
              <ul className="prorank-linking-suggestions__inbound-list">
                {inboundOpportunities.map((item) => (
                  <li key={item.id} className="prorank-linking-suggestions__inbound-item">
                    <span className="prorank-linking-suggestions__inbound-source">
                      {item.sourceTitle || __('Untitled Source', 'prorank-seo')}
                    </span>
                    <span className="prorank-linking-suggestions__inbound-anchor">
                      {sprintf(__('Anchor: %s', 'prorank-seo'), item.anchor)}
                    </span>
                    {item.sourceUrl && (
                      <ExternalLink href={item.sourceUrl}>
                        {__('Open Source Post', 'prorank-seo')}
                      </ExternalLink>
                    )}
                  </li>
                ))}
              </ul>
            </div>
          )}
        </div>
      )}

      {/* Link density stats if available */}
      {suggestions.length > 0 && (
        <div className="prorank-linking-suggestions__stats">
          <p className="prorank-linking-suggestions__stat">
            {sprintf(
              /* translators: 1: visible suggestions, 2: total suggestions */
              __('Showing %1$d of %2$d suggestions', 'prorank-seo'),
              quickSuggestions.length,
              suggestions.length
            )}
          </p>
        </div>
      )}

      {/* Anchor text editing modal */}
      {isModalOpen && pendingSuggestion && (
        <Modal
          title={__('Edit Anchor Text', 'prorank-seo')}
          onRequestClose={handleCancelInsert}
          className="prorank-linking-suggestion__modal"
          overlayClassName="prorank-linking-suggestion__modal-overlay"
          shouldCloseOnClickOutside={true}
          shouldCloseOnEsc={true}
          aria={{
            labelledby: 'prorank-anchor-edit-modal-title',
            describedby: 'prorank-anchor-edit-modal-description',
          }}
        >
          <div
            id="prorank-anchor-edit-modal-description"
            className="prorank-linking-suggestion__modal-description"
          >
            <p>
              {sprintf(
                /* translators: %s: post title */
                __(
                  'You are about to insert a link to "%s". You can edit the anchor text below:',
                  'prorank-seo'
                ),
                pendingSuggestion.title
              )}
            </p>
          </div>

          <TextControl
            label={__('Anchor Text', 'prorank-seo')}
            value={editingAnchorText}
            onChange={setEditingAnchorText}
            help={__('This is the text that will be displayed as the link.', 'prorank-seo')}
            className="prorank-linking-suggestion__anchor-input"
          />

          <div className="prorank-linking-suggestion__modal-actions">
            <Button
              variant="tertiary"
              onClick={handleCancelInsert}
              className="prorank-linking-suggestion__cancel-button"
            >
              {__('Cancel', 'prorank-seo')}
            </Button>
            <Button
              variant="primary"
              onClick={handleConfirmInsert}
              disabled={!editingAnchorText.trim()}
              className="prorank-linking-suggestion__confirm-button"
            >
              {__('Insert Link', 'prorank-seo')}
            </Button>
          </div>
        </Modal>
      )}
    </div>
  );
};

export default LinkingSuggestionsPanel;
