/**
 * Internal Links Tab Content
 *
 * Adds post-level internal linking workflow in the SEO metabox.
 *
 * @package
 */

import { __, sprintf } from '@wordpress/i18n';
import { useState, useEffect, useCallback } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
import { Button, Card, CardBody, ExternalLink, Notice, Spinner, TextControl } from '@wordpress/components';

const decodeHtmlEntities = (value) => {
  if (typeof document === 'undefined') {
    return 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 sanitizeUrl = (rawUrl) => {
  const url = String(rawUrl || '')
    .trim()
    .replace(/"/g, '&quot;')
    .replace(/javascript:/gi, '')
    .replace(/data:/gi, '')
    .replace(/vbscript:/gi, '');

  if (!url) {
    return '';
  }

  if (url.startsWith('/') || url.startsWith('http://') || url.startsWith('https://')) {
    return url;
  }

  return '';
};

const buildAnchorRegex = (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 insertLinkInHtml = (html, anchorText, targetUrl) => {
  if (!html || !anchorText || !targetUrl) {
    return { inserted: false, html };
  }

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

  if (typeof DOMParser === 'undefined' || typeof window === 'undefined' || !window.NodeFilter) {
    const match = matcher.exec(html);
    if (!match) {
      return { inserted: false, html };
    }

    const before = html.slice(0, match.index);
    const after = html.slice(match.index + match[0].length);
    return {
      inserted: true,
      html: `${before}<a href="${targetUrl}">${match[0]}</a>${after}`,
    };
  }

  const parser = new DOMParser();
  const documentNode = parser.parseFromString(`<div>${html}</div>`, 'text/html');
  const container = documentNode.body?.firstChild;

  if (!container) {
    return { inserted: false, html };
  }

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

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

  if (textNodes.length) {
    let fullText = '';
    const positionMap = [];

    for (const node of textNodes) {
      const start = fullText.length;
      const value = node.nodeValue || '';
      fullText += value;
      positionMap.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 positionMap) {
        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 = documentNode.createRange();
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);

        const fragment = range.cloneContents();
        if (!fragment.querySelector || !fragment.querySelector('a')) {
          const linkNode = documentNode.createElement('a');
          linkNode.setAttribute('href', targetUrl);
          const contents = range.extractContents();
          linkNode.appendChild(contents);
          range.insertNode(linkNode);

          return {
            inserted: true,
            html: container.innerHTML,
          };
        }
      }
    }
  }

  for (const node of textNodes) {
    const textValue = node.nodeValue || '';
    const match = matcher.exec(textValue);
    if (!match) {
      continue;
    }

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

    const fragment = documentNode.createDocumentFragment();
    if (before) {
      fragment.appendChild(documentNode.createTextNode(before));
    }

    const linkNode = documentNode.createElement('a');
    linkNode.setAttribute('href', targetUrl);
    linkNode.textContent = match[0];
    fragment.appendChild(linkNode);

    if (after) {
      fragment.appendChild(documentNode.createTextNode(after));
    }

    node.parentNode.replaceChild(fragment, node);
    return {
      inserted: true,
      html: container.innerHTML,
    };
  }

  return { inserted: false, html };
};

const normalizeOutboundSuggestions = (items) =>
  (Array.isArray(items) ? items : [])
    .map((item, index) => {
      const url = sanitizeUrl(item?.url || item?.target_url || '');
      const anchor = String(item?.suggested_anchor || item?.anchor_text || item?.anchor || '').trim();

      return {
        id: String(item?.id || `outbound-${index}-${url}`),
        title: item?.title || item?.target_title || __('Untitled Post', 'prorank-seo'),
        url,
        anchor,
        relevance: typeof item?.relevance === 'number' ? item.relevance : item?.final_score || null,
      };
    })
    .filter((item) => item.url && item.anchor);

const normalizeInboundOpportunities = (payload, currentPostId) => {
  const root = payload?.data || payload || {};
  const items = Array.isArray(root.opportunities) ? root.opportunities : [];
  const normalized = [];

  items.forEach((item, index) => {
    const hasPairShape = item?.source_id && item?.target_id;
    if (hasPairShape) {
      if (Number(item.target_id) !== Number(currentPostId)) {
        return;
      }

      normalized.push({
        id: String(item.id || `inbound-${index}`),
        sourceId: Number(item.source_id),
        sourceTitle: item.source_title || __('Untitled Source', 'prorank-seo'),
        sourceUrl: sanitizeUrl(item.source_url || ''),
        targetId: Number(item.target_id),
        targetTitle: item.target_title || '',
        anchor: String(item.anchor_text || item.target_title || '').trim(),
        relevance: item.relevance_score || null,
        context: item.context || '',
      });
      return;
    }

    // Legacy shape: one row per target page with potential sources.
    if (Number(item?.id) === Number(currentPostId) && Array.isArray(item?.potentialSources)) {
      item.potentialSources.forEach((source, sourceIndex) => {
        normalized.push({
          id: `inbound-legacy-${item.id}-${source.id}-${sourceIndex}`,
          sourceId: Number(source.id),
          sourceTitle: source.title || __('Untitled Source', 'prorank-seo'),
          sourceUrl: sanitizeUrl(source.url || ''),
          targetId: Number(item.id),
          targetTitle: item.title || '',
          anchor: String(item.title || '').trim(),
          relevance: item.opportunityScore || null,
          context: '',
        });
      });
    }
  });

  return normalized.filter((item) => item.sourceId > 0 && item.targetId > 0 && item.anchor);
};

const countInternalLinks = (content) => {
  const html = String(content || '');
  if (!html) {
    return 0;
  }

  const siteOrigin = typeof window !== 'undefined' ? window.location.origin : '';
  const linkRegex = /<a[^>]+href=(["'])([^"']+)\1[^>]*>/gi;
  let match;
  let count = 0;

  while ((match = linkRegex.exec(html)) !== null) {
    const href = String(match[2] || '').trim();
    if (!href) {
      continue;
    }
    if (href.startsWith('/') || (siteOrigin && href.startsWith(siteOrigin))) {
      count += 1;
    }
  }

  return count;
};

const normalizeComparableUrl = (rawUrl) => {
  const value = sanitizeUrl(rawUrl);
  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 normalizeComparableText = (value) =>
  decodeHtmlEntities(value)
    .toLowerCase()
    .replace(/[\-–—'"’“”.,;:!?()]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();

const buildInboundPairKey = (sourceId, targetId) =>
  `${Number(sourceId || 0)}:${Number(targetId || 0)}`;

const isSuggestionAlreadyInserted = (html, anchorText, targetUrl) => {
  const content = String(html || '');
  const normalizedTarget = normalizeComparableUrl(targetUrl);
  if (!content || !normalizedTarget) {
    return false;
  }

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

  if (typeof DOMParser === 'undefined') {
    const escapedUrl = normalizedTarget.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const hrefRegex = new RegExp(`<a[^>]+href=(["'])${escapedUrl}\\1[^>]*>([\\s\\S]*?)</a>`, 'i');
    const match = hrefRegex.exec(content);
    if (!match) {
      return false;
    }
    if (!normalizedAnchor) {
      return true;
    }
    const linkedText = decodeHtmlEntities(match[2] || '').trim().toLowerCase();
    return linkedText.includes(normalizedAnchor);
  }

  try {
    const parser = new DOMParser();
    const documentNode = parser.parseFromString(`<div>${content}</div>`, 'text/html');
    const links = documentNode.querySelectorAll('a[href]');
    for (const link of links) {
      const href = link.getAttribute('href') || '';
      if (normalizeComparableUrl(href) !== normalizedTarget) {
        continue;
      }

      if (!normalizedAnchor) {
        return true;
      }

      const linkedText = decodeHtmlEntities(link.textContent || '').trim();
      if (!linkedText) {
        continue;
      }

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

      if (anchorMatcher && anchorMatcher.test(linkedText)) {
        return true;
      }
    }
  } catch (error) {
    return false;
  }

  return false;
};

const buildInsertedOutboundState = (suggestions, content) => {
  const state = {};
  (Array.isArray(suggestions) ? suggestions : []).forEach((item) => {
    if (isSuggestionAlreadyInserted(content, item?.anchor || '', item?.url || '')) {
      state[item.id] = true;
    }
  });
  return state;
};


const removeAppliedInboundSuggestions = (suggestions, appliedSuggestion) => {
  return (Array.isArray(suggestions) ? suggestions : []).filter((item) => {
    if (String(item?.id || '') === String(appliedSuggestion?.id || '')) {
      return false;
    }

    const samePair =
      Number(item?.sourceId || 0) === Number(appliedSuggestion?.sourceId || 0) &&
      Number(item?.targetId || 0) === Number(appliedSuggestion?.targetId || 0);

    return !samePair;
  });
};

const InternalLinksTabContent = ({ postId }) => {
  const [isLoading, setIsLoading] = useState(true);
  const [isScanning, setIsScanning] = useState(false);
  const [isApplying, setIsApplying] = useState(false);
  const [notice, setNotice] = useState(null);
  const [outboundSuggestions, setOutboundSuggestions] = useState([]);
  const [inboundSuggestions, setInboundSuggestions] = useState([]);
  const [outboundAnchors, setOutboundAnchors] = useState({});
  const [inboundAnchors, setInboundAnchors] = useState({});
  const [selectedOutbound, setSelectedOutbound] = useState({});
  const [selectedInbound, setSelectedInbound] = useState({});
  const [insertedOutbound, setInsertedOutbound] = useState({});
  const [appliedInboundPairs, setAppliedInboundPairs] = useState({});
  const [outboundFeedback, setOutboundFeedback] = useState(null);
  const [inboundFeedback, setInboundFeedback] = useState(null);
  const [health, setHealth] = useState({
    inboundCount: 0,
    outboundCount: 0,
    isOrphan: true,
    lastSiteScan: null,
  });
  const [lastPostScan, setLastPostScan] = useState(null);

  const editorState = useSelect((select) => {
    const editor = select('core/editor');
    if (!editor || typeof editor.getEditedPostContent !== 'function') {
      return {
        hasStore: false,
        currentPostId: 0,
        editedContent: '',
        editedTitle: '',
      };
    }

    return {
      hasStore: true,
      currentPostId: editor.getCurrentPostId?.() || 0,
      editedContent: editor.getEditedPostContent?.() || '',
      editedTitle: editor.getEditedPostAttribute?.('title') || '',
    };
  }, []);

  const editorDispatch = useDispatch('core/editor');
  const editPost =
    editorDispatch && typeof editorDispatch.editPost === 'function' ? editorDispatch.editPost : null;
  const currentPostId = Number(postId || editorState.currentPostId || 0);

  const pushWpNotice = useCallback((status, message) => {
    const notices = window.wp?.data?.dispatch?.('core/notices');
    if (!notices || !message) {
      return;
    }

    if (status === 'success' && typeof notices.createSuccessNotice === 'function') {
      notices.createSuccessNotice(message, { type: 'snackbar', isDismissible: true });
      return;
    }

    if (status === 'error' && typeof notices.createErrorNotice === 'function') {
      notices.createErrorNotice(message, { type: 'snackbar', isDismissible: true });
      return;
    }

    if (typeof notices.createNotice === 'function') {
      notices.createNotice(status, message, { type: 'snackbar', isDismissible: true });
    }
  }, []);

  const getEditorContent = useCallback(() => {
    const storeContent = String(editorState.editedContent || '');
    if (storeContent.trim()) {
      return storeContent;
    }

    const tinyMce = window.tinymce?.get?.('content') || window.tinyMCE?.get?.('content');
    if (tinyMce && typeof tinyMce.getContent === 'function') {
      const richContent = tinyMce.getContent();
      if (String(richContent || '').trim()) {
        return String(richContent);
      }
    }

    const textarea = document.getElementById('content');
    if (textarea && typeof textarea.value === 'string') {
      return textarea.value;
    }

    return storeContent;
  }, [editorState.editedContent]);

  const hasAnalyzableContent = useCallback(() => {
    const plainText = String(getEditorContent() || '')
      .replace(/<[^>]+>/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();

    return plainText.length > 0;
  }, [getEditorContent]);

  const setEditorContent = useCallback(
    (html) => {
      let updated = false;

      if (editorState.hasStore && typeof editPost === 'function') {
        editPost({ content: html });
        updated = true;
        return updated;
      }

      const textarea = document.getElementById('content');
      if (textarea && typeof textarea.value === 'string') {
        textarea.value = html;
        textarea.dispatchEvent(new Event('input', { bubbles: true }));
        textarea.dispatchEvent(new Event('change', { bubbles: true }));
        updated = true;
      }

      const tinyMce = window.tinymce?.get?.('content') || window.tinyMCE?.get?.('content');
      if (tinyMce && typeof tinyMce.setContent === 'function') {
        tinyMce.setContent(html);
        updated = true;
      }

      return updated;
    },
    [editPost, editorState.hasStore]
  );

  const insertLinkIntoEditor = useCallback(
    (anchorText, targetUrl) => {
      const anchorTextRaw = String(anchorText || '').trim();
      const sanitizedUrl = sanitizeUrl(targetUrl);

      if (!anchorTextRaw || !sanitizedUrl) {
        return false;
      }

      const matcher = buildAnchorRegex(anchorTextRaw);
      const blockEditor = window.wp?.data?.select?.('core/block-editor');
      const blockEditorDispatch = window.wp?.data?.dispatch?.('core/block-editor') || {};
      const replaceBlock = blockEditorDispatch.replaceBlock;
      const updateBlockAttributes = blockEditorDispatch.updateBlockAttributes;
      const createBlock = window.wp?.blocks?.createBlock;

      const applyBlockUpdate = (block, key, html) => {
        try {
          if (typeof replaceBlock === 'function' && typeof createBlock === 'function') {
            const newAttributes = { ...block.attributes, [key]: html };
            const newBlock = createBlock(block.name, newAttributes, block.innerBlocks);
            replaceBlock(block.clientId, newBlock);
            return true;
          }
        } catch (error) {
          // Fall through to attribute update.
        }

        if (typeof updateBlockAttributes === 'function') {
          updateBlockAttributes(block.clientId, { [key]: html });
          return true;
        }

        return false;
      };

      const insertIntoBlock = (block) => {
        if (!block || !matcher) {
          return false;
        }

        const attributes = block.attributes || {};
        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() || [] : [];
      for (const block of blocks) {
        if (insertIntoBlock(block)) {
          return true;
        }
      }

      const currentContent = getEditorContent();
      const result = insertLinkInHtml(currentContent, anchorTextRaw, sanitizedUrl);
      if (result.inserted) {
        return setEditorContent(result.html);
      }

      return false;
    },
    [getEditorContent, setEditorContent]
  );


  const refreshInboundState = useCallback(async () => {
    if (!currentPostId) {
      return false;
    }

    const safeFetch = async (request) => {
      try {
        return { data: await apiFetch(request), error: null };
      } catch (error) {
        return { data: null, error };
      }
    };

    const [inboundResult, orphanResult, scanResult] = await Promise.all([
      safeFetch({
        path: `/prorank-seo/v1/linking/inbound-opportunities?target_post_id=${currentPostId}`,
        method: 'GET',
      }),
      safeFetch({
        path: `/prorank-seo/v1/linking/orphan-check/${currentPostId}`,
        method: 'GET',
      }),
      safeFetch({
        path: '/prorank-seo/v1/linking/scan-info',
        method: 'GET',
      }),
    ]);

    const inboundPayload = inboundResult.data || {};
    const orphanPayload = orphanResult.data?.data || orphanResult.data || {};
    const scanPayload = scanResult.data?.data || scanResult.data || {};
    const normalizedInbound = normalizeInboundOpportunities(inboundPayload, currentPostId).filter(
      (item) => !appliedInboundPairs[buildInboundPairKey(item.sourceId, item.targetId)]
    );

    setInboundSuggestions(normalizedInbound);
    setInboundAnchors((previous) => {
      const next = {};
      normalizedInbound.forEach((item) => {
        next[item.id] = String(previous[item.id] || item.anchor || '').trim();
      });
      return next;
    });
    setSelectedInbound((previous) => {
      const next = {};
      normalizedInbound.forEach((item) => {
        if (previous[item.id]) {
          next[item.id] = true;
        }
      });
      return next;
    });

    const inboundCount = Number(orphanPayload.inbound_count || 0);
    setHealth((previous) => ({
      ...previous,
      inboundCount,
      isOrphan: typeof orphanPayload.is_orphan === 'boolean' ? orphanPayload.is_orphan : inboundCount === 0,
      lastSiteScan: scanPayload.last_scan || previous.lastSiteScan,
    }));

    return !inboundResult.error && !orphanResult.error;
  }, [appliedInboundPairs, currentPostId]);

  const loadData = useCallback(async () => {
    if (!currentPostId) {
      setIsLoading(false);
      return;
    }

    setIsLoading(true);
    setNotice(null);

    const safeFetch = async (request) => {
      try {
        return { data: await apiFetch(request), error: null };
      } catch (error) {
        return { data: null, error };
      }
    };

    const [outboundResult, inboundResult, orphanResult, scanResult] = await Promise.all([
      safeFetch({
        path: `/prorank-seo/v1/linking/basic-suggestions/${currentPostId}`,
        method: 'GET',
      }),
      safeFetch({
        path: `/prorank-seo/v1/linking/inbound-opportunities?target_post_id=${currentPostId}`,
        method: 'GET',
      }),
      safeFetch({
        path: `/prorank-seo/v1/linking/orphan-check/${currentPostId}`,
        method: 'GET',
      }),
      safeFetch({
        path: '/prorank-seo/v1/linking/scan-info',
        method: 'GET',
      }),
    ]);

    const outboundPayload = outboundResult.data?.data || outboundResult.data || {};
    const inboundPayload = inboundResult.data || {};
    const orphanPayload = orphanResult.data?.data || orphanResult.data || {};
    const scanPayload = scanResult.data?.data || scanResult.data || {};

    const normalizedOutbound = normalizeOutboundSuggestions(outboundPayload.suggestions || []);
    const normalizedInbound = normalizeInboundOpportunities(inboundPayload, currentPostId).filter(
      (item) => !appliedInboundPairs[buildInboundPairKey(item.sourceId, item.targetId)]
    );

    const currentContent = getEditorContent();

    setOutboundSuggestions(normalizedOutbound);
    setInboundSuggestions(normalizedInbound);

    const outboundAnchorState = {};
    normalizedOutbound.forEach((item) => {
      outboundAnchorState[item.id] = item.anchor;
    });
    setOutboundAnchors(outboundAnchorState);

    const inboundAnchorState = {};
    normalizedInbound.forEach((item) => {
      inboundAnchorState[item.id] = item.anchor;
    });
    setInboundAnchors(inboundAnchorState);

    setSelectedOutbound({});
    setSelectedInbound({});
    setInsertedOutbound(buildInsertedOutboundState(normalizedOutbound, currentContent));

    const inboundCount = Number(orphanPayload.inbound_count || 0);
    setHealth({
      inboundCount,
      outboundCount: countInternalLinks(currentContent),
      isOrphan: typeof orphanPayload.is_orphan === 'boolean' ? orphanPayload.is_orphan : inboundCount === 0,
      lastSiteScan: scanPayload.last_scan || null,
    });

    const hasEditorContent = hasAnalyzableContent();
    const outboundErrorMessage =
      outboundResult.error?.message ||
      (!normalizedOutbound.length && !normalizedInbound.length && outboundPayload?.message
        ? outboundPayload.message
        : '');
    const inboundErrorMessage = inboundResult.error?.message || '';

    if (outboundErrorMessage || inboundErrorMessage) {
      let message = [outboundErrorMessage, inboundErrorMessage].filter(Boolean).join(' ');
      let status = 'warning';

      if (
        !outboundResult.error &&
        outboundErrorMessage === __('No content to analyze for suggestions.', 'prorank-seo')
      ) {
        if (hasEditorContent) {
          message = __('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 {
          message = null;
        }
        status = 'info';
      }

      if (message) {
        setNotice({
          status,
          message,
        });
      } else {
        setNotice(null);
      }
    }

    setIsLoading(false);
  }, [appliedInboundPairs, currentPostId, getEditorContent]);

  useEffect(() => {
    loadData();
  }, [loadData]);

  useEffect(() => {
    setHealth((previous) => ({
      ...previous,
      outboundCount: countInternalLinks(getEditorContent()),
    }));
  }, [editorState.editedContent, getEditorContent]);

  // Keep metabox inserted-state synchronized with sidebar-driven insertions.
  useEffect(() => {
    const currentContent = getEditorContent();
    const normalizedOutbound = (Array.isArray(outboundSuggestions) ? outboundSuggestions : []).map((item) => ({
      ...item,
      anchor: String(outboundAnchors[item.id] || item.anchor || '').trim(),
    }));

    setInsertedOutbound(buildInsertedOutboundState(normalizedOutbound, currentContent));
  }, [editorState.editedContent, outboundSuggestions, outboundAnchors, getEditorContent]);

  useEffect(() => {
    if (!outboundFeedback && !inboundFeedback) {
      return undefined;
    }

    const timer = setTimeout(() => {
      setOutboundFeedback(null);
      setInboundFeedback(null);
    }, 6000);

    return () => clearTimeout(timer);
  }, [outboundFeedback, inboundFeedback]);

  const insertOutboundSuggestion = useCallback(
    async (suggestion, options = {}) => {
      const { silent = false } = options;
      const anchorText = String(outboundAnchors[suggestion.id] || suggestion.anchor || '').trim();
      const targetUrl = sanitizeUrl(suggestion.url);
      const currentContent = getEditorContent();

      if (isSuggestionAlreadyInserted(currentContent, anchorText, targetUrl)) {
        setInsertedOutbound((previous) => ({
          ...previous,
          [suggestion.id]: true,
        }));

        if (!silent) {
          const message = __('This link is already inserted in the content.', 'prorank-seo');
          setNotice({ status: 'info', message });
          setOutboundFeedback({ status: 'info', message });
          pushWpNotice('info', message);
        }

        return true;
      }

      if (!anchorText || !targetUrl) {
        if (!silent) {
          const message = __('Anchor text and URL are required before inserting a link.', 'prorank-seo');
          setNotice({ status: 'error', message });
          setOutboundFeedback({ status: 'error', message });
          pushWpNotice('error', message);
        }
        return false;
      }

      if (!String(currentContent || '').trim()) {
        if (!silent) {
          const message = __('Add content first, then insert internal links.', 'prorank-seo');
          setNotice({ status: 'warning', message });
          setOutboundFeedback({ status: 'warning', message });
          pushWpNotice('warning', message);
        }
        return false;
      }

      const inserted = insertLinkIntoEditor(anchorText, targetUrl);
      if (!inserted) {
        if (!silent) {
          const message = __(
            'Anchor text was not found in editable content. Adjust the anchor text to match your post and try again.',
            'prorank-seo'
          );
          setNotice({ status: 'error', message });
          setOutboundFeedback({ status: 'error', message });
          pushWpNotice('error', message);
        }
        return false;
      }

      setInsertedOutbound((previous) => ({
        ...previous,
        [suggestion.id]: true,
      }));
      setSelectedOutbound((previous) => {
        const next = { ...previous };
        delete next[suggestion.id];
        return next;
      });

      setHealth((previous) => ({
        ...previous,
        outboundCount: previous.outboundCount + 1,
      }));

      if (!silent) {
        const message = sprintf(__('Inserted link to "%s".', 'prorank-seo'), suggestion.title);
        setNotice({ status: 'success', message });
        setOutboundFeedback({ status: 'success', message });
        pushWpNotice('success', message);
      }

      return true;
    },
    [getEditorContent, insertLinkIntoEditor, outboundAnchors, pushWpNotice]
  );


  const applyInboundSuggestion = useCallback(
    async (suggestion, options = {}) => {
      const { silent = false, sync = true } = options;
      const anchorText = String(inboundAnchors[suggestion.id] || suggestion.anchor || '').trim();

      if (!anchorText) {
        if (!silent) {
          const message = __('Anchor text is required before applying an inbound link.', 'prorank-seo');
          setNotice({ status: 'error', message });
          setInboundFeedback({ status: 'error', message });
        }
        return false;
      }

      try {
        const response = await apiFetch({
          path: '/prorank-seo/v1/linking/apply-link',
          method: 'POST',
          data: {
            source_id: suggestion.sourceId,
            target_id: suggestion.targetId,
            anchor_text: anchorText,
            context: suggestion.context || '',
          },
        });

        const success = response?.success ?? true;
        if (!success) {
          throw new Error(response?.message || __('Failed to apply inbound link.', 'prorank-seo'));
        }

        setInboundSuggestions((previous) => removeAppliedInboundSuggestions(previous, suggestion));
        setAppliedInboundPairs((previous) => ({
          ...previous,
          [buildInboundPairKey(suggestion.sourceId, suggestion.targetId)]: true,
        }));
        setInboundAnchors((previous) => {
          const next = { ...previous };
          delete next[suggestion.id];
          return next;
        });
        setSelectedInbound((previous) => {
          const next = { ...previous };
          delete next[suggestion.id];
          return next;
        });
        setHealth((previous) => ({
          ...previous,
          inboundCount: previous.inboundCount + 1,
          isOrphan: false,
        }));

        if (!silent) {
          const message = sprintf(
            __('Applied inbound link from "%s".', 'prorank-seo'),
            suggestion.sourceTitle
          );
          setNotice({ status: 'success', message });
          setInboundFeedback({ status: 'success', message });
          pushWpNotice('success', message);
        }

        if (sync) {
          await refreshInboundState();
        }

        return true;
      } catch (error) {
        if (!silent) {
          const message = error?.message || __('Failed to apply inbound link.', 'prorank-seo');
          setNotice({ status: 'error', message });
          setInboundFeedback({ status: 'error', message });
          pushWpNotice('error', message);
        }
        return false;
      }
    },
    [inboundAnchors, pushWpNotice, refreshInboundState]
  );

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

    setIsScanning(true);
    setNotice(null);

    try {
      const response = await apiFetch({
        path: '/prorank-seo/v1/linking/analyze',
        method: 'POST',
        data: {
          post_id: currentPostId,
          content: getEditorContent(),
          title: editorState.editedTitle || '',
        },
      });

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

      const payload = response?.data && typeof response.data === 'object' ? response.data : {};
      const hasSuggestionsArray = Array.isArray(payload.suggestions);
      const normalizedOutbound = normalizeOutboundSuggestions(hasSuggestionsArray ? payload.suggestions : []);
      const currentContent = getEditorContent();

      const inboundResponse = await apiFetch({
        path: `/prorank-seo/v1/linking/inbound-opportunities?target_post_id=${currentPostId}`,
        method: 'GET',
      });
      const normalizedInbound = normalizeInboundOpportunities(inboundResponse, currentPostId).filter(
        (item) => !appliedInboundPairs[buildInboundPairKey(item.sourceId, item.targetId)]
      );

      if (!hasSuggestionsArray) {
        throw new Error(__('Scan returned an invalid response. Please refresh and try again.', 'prorank-seo'));
      }

      setOutboundSuggestions(normalizedOutbound);
      setInboundSuggestions(normalizedInbound);
      const anchors = {};
      normalizedOutbound.forEach((item) => {
        anchors[item.id] = item.anchor;
      });
      setOutboundAnchors(anchors);
      const inboundAnchorState = {};
      normalizedInbound.forEach((item) => {
        inboundAnchorState[item.id] = item.anchor;
      });
      setInboundAnchors(inboundAnchorState);
      setSelectedOutbound({});
      setSelectedInbound({});
      setInsertedOutbound(buildInsertedOutboundState(normalizedOutbound, currentContent));

      setHealth((previous) => ({
        ...previous,
        outboundCount:
          typeof payload?.stats?.internal === 'number'
            ? payload.stats.internal
            : countInternalLinks(currentContent),
        inboundCount:
          typeof payload?.stats?.inbound === 'number' ? payload.stats.inbound : normalizedInbound.length,
      }));

      if (hasAnalyzableContent()) {
        const scanTimestamp = new Date().toISOString();
        setLastPostScan(scanTimestamp);
        const emptyScanMessage =
          String(payload?.message || '').trim() ||
          __('Scan complete. No outbound suggestions matched this content.', 'prorank-seo');
        const message =
          normalizedOutbound.length > 0
            ? __('Post scan complete. Suggestions refreshed.', 'prorank-seo')
            : emptyScanMessage;
        const status = normalizedOutbound.length > 0 ? 'success' : 'info';
        setNotice({ status, message });
        pushWpNotice(status, message);
      } else {
        const message = __('Add content to this post, then scan again for internal link suggestions.', 'prorank-seo');
        setNotice({ status: 'info', message });
        pushWpNotice('info', message);
      }
    } catch (error) {
      const message = error?.message || __('Failed to scan this post.', 'prorank-seo');
      setNotice({ status: 'error', message });
      pushWpNotice('error', message);
    } finally {
      setIsScanning(false);
    }
  }, [appliedInboundPairs, currentPostId, editorState.editedTitle, getEditorContent, hasAnalyzableContent, pushWpNotice]);


  const handleApplySelected = useCallback(async () => {
    const outboundToApply = outboundSuggestions.filter(
      (item) => selectedOutbound[item.id] && !insertedOutbound[item.id]
    );
    const inboundToApply = inboundSuggestions.filter((item) => selectedInbound[item.id]);
    const totalToApply = outboundToApply.length + inboundToApply.length;

    if (!totalToApply) {
      setNotice({
        status: 'info',
        message: __('Select at least one suggestion to apply.', 'prorank-seo'),
      });
      return;
    }

    setIsApplying(true);
    let successCount = 0;
    let inboundAppliedCount = 0;

    for (const suggestion of outboundToApply) {
      // eslint-disable-next-line no-await-in-loop
      const applied = await insertOutboundSuggestion(suggestion, { silent: true });
      if (applied) {
        successCount += 1;
      }
    }

    for (const suggestion of inboundToApply) {
      // eslint-disable-next-line no-await-in-loop
      const applied = await applyInboundSuggestion(suggestion, { silent: true, sync: false });
      if (applied) {
        successCount += 1;
        inboundAppliedCount += 1;
      }
    }

    if (inboundAppliedCount > 0) {
      await refreshInboundState();
    }

    const failedCount = totalToApply - successCount;
    const message =
      failedCount > 0
        ? sprintf(
            __('Applied %1$d links. %2$d could not be applied.', 'prorank-seo'),
            successCount,
            failedCount
          )
        : sprintf(__('Applied %d selected links.', 'prorank-seo'), successCount);

    setNotice({
      status: failedCount > 0 ? 'warning' : 'success',
      message,
    });
    pushWpNotice(failedCount > 0 ? 'warning' : 'success', message);
    setIsApplying(false);
  }, [
    outboundSuggestions,
    inboundSuggestions,
    selectedOutbound,
    selectedInbound,
    insertedOutbound,
    insertOutboundSuggestion,
    applyInboundSuggestion,
    pushWpNotice,
    refreshInboundState,
  ]);

  if (isLoading) {
    return (
      <div className="prorank-internal-links-loading">
        <Spinner />
        <span>{__('Loading internal links…', 'prorank-seo')}</span>
      </div>
    );
  }

  const adminBase = window.prorankSettings?.adminUrl || '/wp-admin/';
  const fullReportUrl = `${adminBase}admin.php?page=prorank-internal-linking#?tab=links-report`;
  const selectedCount =
    Object.values(selectedOutbound).filter(Boolean).length +
    Object.values(selectedInbound).filter(Boolean).length;
  const totalSuggestions = outboundSuggestions.length + inboundSuggestions.length;
  const hasRecentScan = Boolean(lastPostScan);
  const appliedAnyLinks = health.outboundCount > 0 || health.inboundCount > 0;

  const workflowSteps = [
    {
      key: 'scan',
      label: __('Scan This Post', 'prorank-seo'),
      description: __('Analyze current content and fetch fresh opportunities.', 'prorank-seo'),
      complete: hasRecentScan,
    },
    {
      key: 'review',
      label: __('Review Suggestions', 'prorank-seo'),
      description: __('Check outbound and inbound opportunities before applying.', 'prorank-seo'),
      complete: hasRecentScan && totalSuggestions > 0,
    },
    {
      key: 'apply',
      label: __('Apply Links', 'prorank-seo'),
      description: __('Apply selected links in bulk or one-by-one.', 'prorank-seo'),
      complete: appliedAnyLinks,
    },
    {
      key: 'measure',
      label: __('Measure Impact', 'prorank-seo'),
      description: __('Use full report metrics to monitor orphan risk and coverage.', 'prorank-seo'),
      complete: hasRecentScan && appliedAnyLinks,
    },
  ];

  const completedWorkflowSteps = workflowSteps.filter((step) => step.complete).length;
  const workflowProgress = Math.round((completedWorkflowSteps / workflowSteps.length) * 100);

  const currentScanLabel = lastPostScan
    ? __('Last Post Scan', 'prorank-seo')
    : __('Last Full Site Scan', 'prorank-seo');
  const currentScanValue = lastPostScan || health.lastSiteScan;

  return (
    <div className="prorank-internal-links-tab">
      {notice && (
        <Notice status={notice.status} isDismissible={true} onRemove={() => setNotice(null)}>
          {notice.message}
        </Notice>
      )}

      <div className="prorank-internal-links-workflow">
        <div className="prorank-internal-links-workflow__header">
          <h3>{__('Guided Workflow', 'prorank-seo')}</h3>
          <span>
            {completedWorkflowSteps}/{workflowSteps.length} {__('complete', 'prorank-seo')}
          </span>
        </div>
        <div className="prorank-internal-links-workflow__progress">
          <span style={{ width: `${workflowProgress}%` }} />
        </div>
        <div className="prorank-internal-links-workflow__steps">
          {workflowSteps.map((step, index) => (
            <div
              key={step.key}
              className={`prorank-internal-links-workflow__step ${
                step.complete ? 'prorank-internal-links-workflow__step--complete' : ''
              }`}
            >
              <span className="prorank-internal-links-workflow__step-index">{index + 1}</span>
              <div>
                <div className="prorank-internal-links-workflow__step-label">{step.label}</div>
                <div className="prorank-internal-links-workflow__step-description">{step.description}</div>
              </div>
            </div>
          ))}
        </div>
        <div className="prorank-internal-links-workflow__actions">
          {!hasRecentScan && (
            <Button variant="secondary" onClick={handleScanThisPost} disabled={isScanning}>
              {isScanning ? __('Scanning…', 'prorank-seo') : __('Start With Scan', 'prorank-seo')}
            </Button>
          )}
          {hasRecentScan && totalSuggestions > 0 && selectedCount === 0 && (
            <span className="prorank-internal-links-workflow__hint">
              {__('Select suggestions below, then apply in one click.', 'prorank-seo')}
            </span>
          )}
          {selectedCount > 0 && (
            <Button variant="primary" onClick={handleApplySelected} disabled={isApplying}>
              {isApplying
                ? __('Applying…', 'prorank-seo')
                : sprintf(__('Apply Selected (%d)', 'prorank-seo'), selectedCount)}
            </Button>
          )}
          <Button variant="tertiary" href={fullReportUrl} target="_blank">
            {__('Open Full Links Report', 'prorank-seo')}
          </Button>
        </div>
      </div>

      <div className="prorank-internal-links-actions">
        <Button variant="secondary" onClick={handleScanThisPost} disabled={isScanning}>
          {isScanning ? __('Scanning…', 'prorank-seo') : __('Scan This Post', 'prorank-seo')}
        </Button>
        <Button variant="primary" onClick={handleApplySelected} disabled={isApplying || selectedCount === 0}>
          {isApplying
            ? __('Applying…', 'prorank-seo')
            : sprintf(__('Apply Selected (%d)', 'prorank-seo'), selectedCount)}
        </Button>
        <Button variant="tertiary" href={fullReportUrl} target="_blank">
          {__('Open Full Links Report', 'prorank-seo')}
        </Button>
      </div>

      <div className="prorank-internal-links-health">
        <Card>
          <CardBody>
            <div className="prorank-internal-links-health__label">
              {__('Inbound Internal Links', 'prorank-seo')}
            </div>
            <div className="prorank-internal-links-health__value">{health.inboundCount}</div>
          </CardBody>
        </Card>
        <Card>
          <CardBody>
            <div className="prorank-internal-links-health__label">
              {__('Outbound Internal Links', 'prorank-seo')}
            </div>
            <div className="prorank-internal-links-health__value">{health.outboundCount}</div>
          </CardBody>
        </Card>
        <Card>
          <CardBody>
            <div className="prorank-internal-links-health__label">
              {__('Orphan Risk', 'prorank-seo')}
            </div>
            <div className="prorank-internal-links-health__value">
              {health.isOrphan ? __('High', 'prorank-seo') : __('Low', 'prorank-seo')}
            </div>
          </CardBody>
        </Card>
        <Card>
          <CardBody>
            <div className="prorank-internal-links-health__label">
              {currentScanLabel}
            </div>
            <div className="prorank-internal-links-health__value prorank-internal-links-health__value--date">
              {currentScanValue ? new Date(currentScanValue).toLocaleString() : __('Not scanned yet', 'prorank-seo')}
            </div>
          </CardBody>
        </Card>
      </div>

      <section className="prorank-internal-links-section">
        <div className="prorank-internal-links-section__header">
          <h3>{__('Suggested Outbound Links', 'prorank-seo')}</h3>
          <span>{outboundSuggestions.length}</span>
        </div>
        {outboundFeedback?.message && (
          <p
            className={`prorank-internal-links-section__feedback prorank-internal-links-section__feedback--${outboundFeedback.status}`}
            role={outboundFeedback.status === 'error' ? 'alert' : 'status'}
          >
            {outboundFeedback.message}
          </p>
        )}
        {outboundSuggestions.length === 0 ? (
          <p className="prorank-internal-links-empty">
            {hasAnalyzableContent()
              ? __('No outbound suggestions yet. Run scan to refresh.', 'prorank-seo')
              : __('Add content to this post, then run a scan to generate outbound suggestions.', 'prorank-seo')}
          </p>
        ) : (
          <div className="prorank-internal-links-list">
            {outboundSuggestions.map((item) => (
              <Card key={item.id}>
                <CardBody>
                  <div className="prorank-internal-links-item">
                    <label className="prorank-internal-links-item__select">
                      <input
                        type="checkbox"
                        checked={Boolean(selectedOutbound[item.id])}
                        onChange={(event) =>
                          setSelectedOutbound((previous) => ({
                            ...previous,
                            [item.id]: event.target.checked,
                          }))
                        }
                      />
                    </label>
                    <div className="prorank-internal-links-item__content">
                      <div className="prorank-internal-links-item__title">{item.title}</div>
                      <div className="prorank-internal-links-item__meta">
                        <ExternalLink href={item.url}>{item.url}</ExternalLink>
                      </div>
                      <TextControl
                        label={__('Anchor Text', 'prorank-seo')}
                        value={outboundAnchors[item.id] || ''}
                        onChange={(value) =>
                          setOutboundAnchors((previous) => ({
                            ...previous,
                            [item.id]: value,
                          }))
                        }
                        __nextHasNoMarginBottom={true}
                      />
                      <div className="prorank-internal-links-item__actions">
                        <Button
                          variant={insertedOutbound[item.id] ? 'secondary' : 'primary'}
                          onClick={() => insertOutboundSuggestion(item)}
                          disabled={Boolean(insertedOutbound[item.id])}
                        >
                          {insertedOutbound[item.id] ? __('Inserted', 'prorank-seo') : __('Insert Link', 'prorank-seo')}
                        </Button>
                        {typeof item.relevance === 'number' && (
                          <span className="prorank-internal-links-item__score">
                            {sprintf(
                              __('Relevance: %d%%', 'prorank-seo'),
                              Math.round(item.relevance <= 1 ? item.relevance * 100 : item.relevance)
                            )}
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                </CardBody>
              </Card>
            ))}
          </div>
        )}
      </section>

      <section className="prorank-internal-links-section">
        <div className="prorank-internal-links-section__header">
          <h3>{__('Suggested Inbound Links', 'prorank-seo')}</h3>
          <span>{inboundSuggestions.length}</span>
        </div>
        {inboundFeedback?.message && (
          <p
            className={`prorank-internal-links-section__feedback prorank-internal-links-section__feedback--${inboundFeedback.status}`}
            role={inboundFeedback.status === 'error' ? 'alert' : 'status'}
          >
            {inboundFeedback.message}
          </p>
        )}
        {inboundSuggestions.length === 0 ? (
          <p className="prorank-internal-links-empty">
            {hasAnalyzableContent()
              ? __('No inbound opportunities found for this post yet.', 'prorank-seo')
              : __('Inbound opportunities will appear after this post has content and is included in a link scan.', 'prorank-seo')}
          </p>
        ) : (
          <div className="prorank-internal-links-list">
            {inboundSuggestions.map((item) => (
              <Card key={item.id}>
                <CardBody>
                  <div className="prorank-internal-links-item">
                    <label className="prorank-internal-links-item__select">
                      <input
                        type="checkbox"
                        checked={Boolean(selectedInbound[item.id])}
                        onChange={(event) =>
                          setSelectedInbound((previous) => ({
                            ...previous,
                            [item.id]: event.target.checked,
                          }))
                        }
                      />
                    </label>
                    <div className="prorank-internal-links-item__content">
                      <div className="prorank-internal-links-item__title">{item.sourceTitle}</div>
                      <div className="prorank-internal-links-item__meta">
                        <ExternalLink href={item.sourceUrl || '#'}>{__('Open Source Post', 'prorank-seo')}</ExternalLink>
                      </div>
                      <TextControl
                        label={__('Anchor Text', 'prorank-seo')}
                        value={inboundAnchors[item.id] || ''}
                        onChange={(value) =>
                          setInboundAnchors((previous) => ({
                            ...previous,
                            [item.id]: value,
                          }))
                        }
                        __nextHasNoMarginBottom={true}
                      />
                      <div className="prorank-internal-links-item__actions">
                        <Button variant="secondary" onClick={() => applyInboundSuggestion(item)}>
                          {__('Apply Inbound Link', 'prorank-seo')}
                        </Button>
                        {typeof item.relevance === 'number' && (
                          <span className="prorank-internal-links-item__score">
                            {sprintf(
                              __('Score: %d', 'prorank-seo'),
                              Math.round(item.relevance <= 1 ? item.relevance * 100 : item.relevance)
                            )}
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                </CardBody>
              </Card>
            ))}
          </div>
        )}
      </section>
    </div>
  );
};

export default InternalLinksTabContent;
