/**
 * Link Map Lite
 *
 * Read-only internal link graph for the free tier.
 */

import { __, sprintf } from '@wordpress/i18n';
import { useState, useEffect, useMemo, useRef, useCallback } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import * as d3 from 'd3';
import {
  ArrowPathIcon,
  ExclamationTriangleIcon,
  LinkIcon,
  Squares2X2Icon,
} from '@heroicons/react/24/outline';
import { Card, Button, Toggle, Select } from '../../components/ui';

const MAX_NODES = 40;

const normalizeGraphData = (rawData) => {
  const rawNodes = Array.isArray(rawData?.nodes) ? rawData.nodes : [];
  const nodes = rawNodes.map((node) => ({
    ...node,
    id: String(node.id),
    inbound_links: Number(node?.inbound_links) || 0,
    outbound_links: Number(node?.outbound_links) || 0,
    is_orphan: Boolean(node?.is_orphan || node?.orphan),
    is_missing: Boolean(node?.is_missing || node?.type === 'missing'),
    size: Number(node?.size) || 6,
  }));
  const nodeIds = new Set(nodes.map((node) => node.id));

  const rawLinks = Array.isArray(rawData?.links) ? rawData.links : [];
  const links = rawLinks
    .map((link) => ({
      ...link,
      source: String(typeof link?.source === 'object' ? link.source?.id : link?.source),
      target: String(typeof link?.target === 'object' ? link.target?.id : link?.target),
      value: Math.max(1, Number(link?.value) || 1),
      type: link?.type || 'internal',
    }))
    .filter((link) => (
      link.source &&
      link.target &&
      link.source !== link.target &&
      nodeIds.has(link.source) &&
      nodeIds.has(link.target)
    ));

  const totalNodes = nodes.length;
  const totalLinks = links.length;
  const orphanCount = nodes.filter((node) => node.is_orphan).length;
  const missingCount = nodes.filter((node) => node.is_missing).length;
  const density = totalNodes > 1 ? totalLinks / (totalNodes * (totalNodes - 1)) : 0;

  return {
    nodes,
    links,
    metrics: {
      total_nodes: totalNodes,
      total_links: totalLinks,
      orphan_count: orphanCount,
      unresolved_links: missingCount,
      link_density: density,
    },
  };
};

const getNodeTone = (node) => {
  if (node?.is_missing) {
    return '#ff8c42';
  }

  if (node?.is_orphan) {
    return '#ef4444';
  }

  if ((node?.post_type || node?.type) === 'page') {
    return '#3b82f6';
  }

  return '#10b981';
};

