'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
 * Links tab — internal / external link tracking with AI-powered suggestions
 * (orphan recovery, under-linked coverage). Backed by `/seo/links/*` APIs.
 */
import { useCallback, useMemo, useState } from 'react';
import { Link2, Sparkles, AlertTriangle, CheckCircle2, Loader2, RefreshCw, ExternalLink, Bot, } from 'lucide-react';
import { toast } from 'sonner';
import { fetchLinkGraphSnapshot, fetchLinkHealthIssues, fetchLinkSuggestions, runLinkAiIndex, } from '../../lib/seo-service.js';
import { editPathForDoc } from '../../lib/collection-routes.js';
import { SectionCard, SeoLoading, SeoErrorState, SeoEmptyState, btnPrimary, btnSecondary, gradeMeta, } from '../../components/seo/primitives.js';
import { useSeoResource } from './useSeoResource.js';
function scoreFromCoverage(coverage, orphanCount, broken) {
    let score = coverage;
    score -= Math.min(30, orphanCount * 0.5);
    score -= Math.min(20, broken * 2);
    return Math.max(0, Math.min(100, Math.round(score)));
}
function ActionCard({ icon: Icon, title, description, impact, actionLabel, onAction, busy, disabled, }) {
    const impactTone = impact === 'High' ? 'text-destructive' : 'text-warning';
    return (_jsxs("div", { className: "border-border bg-card flex flex-col gap-4 rounded-xl border p-5 shadow-sm sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 gap-4", children: [_jsx("div", { className: "bg-primary/10 text-primary flex h-11 w-11 shrink-0 items-center justify-center rounded-lg", children: _jsx(Icon, { className: "h-5 w-5", "aria-hidden": true }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: title }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: description }), _jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: _jsxs("span", { className: `text-xs font-medium ${impactTone}`, children: ["Impact: ", impact] }) })] })] }), _jsxs("button", { type: "button", className: btnPrimary, onClick: onAction, disabled: disabled || busy, children: [busy ? _jsx(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }) : null, actionLabel] })] }));
}
function SuggestionList({ suggestions, loading, }) {
    if (loading) {
        return (_jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }), "Finding AI link opportunities\u2026"] }));
    }
    if (suggestions.length === 0) {
        return (_jsx("p", { className: "text-muted-foreground text-sm", children: "No suggestions yet. Run AI indexing first, or add more published content." }));
    }
    return (_jsx("ul", { className: "divide-border divide-y rounded-lg border", children: suggestions.map((s) => (_jsxs("li", { className: "px-4 py-3", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: s.sourceTitle }), _jsxs("p", { className: "text-muted-foreground mt-0.5 text-xs", children: ["Link \u201C", s.anchorText, "\u201D \u2192 ", s.sourceUrl || 'target page', ' ', _jsxs("span", { className: "text-muted-foreground/80 tabular-nums", children: ["(", Math.round(s.confidence * 100), "% match)"] })] }), s.excerpt && (_jsx("p", { className: "text-muted-foreground mt-1 line-clamp-2 text-xs", children: s.excerpt }))] }, `${s.sourceEntityId}-${s.anchorText}`))) }));
}
export function LinksTab({ onNavigate }) {
    const { data, loading, error, refetch } = useSeoResource(fetchLinkGraphSnapshot, []);
    const [indexing, setIndexing] = useState(false);
    const [healthLoading, setHealthLoading] = useState(false);
    const [healthIssues, setHealthIssues] = useState(null);
    const [selectedOrphan, setSelectedOrphan] = useState(null);
    const [suggestions, setSuggestions] = useState([]);
    const [suggestLoading, setSuggestLoading] = useState(false);
    const totalLinks = (data?.internalLinks ?? 0) + (data?.externalLinks ?? 0);
    const internalPct = totalLinks > 0 ? Math.round((data.internalLinks / totalLinks) * 100) : 0;
    const externalPct = totalLinks > 0 ? 100 - internalPct : 0;
    const healthScore = useMemo(() => data
        ? scoreFromCoverage(data.linkCoveragePercent, data.orphanCount, data.brokenLinkCount)
        : null, [data]);
    const healthGrade = healthScore !== null
        ? gradeMeta(healthScore >= 80
            ? 'good'
            : healthScore >= 60
                ? 'fair'
                : healthScore >= 40
                    ? 'poor'
                    : 'critical')
        : null;
    const handleAiIndex = useCallback(async () => {
        setIndexing(true);
        const res = await runLinkAiIndex();
        setIndexing(false);
        if (res.error) {
            toast.error(res.error);
            return;
        }
        toast.success(res.data?.message ?? 'Link index updated.');
        refetch();
    }, [refetch]);
    const handleLinkHealthScan = useCallback(async () => {
        setHealthLoading(true);
        const res = await fetchLinkHealthIssues();
        setHealthLoading(false);
        if (res.error) {
            toast.error(res.error);
            return;
        }
        setHealthIssues(res.data ?? null);
        toast.success(`Checked ${res.data?.checked ?? 0} links.`);
    }, []);
    const loadSuggestions = useCallback(async (orphan) => {
        setSelectedOrphan(orphan);
        setSuggestLoading(true);
        setSuggestions([]);
        const res = await fetchLinkSuggestions({
            targetEntityId: orphan.id,
            targetEntityType: orphan.entityType,
        });
        setSuggestLoading(false);
        if (res.error) {
            toast.error(res.error);
            return;
        }
        setSuggestions(res.data?.suggestions ?? []);
    }, []);
    const openEditor = (ref) => {
        const path = editPathForDoc({ type: ref.entityType === 'post' ? 'post' : 'page' }, ref.collection, ref.id);
        onNavigate?.(path);
    };
    if (loading)
        return _jsx(SeoLoading, { label: "Analyzing link graph\u2026" });
    if (error)
        return _jsx(SeoErrorState, { message: error, onRetry: refetch });
    if (!data)
        return _jsx(SeoEmptyState, { title: "No link data", description: "Publish content to track links." });
    return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-foreground text-lg font-medium", children: "Internal linking snapshot" }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: "Track internal and external links across published pages and posts. Use AI to recover orphaned content and improve link coverage." })] }), _jsx(SectionCard, { title: "Link distribution", description: "Published content only", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex h-3 overflow-hidden rounded-full", children: [_jsx("div", { className: "bg-primary motion-safe:transition-all", style: { width: `${internalPct}%` }, title: `Internal ${internalPct}%` }), _jsx("div", { className: "bg-success motion-safe:transition-all", style: { width: `${externalPct}%` }, title: `External ${externalPct}%` })] }), _jsxs("div", { className: "flex flex-wrap gap-6 text-sm", children: [_jsxs("span", { className: "text-foreground", children: [_jsx("span", { className: "bg-primary mr-2 inline-block h-2 w-2 rounded-full", "aria-hidden": true }), "Internal: ", data.internalLinks.toLocaleString(), " (", internalPct, "%)"] }), _jsxs("span", { className: "text-foreground", children: [_jsx("span", { className: "bg-success mr-2 inline-block h-2 w-2 rounded-full", "aria-hidden": true }), "External: ", data.externalLinks.toLocaleString(), " (", externalPct, "%)"] })] }), externalPct > 0 && externalPct < 15 && (_jsxs("p", { className: "border-info/30 bg-info/10 text-info flex items-start gap-2 rounded-lg border p-4 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), "External ratio is low. Consider citing relevant high-authority sources where appropriate."] }))] }) }), _jsxs("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: [_jsx(SectionCard, { title: "Site link health", children: healthScore !== null && healthGrade && (_jsxs("div", { children: [_jsx("span", { className: `text-3xl font-medium tabular-nums ${healthGrade.text}`, children: healthScore }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: healthGrade.label })] })) }), _jsxs(SectionCard, { title: "Link coverage", children: [_jsxs("span", { className: "text-foreground text-3xl font-medium tabular-nums", children: [data.linkCoveragePercent, "%"] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "Pages with inbound internal links" })] }), _jsxs(SectionCard, { title: "Orphaned pages", children: [_jsx("span", { className: `text-3xl font-medium tabular-nums ${data.orphanCount > 0 ? 'text-destructive' : 'text-success'}`, children: data.orphanCount }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "No inbound internal links" })] }), _jsxs(SectionCard, { title: "Broken links", children: [_jsx("span", { className: `text-3xl font-medium tabular-nums ${data.brokenLinkCount > 0 ? 'text-warning' : 'text-success'}`, children: data.brokenLinkCount }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "From latest SEO audit" })] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(ActionCard, { icon: Bot, title: "Perform basic AI scanning", description: "Index published content so AI can suggest which pages should link to each other. Re-run after publishing a significant batch of content.", impact: "High", actionLabel: indexing ? 'Indexing…' : 'Run AI index', onAction: handleAiIndex, busy: indexing }), data.aiIndexLastRunAt && (_jsxs("p", { className: "text-muted-foreground -mt-2 text-xs", children: ["Last indexed ", new Date(data.aiIndexLastRunAt).toLocaleString(), data.aiIndexReady ? ' · AI ready' : ''] })), _jsx(ActionCard, { icon: Link2, title: "Orphaned posts & pages", description: "These published URLs have no inbound internal links. Add links from related content so visitors and search engines can find them.", impact: "High", actionLabel: "Review list below", onAction: () => {
                            document.getElementById('orphan-list')?.scrollIntoView({ behavior: 'smooth' });
                        } }), _jsx(ActionCard, { icon: Sparkles, title: "Link coverage", description: "Pages with fewer than two outbound internal links. Strengthen topical clusters by linking to related content.", impact: "High", actionLabel: "Review under-linked", onAction: () => {
                            document.getElementById('under-linked-list')?.scrollIntoView({ behavior: 'smooth' });
                        } })] }), _jsx(SectionCard, { title: "Orphaned content", description: `${data.orphanCount} published item${data.orphanCount === 1 ? '' : 's'} with no inbound internal links`, children: _jsxs("div", { id: "orphan-list", className: "space-y-4", children: [data.orphaned.length === 0 ? (_jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [_jsx(CheckCircle2, { className: "text-success h-4 w-4", "aria-hidden": true }), "No orphaned pages detected."] })) : (_jsx("ul", { className: "divide-border divide-y rounded-lg border", children: data.orphaned.map((row) => (_jsxs("li", { className: "flex flex-wrap items-center justify-between gap-3 px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground truncate text-sm font-medium", children: row.title }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: row.url || row.path })] }), _jsxs("div", { className: "flex shrink-0 flex-wrap gap-2", children: [_jsx("button", { type: "button", className: btnSecondary, onClick: () => openEditor(row), children: "Edit" }), _jsxs("button", { type: "button", className: btnPrimary, onClick: () => void loadSuggestions(row), children: [_jsx(Sparkles, { className: "h-3.5 w-3.5", "aria-hidden": true }), "AI suggest"] })] })] }, row.id))) })), selectedOrphan && (_jsxs("div", { className: "border-border bg-muted/30 rounded-lg border p-4", children: [_jsxs("p", { className: "text-foreground mb-2 text-sm font-medium", children: ["AI suggestions for \u201C", selectedOrphan.title, "\u201D"] }), _jsx(SuggestionList, { suggestions: suggestions, loading: suggestLoading })] }))] }) }), _jsx(SectionCard, { title: "Under-linked pages", description: "Fewer than 2 outbound internal links", children: _jsx("div", { id: "under-linked-list", children: data.underLinked.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "All crawled pages have adequate outbound links." })) : (_jsx("ul", { className: "divide-border max-h-80 divide-y overflow-y-auto rounded-lg border", children: data.underLinked.map((row) => (_jsxs("li", { className: "flex items-center justify-between gap-3 px-4 py-3 text-sm", children: [_jsx("span", { className: "text-foreground min-w-0 truncate font-medium", children: row.title }), _jsxs("span", { className: "text-muted-foreground shrink-0 tabular-nums", children: [row.outboundInternal, " outbound"] })] }, row.id))) })) }) }), _jsx(SectionCard, { title: "Live link health scan", description: "Probe outbound URLs (rate-limited). Complements audit broken-link issues.", action: _jsxs("button", { type: "button", className: btnSecondary, onClick: () => void handleLinkHealthScan(), disabled: healthLoading, children: [healthLoading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true })) : (_jsx(RefreshCw, { className: "h-4 w-4", "aria-hidden": true })), "Scan now"] }), children: healthIssues && healthIssues.issues.length > 0 ? (_jsx("ul", { className: "divide-border max-h-64 divide-y overflow-y-auto rounded-lg border", children: healthIssues.issues.map((issue) => (_jsxs("li", { className: "px-4 py-3 text-sm", children: [_jsx("p", { className: "text-foreground font-medium", children: issue.page }), _jsxs("p", { className: "text-muted-foreground mt-0.5 flex items-center gap-1 truncate text-xs", children: [_jsx(ExternalLink, { className: "h-3 w-3 shrink-0", "aria-hidden": true }), issue.url, _jsxs("span", { className: "text-destructive ml-1", children: ["HTTP ", issue.status || 'error'] }), _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", issue.type] })] })] }, issue.id))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: healthIssues
                        ? 'No failing links in the last scan.'
                        : 'Run a scan to check live URL responses across published content.' })) }), _jsxs("p", { className: "text-muted-foreground text-xs", children: ["Crawled ", data.postsCrawled.toLocaleString(), " published documents \u00B7 Updated", ' ', new Date(data.scannedAt).toLocaleString()] })] }));
}
//# sourceMappingURL=LinksTab.js.map