const VisualLinkMapLite = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [errorMessage, setErrorMessage] = useState('');
  const [statusMessage, setStatusMessage] = useState('');
  const [graphData, setGraphData] = useState(null);
  const [selectedNodeId, setSelectedNodeId] = useState('');
  const [showOrphans, setShowOrphans] = useState(true);
  const [showLinkStrength, setShowLinkStrength] = useState(true);
  const [contentType, setContentType] = useState('all');
  const canvasRef = useRef(null);
  const simulationRef = useRef(null);

  const fetchGraphData = useCallback(async () => {
    setIsLoading(true);
    setErrorMessage('');

    try {
      const params = new URLSearchParams({
        post_types: 'post,page',
        max_nodes: String(MAX_NODES),
        depth: '2',
      });

      const response = await apiFetch({
        path: `/prorank-seo/v1/linkmap/data?${params.toString()}`,
        method: 'GET',
      });

      if (!response?.success) {
        throw new Error(response?.message || __('Unable to load link map data.', 'prorank-seo'));
      }

      setGraphData(normalizeGraphData(response?.data || {}));
      setStatusMessage(response?.message || '');
    } catch (error) {
      setGraphData(null);
      setStatusMessage('');
      setErrorMessage(error?.message || __('Unable to load link map data.', 'prorank-seo'));
    } finally {
      setIsLoading(false);
    }
  }, []);

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

  const filteredGraph = useMemo(() => {
    if (!graphData) {
      return {
        nodes: [],
        links: [],
        metrics: {
          total_nodes: 0,
          total_links: 0,
          orphan_count: 0,
          unresolved_links: 0,
          link_density: 0,
        },
      };
    }

    const nodes = graphData.nodes.filter((node) => {
      if (!showOrphans && node.is_orphan) {
        return false;
      }

      if (contentType === 'all') {
        return true;
      }

      if (contentType === 'missing') {
        return node.is_missing;
      }

      return (node.post_type || node.type) === contentType;
    });

    const visibleIds = new Set(nodes.map((node) => node.id));
    const links = graphData.links.filter((link) => (
      visibleIds.has(String(link.source)) &&
      visibleIds.has(String(link.target))
    ));

    return {
      nodes,
      links,
      metrics: {
        total_nodes: nodes.length,
        total_links: links.length,
        orphan_count: nodes.filter((node) => node.is_orphan).length,
        unresolved_links: nodes.filter((node) => node.is_missing).length,
        link_density: nodes.length > 1 ? links.length / (nodes.length * (nodes.length - 1)) : 0,
      },
    };
  }, [contentType, graphData, showOrphans]);

  useEffect(() => {
    if (!selectedNodeId) {
      return;
    }

    const stillVisible = filteredGraph.nodes.some((node) => node.id === selectedNodeId);
    if (!stillVisible) {
      setSelectedNodeId('');
    }
  }, [filteredGraph.nodes, selectedNodeId]);

  const selectedNodeDetails = useMemo(() => {
    if (!selectedNodeId) {
      return null;
    }

    const selectedNode = filteredGraph.nodes.find((node) => node.id === selectedNodeId);
    if (!selectedNode) {
      return null;
    }

    const inbound = filteredGraph.links
      .filter((link) => String(link.target) === selectedNodeId)
      .map((link) => filteredGraph.nodes.find((node) => node.id === String(link.source)))
      .filter(Boolean)
      .slice(0, 6);

    const outbound = filteredGraph.links
      .filter((link) => String(link.source) === selectedNodeId)
      .map((link) => filteredGraph.nodes.find((node) => node.id === String(link.target)))
      .filter(Boolean)
      .slice(0, 6);

    return {
      node: selectedNode,
      inbound,
      outbound,
    };
  }, [filteredGraph.links, filteredGraph.nodes, selectedNodeId]);

  useEffect(() => {
    const container = canvasRef.current;
    if (!container) {
      return undefined;
    }

    if (filteredGraph.nodes.length === 0) {
      d3.select(container).selectAll('*').remove();
      simulationRef.current?.stop();
      return undefined;
    }

    const width = Math.max(container.clientWidth || 0, 640);
    const height = 560;
    const nodes = filteredGraph.nodes.map((node) => ({ ...node }));
    const links = filteredGraph.links.map((link) => ({ ...link }));

    d3.select(container).selectAll('*').remove();
    simulationRef.current?.stop();

    const svg = d3.select(container)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .attr('class', 'prorank-link-map-svg');

    const stage = svg.append('g');

    svg.call(
      d3.zoom()
        .scaleExtent([0.55, 2.5])
        .on('zoom', (event) => {
          stage.attr('transform', event.transform);
        })
    );

    const linkSelection = stage.append('g')
      .attr('class', 'prorank-link-map-svg__links')
      .selectAll('line')
      .data(links)
      .enter()
      .append('line')
      .attr('stroke', (link) => (link.type === 'internal_missing' ? 'rgba(255, 140, 66, 0.75)' : 'rgba(148, 163, 184, 0.56)'))
      .attr('stroke-dasharray', (link) => (link.type === 'internal_missing' ? '6 5' : null))
      .attr('stroke-linecap', 'round')
      .attr('stroke-width', (link) => (showLinkStrength ? Math.min(5, 1 + (Number(link.value) || 1) * 0.55) : 1.8));

    const nodeSelection = stage.append('g')
      .attr('class', 'prorank-link-map-svg__nodes')
      .selectAll('g')
      .data(nodes)
      .enter()
      .append('g')
      .attr('class', (node) => `node ${node.is_orphan ? 'orphan-node' : ''} ${selectedNodeId === node.id ? 'is-selected' : ''}`)
      .style('cursor', 'pointer')
      .on('click', (_, node) => {
        setSelectedNodeId(String(node.id));
      });

    nodeSelection.append('circle')
      .attr('r', (node) => Math.max(8, Math.min(22, Number(node.size) || 8)))
      .attr('fill', (node) => getNodeTone(node))
      .attr('stroke', (node) => (selectedNodeId === node.id ? '#f8fafc' : '#0f172a'))
      .attr('stroke-width', (node) => (selectedNodeId === node.id ? 3 : 1.5))
      .attr('opacity', 0.94);

    nodeSelection.append('title')
      .text((node) => `${node.title || node.label || node.id}\n${node.url || ''}`);

    if (nodes.length <= 18) {
      nodeSelection.append('text')
        .attr('class', 'prorank-link-map-svg__label')
        .attr('x', 0)
        .attr('y', (node) => -1 * (Math.max(8, Math.min(22, Number(node.size) || 8)) + 10))
        .attr('text-anchor', 'middle')
        .text((node) => {
          const label = node.title || node.label || '';
          return label.length > 26 ? `${label.slice(0, 23)}...` : label;
        });
    }

    let simulation;

    const drag = d3.drag()
      .on('start', (event, node) => {
        if (!event.active) {
          simulation.alphaTarget(0.3).restart();
        }
        node.fx = node.x;
        node.fy = node.y;
      })
      .on('drag', (event, node) => {
        node.fx = event.x;
        node.fy = event.y;
      })
      .on('end', (event, node) => {
        if (!event.active) {
          simulation.alphaTarget(0);
        }
        node.fx = null;
        node.fy = null;
      });

    nodeSelection.call(drag);

    simulation = d3.forceSimulation(nodes)
      .force('link', d3.forceLink(links).id((node) => node.id).distance((link) => (
        link.type === 'internal_missing' ? 150 : 110
      )))
      .force('charge', d3.forceManyBody().strength((node) => (node.is_missing ? -220 : -320)))
      .force('center', d3.forceCenter(width / 2, height / 2))
      .force('collision', d3.forceCollide().radius((node) => Math.max(20, (Number(node.size) || 8) + 8)))
      .force('x', d3.forceX(width / 2).strength(0.03))
      .force('y', d3.forceY(height / 2).strength(0.03));

    simulation.on('tick', () => {
      linkSelection
        .attr('x1', (link) => link.source.x)
        .attr('y1', (link) => link.source.y)
        .attr('x2', (link) => link.target.x)
        .attr('y2', (link) => link.target.y);

      nodeSelection.attr('transform', (node) => `translate(${node.x}, ${node.y})`);
    });

    simulationRef.current = simulation;

    return () => {
      simulation.stop();
    };
  }, [filteredGraph.links, filteredGraph.nodes, selectedNodeId, showLinkStrength]);

  const openTab = (tabName) => {
    window.location.hash = `#?tab=${tabName}`;
  };

  const summaryCards = [
    {
      key: 'nodes',
      tone: 'posts',
      label: __('Nodes in map', 'prorank-seo'),
      value: filteredGraph.metrics.total_nodes,
    },
    {
      key: 'links',
      tone: 'internal',
      label: __('Internal paths', 'prorank-seo'),
      value: filteredGraph.metrics.total_links,
    },
    {
      key: 'orphans',
      tone: 'orphan',
      label: __('Orphan pages', 'prorank-seo'),
      value: filteredGraph.metrics.orphan_count,
    },
    {
      key: 'missing',
      tone: 'broken',
      label: __('Missing targets', 'prorank-seo'),
      value: filteredGraph.metrics.unresolved_links,
    },
  ];

  return (
    <div className="prorank-visual-link-map-modern prorank-il-map-lite">
      <div className="prorank-il-hero prorank-il-map-lite__hero">
        <div>
          <span className="prorank-il-hero__eyebrow">
            {__('Link Map Lite', 'prorank-seo')}
          </span>
          <h2 className="prorank-il-hero__title">
            {__('Visualise how your content connects', 'prorank-seo')}
          </h2>
          <p className="prorank-il-hero__meta">
            {sprintf(
              __('Free shows a read-only graph of your top %d posts and pages so orphan pages, missing targets, and link clusters are obvious at a glance.', 'prorank-seo'),
              MAX_NODES
            )}
          </p>
        </div>

        <div className="prorank-il-hero__actions">
          <Button
            variant="secondary"
            className="prorank-il-hero-button"
            onClick={() => openTab('links-report')}
          >
            {__('Open Links Report', 'prorank-seo')}
          </Button>
          <Button
            variant="secondary"
            className="prorank-il-hero-button is-primary"
            onClick={fetchGraphData}
            loading={isLoading}
            icon={ArrowPathIcon}
          >
            {__('Refresh Map', 'prorank-seo')}
          </Button>
        </div>
      </div>

      {errorMessage ? (
        <div className="prorank-il-map-lite__notice is-error">
          <ExclamationTriangleIcon className="prorank-il-map-lite__notice-icon" />
          <div>
            <strong>{__('Link map unavailable', 'prorank-seo')}</strong>
            <p>{errorMessage}</p>
          </div>
        </div>
      ) : null}

      {!errorMessage && statusMessage && filteredGraph.nodes.length === 0 ? (
        <div className="prorank-il-map-lite__notice">
          <Squares2X2Icon className="prorank-il-map-lite__notice-icon" />
          <div>
            <strong>{__('Link map is currently empty', 'prorank-seo')}</strong>
            <p>{statusMessage}</p>
          </div>
        </div>
      ) : null}

      <div className="prorank-il-kpi-grid prorank-il-kpi-grid--compact">
        {summaryCards.map((card) => (
          <Card key={card.key} className="prorank-il-kpi-card" data-tone={card.tone}>
            <div>
              <span className="prorank-il-kpi-label">{card.label}</span>
              <strong className="prorank-il-kpi-value">{card.value}</strong>
            </div>
          </Card>
        ))}
      </div>

      <div className="prorank-map-layout prorank-il-map-lite__layout">
        <div className="prorank-map-sidebar">
          <Card className="prorank-il-panel prorank-il-map-lite__card">
            <div className="prorank-il-map-lite__card-head">
              <h3>{__('Display controls', 'prorank-seo')}</h3>
              <span>{__('Read-only', 'prorank-seo')}</span>
            </div>

            <div className="prorank-il-map-lite__controls">
              <Select
                label={__('Content type', 'prorank-seo')}
                value={contentType}
                onChange={setContentType}
                options={[
                  { label: __('All content', 'prorank-seo'), value: 'all' },
                  { label: __('Posts only', 'prorank-seo'), value: 'post' },
                  { label: __('Pages only', 'prorank-seo'), value: 'page' },
                  { label: __('Missing targets', 'prorank-seo'), value: 'missing' },
                ]}
              />

              <Toggle
                label={__('Show orphan pages', 'prorank-seo')}
                description={__('Keep nodes with zero inbound internal links visible in the graph.', 'prorank-seo')}
                checked={showOrphans}
                onChange={setShowOrphans}
              />

              <Toggle
                label={__('Show link strength', 'prorank-seo')}
                description={__('Use thicker lines when a source page links to the same target more than once.', 'prorank-seo')}
                checked={showLinkStrength}
                onChange={setShowLinkStrength}
              />
            </div>

            <div className="prorank-il-map-lite__callout">
              <strong>{__('Free scope', 'prorank-seo')}</strong>
              <p>{__('Top 40 nodes, force layout, and read-only controls for focused internal-link review.', 'prorank-seo')}</p>
            </div>
          </Card>

          <Card className="prorank-il-panel prorank-il-map-lite__card">
            <div className="prorank-il-map-lite__card-head">
              <h3>{__('Legend', 'prorank-seo')}</h3>
            </div>

            <div className="prorank-legend-items prorank-il-map-lite__legend">
              <div className="prorank-il-map-lite__legend-row">
                <span className="prorank-legend-dot prorank-il-map-lite__legend-dot is-page" />
                <span>{__('Page', 'prorank-seo')}</span>
              </div>
              <div className="prorank-il-map-lite__legend-row">
                <span className="prorank-legend-dot prorank-il-map-lite__legend-dot is-post" />
                <span>{__('Post', 'prorank-seo')}</span>
              </div>
              <div className="prorank-il-map-lite__legend-row">
                <span className="prorank-legend-dot prorank-orphan-dot prorank-il-map-lite__legend-dot is-orphan" />
                <span>{__('Orphan page', 'prorank-seo')}</span>
              </div>
              <div className="prorank-il-map-lite__legend-row">
                <span className="prorank-legend-dot prorank-il-map-lite__legend-dot is-missing" />
                <span>{__('Missing target', 'prorank-seo')}</span>
              </div>
            </div>
          </Card>

          <Card className="prorank-il-panel prorank-il-map-lite__card">
            <div className="prorank-il-map-lite__card-head">
              <h3>{__('Selected node', 'prorank-seo')}</h3>
            </div>

            {selectedNodeDetails ? (
              <div className="prorank-il-map-lite__node-inspector">
                <div className="prorank-il-map-lite__node-meta">
                  <span className="prorank-il-map-lite__node-type">
                    {selectedNodeDetails.node.is_missing
                      ? __('Missing target', 'prorank-seo')
                      : (selectedNodeDetails.node.post_type || selectedNodeDetails.node.type || __('Content', 'prorank-seo'))}
                  </span>
                  <strong>{selectedNodeDetails.node.title || selectedNodeDetails.node.label}</strong>
                  {selectedNodeDetails.node.url ? (
                    <a href={selectedNodeDetails.node.url} target="_blank" rel="noreferrer">
                      {selectedNodeDetails.node.url}
                    </a>
                  ) : null}
                </div>

                <div className="prorank-il-map-lite__node-stats">
                  <div>
                    <span>{__('Inbound', 'prorank-seo')}</span>
                    <strong>{selectedNodeDetails.node.inbound_links}</strong>
                  </div>
                  <div>
                    <span>{__('Outbound', 'prorank-seo')}</span>
                    <strong>{selectedNodeDetails.node.outbound_links}</strong>
                  </div>
                </div>

                <div className="prorank-il-map-lite__node-list">
                  <span>{__('Incoming from', 'prorank-seo')}</span>
                  {selectedNodeDetails.inbound.length > 0 ? (
                    <ul>
                      {selectedNodeDetails.inbound.map((node) => (
                        <li key={`in-${node.id}`}>{node.title || node.label}</li>
                      ))}
                    </ul>
                  ) : (
                    <p>{__('No inbound pages in the current view.', 'prorank-seo')}</p>
                  )}
                </div>

                <div className="prorank-il-map-lite__node-list">
                  <span>{__('Links out to', 'prorank-seo')}</span>
                  {selectedNodeDetails.outbound.length > 0 ? (
                    <ul>
                      {selectedNodeDetails.outbound.map((node) => (
                        <li key={`out-${node.id}`}>{node.title || node.label}</li>
                      ))}
                    </ul>
                  ) : (
                    <p>{__('No outbound pages in the current view.', 'prorank-seo')}</p>
                  )}
                </div>
              </div>
            ) : (
              <div className="prorank-il-map-lite__empty-inspector">
                <LinkIcon className="prorank-il-map-lite__empty-icon" />
                <p>{__('Click a node in the map to inspect its inbound and outbound connections.', 'prorank-seo')}</p>
              </div>
            )}
          </Card>
        </div>

        <div className="prorank-map-main">
          <div className="prorank-il-progress-card prorank-il-map-lite__stage">
            <div>
              <div className="prorank-il-map-lite__stage-head">
                <div>
                  <h3 className="prorank-il-progress-title">{__('Link graph', 'prorank-seo')}</h3>
                  <p className="prorank-il-progress-copy">
                    {__('Drag nodes to inspect clusters. Dashed orange lines point to missing internal targets.', 'prorank-seo')}
                  </p>
                </div>
                <span className="prorank-il-map-lite__density">
                  {sprintf(
                    __('Density %s%%', 'prorank-seo'),
                    (filteredGraph.metrics.link_density * 100).toFixed(1)
                  )}
                </span>
              </div>

              {isLoading ? (
                <div className="prorank-il-map-lite__state">
                  <div className="prorank-il-map-lite__spinner" />
                  <p>{__('Loading link map…', 'prorank-seo')}</p>
                </div>
              ) : null}

              {!isLoading && filteredGraph.nodes.length === 0 ? (
                <div className="prorank-il-map-lite__state">
                  <Squares2X2Icon className="prorank-il-map-lite__state-icon" />
                  <p>{__('No nodes are available for the current filters. Reset the filters or run a link scan to refresh the surrounding reports.', 'prorank-seo')}</p>
                  <div className="prorank-il-map-lite__state-actions">
                    <Button
                      variant="secondary"
                      onClick={() => {
                        setContentType('all');
                        setShowOrphans(true);
                      }}
                    >
                      {__('Reset Filters', 'prorank-seo')}
                    </Button>
                    <Button
                      variant="secondary"
                      onClick={() => openTab('links-report')}
                    >
                      {__('Open Links Report', 'prorank-seo')}
                    </Button>
                  </div>
                </div>
              ) : null}

              <div
                ref={canvasRef}
                className={`prorank-visualization-container prorank-il-map-lite__canvas ${isLoading || filteredGraph.nodes.length === 0 ? 'is-hidden' : ''}`}
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default VisualLinkMapLite;
