"use client";

/**
 * Topic Research Map — Interactive SVG mind map of topic clusters.
 *
 * Radial layout: center topic → clusters → subtopics. Clicking any
 * non-center node opens the detail panel with:
 *   • Cluster / subtopic name + ranking badge
 *   • Keywords list with per-keyword rank position
 *   • Brand pages already ranking for the cluster
 *   • Competitor coverage summary
 *   • Content-gap callout when the brand has no pages for it
 *   • Six routing action buttons (Research, SEO Brief, LLMO Brief,
 *     Write, Track, Competitor Intel)
 *
 * Enrichment data comes from /api/topic-map/generate — the route
 * computes brand-page + competitor matches server-side so this page
 * doesn't fan out N extra queries on every click.
 *
 * Placeholder suggestions + suggestion chips come from the brand's
 * top GSC queries fetched on mount; falls back to a generic prompt
 * when the brand has no GSC data yet.
 */

import { useState, useMemo, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
import { useBrand } from "@/context/brand-context";
import { useBackgroundTasks } from "@/lib/stores/task-store";
import { useAiIndicators } from "@/hooks/useAiIndicators";
import { AiBadge } from "@/components/ai-badge";
import {
  Search,
  Loader2,
  Network,
  FileText,
  PenTool,
  Crosshair,
  Users,
  AlertTriangle,
  ExternalLink,
  Bot,
  Clock,
} from "lucide-react";
import FeatureGate from "@/components/plan/feature-gate";

// ─── Types ─────────────────────────────────────────────────────────────────────

interface Subtopic {
  keyword: string;
  volume: number | null;
  questions?: string[];
  position?: number | null;
  ranking?: boolean;
}
interface BrandPage { pageUrl: string; position: number; clicks: number }
interface CompetitorPage { competitorDomain: string; url: string; title: string | null }
interface Cluster {
  keyword: string;
  volume: number | null;
  subtopics: Subtopic[];
  brandPages?: BrandPage[];
  competitorPages?: CompetitorPage[];
  isContentGap?: boolean;
  rankingCount?: number;
}
interface TopicMap { center: { keyword: string; volume: number | null }; clusters: Cluster[] }
interface TopQuery { query: string; clicks: number }

type FilterMode = "all" | "ranking" | "untapped" | "gaps";
type SelectedTarget =
  | { type: "cluster"; cluster: Cluster }
  | { type: "subtopic"; subtopic: Subtopic; cluster: Cluster }
  | null;

// ─── Helpers ───────────────────────────────────────────────────────────────────

function nodeColor(keyword: string, rankedSet: Set<string>, isGap: boolean): string {
  if (rankedSet.has(keyword.toLowerCase())) return "#10B981"; // green — ranking
  if (isGap) return "#F59E0B"; // amber — content gap
  return "#94A3B8"; // gray — untapped
}

// ─── Page ──────────────────────────────────────────────────────────────────────

// Topic Map is an Agency+ feature. FeatureGate wrapper catches
// direct navigations that bypass the sidebar's locked-click handler.
export default function TopicMapPageGated() {
  return (
    <FeatureGate feature="topic_map">
      <TopicMapPage />
    </FeatureGate>
  );
}

function TopicMapPage() {
  const { brand } = useBrand();
  const router = useRouter();
  const { addTask, completeTask, failTask } = useBackgroundTasks();

  const [seed, setSeed] = useState("");
  const [map, setMap] = useState<TopicMap | null>(null);
  const [rankedKws, setRankedKws] = useState<Set<string>>(new Set());
  const [topQueries, setTopQueries] = useState<TopQuery[]>([]);
  const [loading, setLoading] = useState(false);
  const [selected, setSelected] = useState<SelectedTarget>(null);
  const [filter, setFilter] = useState<FilterMode>("all");
  const [trackingNote, setTrackingNote] = useState<string | null>(null);
  // Recent searches are persisted as `topic_map_search` SiteEvents by
  // the generate route and fetched back via GET ?recent=true. Local
  // state is keyed by brand so a brand switch resets the list.
  const [recentSearches, setRecentSearches] = useState<Array<{
    keyword: string;
    date: string;
    clusters: number;
  }>>([]);

  // Fetch brand-specific seed suggestions on mount. Uses the brand-
  // profile endpoint since it already returns topKeywords + does the
  // same specialty detection the SEO/LLMO briefs rely on.
  useEffect(() => {
    if (!brand.id) return;
    fetch(`/api/content-briefs/brand-profile?brandId=${brand.id}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (!data?.topKeywords?.length) return;
        setTopQueries(
          (data.topKeywords as Array<{ query: string; clicks: number }>).slice(0, 5),
        );
      })
      .catch(() => { /* placeholder falls back to brand name */ });
  }, [brand.id]);

  // Load recent topic-map searches for this brand. Kept as a
  // useCallback so we can refresh the list after a successful
  // generation without duplicating the fetch URL.
  const loadRecentSearches = useCallback(async () => {
    if (!brand.id) return;
    try {
      const res = await fetch(`/api/topic-map/generate?brandId=${brand.id}&recent=true`);
      if (!res.ok) return;
      const data = await res.json();
      setRecentSearches(Array.isArray(data?.recentSearches) ? data.recentSearches : []);
    } catch { /* silent — UI just hides the section */ }
  }, [brand.id]);

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

  const placeholder = useMemo(() => {
    if (topQueries.length > 0) {
      return `Enter a seed topic (e.g. ${topQueries.slice(0, 2).map((q) => q.query).join(", ")})`;
    }
    if (brand?.name) return `Enter a seed topic for ${brand.name}`;
    return "Enter a seed topic";
  }, [topQueries, brand?.name]);

  async function generate(overrideSeed?: string) {
    const target = (overrideSeed ?? seed).trim();
    if (!target || loading) return;
    if (overrideSeed) setSeed(overrideSeed);
    setLoading(true);
    setSelected(null);
    const taskId = addTask({
      id: `topic_${Date.now()}`,
      type: "topic_map",
      label: `Mapping "${target}"...`,
      brandId: brand.id,
      resultUrl: "/topic-map",
    });
    try {
      const r = await fetch("/api/topic-map/generate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ brandId: brand.id, seedTopic: target }),
      });
      if (r.ok) {
        const d = await r.json();
        setMap(d.map);
        setRankedKws(new Set((d.rankedKeywords || []).map((k: string) => k.toLowerCase())));
        if (Array.isArray(d.topQueries) && d.topQueries.length > 0) setTopQueries(d.topQueries);
        completeTask(taskId, {
          resultUrl: "/topic-map",
          resultSummary: `Mapped ${d.map?.clusters?.length || 0} clusters`,
        });
        // The POST just wrote a `topic_map_search` SiteEvent — pull
        // the fresh list so the new search shows up at the top of
        // the recent-searches pills and list.
        loadRecentSearches();
      } else {
        failTask(taskId, "Generation failed");
      }
    } catch {
      failTask(taskId, "Network error");
    }
    setLoading(false);
  }

  // ─── Filter counts (always visible in the legend) ────────────────
  const counts = useMemo(() => {
    if (!map) return { all: 0, ranking: 0, untapped: 0, gaps: 0 };
    let ranking = 0, untapped = 0, gaps = 0;
    for (const cl of map.clusters) {
      if (rankedKws.has(cl.keyword.toLowerCase())) ranking++;
      else untapped++;
      if (cl.isContentGap) gaps++;
    }
    return { all: map.clusters.length, ranking, untapped, gaps };
  }, [map, rankedKws]);

  // Whether a cluster should be dimmed by the current filter. We
  // render every node so the map shape stays stable — only opacity
  // shifts — but clicks on dimmed nodes still work.
  function dimmed(cluster: Cluster): boolean {
    if (filter === "all") return false;
    if (filter === "ranking") return !rankedKws.has(cluster.keyword.toLowerCase());
    if (filter === "untapped") return rankedKws.has(cluster.keyword.toLowerCase());
    if (filter === "gaps") return !cluster.isContentGap;
    return false;
  }

  // SVG layout calculations
  const svgData = useMemo(() => {
    if (!map) return null;
    const cx = 400, cy = 300, r1 = 140, r2 = 240;
    const clusters = map.clusters || [];
    const nodes: Array<{
      x: number; y: number; r: number; label: string; color: string;
      type: "center" | "cluster" | "subtopic";
      cluster?: Cluster;
      subtopic?: Subtopic;
      opacity: number;
    }> = [];
    const lines: Array<{ x1: number; y1: number; x2: number; y2: number; opacity: number }> = [];

    nodes.push({
      x: cx, y: cy, r: 28, label: map.center.keyword,
      color: "#EF4444", type: "center", opacity: 1,
    });

    clusters.forEach((cl, ci) => {
      const angle = (ci / clusters.length) * Math.PI * 2 - Math.PI / 2;
      const clx = cx + r1 * Math.cos(angle);
      const cly = cy + r1 * Math.sin(angle);
      const dim = dimmed(cl);
      nodes.push({
        x: clx, y: cly, r: 18, label: cl.keyword,
        color: nodeColor(cl.keyword, rankedKws, !!cl.isContentGap),
        type: "cluster", cluster: cl, opacity: dim ? 0.25 : 1,
      });
      lines.push({ x1: cx, y1: cy, x2: clx, y2: cly, opacity: dim ? 0.08 : 0.3 });

      (cl.subtopics || []).forEach((st, si) => {
        const subAngle = angle + ((si - (cl.subtopics.length - 1) / 2) * 0.35);
        const stx = cx + r2 * Math.cos(subAngle);
        const sty = cy + r2 * Math.sin(subAngle);
        nodes.push({
          x: stx, y: sty, r: 10, label: st.keyword,
          color: nodeColor(st.keyword, rankedKws, false),
          type: "subtopic", cluster: cl, subtopic: st, opacity: dim ? 0.25 : 1,
        });
        lines.push({ x1: clx, y1: cly, x2: stx, y2: sty, opacity: dim ? 0.05 : 0.15 });
      });
    });

    return { nodes, lines };
  }, [map, rankedKws, filter]);

  // ─── Routing helpers — each button hops to the right feature with
  //     the selected keyword pre-filled via query param. ────────────
  const selectedKeyword = selected
    ? selected.type === "cluster" ? selected.cluster.keyword : selected.subtopic.keyword
    : "";
  const selectedCluster = selected?.cluster;

  function goResearchKeyword() {
    if (!selectedKeyword) return;
    window.open(`/keyword-research?keyword=${encodeURIComponent(selectedKeyword)}`, "_blank");
  }
  function goSeoBrief() {
    if (!selectedKeyword) return;
    window.open(`/content-brief?keyword=${encodeURIComponent(selectedKeyword)}`, "_blank");
  }
  function goLlmoBrief() {
    if (!selectedKeyword) return;
    window.open(`/llmo-content-brief?keyword=${encodeURIComponent(selectedKeyword)}`, "_blank");
  }
  function goWrite() {
    if (!selectedKeyword) return;
    window.open(`/writing-assistant?topic=${encodeURIComponent(selectedKeyword)}`, "_blank");
  }
  function goCompetitors() {
    window.open("/competitors", "_blank");
  }
  async function trackInRankTracker() {
    if (!selectedKeyword) return;
    try {
      const res = await fetch("/api/rank-tracking/keywords", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ brandId: brand.id, keywords: [{ keyword: selectedKeyword }] }),
      });
      if (res.ok) setTrackingNote(`"${selectedKeyword}" added to Rank Tracker`);
      else setTrackingNote("Couldn't add to Rank Tracker");
    } catch { setTrackingNote("Network error"); }
    setTimeout(() => setTrackingNote(null), 2500);
  }

  // Dedup the competitor list by domain for the "Competitors covering
  // this cluster" summary: "vitalheartandvein.com — 2 pages".
  const competitorDomainCounts = useMemo(() => {
    if (!selectedCluster?.competitorPages) return [] as Array<{ domain: string; count: number }>;
    const counts = new Map<string, number>();
    for (const p of selectedCluster.competitorPages) {
      counts.set(p.competitorDomain, (counts.get(p.competitorDomain) ?? 0) + 1);
    }
    return Array.from(counts.entries())
      .map(([domain, count]) => ({ domain, count }))
      .sort((a, b) => b.count - a.count);
  }, [selectedCluster]);

  return (
    <div className="space-y-6 max-w-6xl">
      <div>
        <h1 className="text-2xl font-bold text-slate-900">Topic Research Map</h1>
        <p className="text-sm text-slate-500 mt-1">
          Visualize topic clusters and keyword relationships for {brand.name}.
        </p>
      </div>

      {/* Search bar + suggestion chips */}
      <div className="bg-white rounded-xl border border-slate-200 p-5">
        <div className="flex items-center gap-3">
          <div className="relative flex-1">
            <Search className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
            <input
              value={seed}
              onChange={(e) => setSeed(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && generate()}
              placeholder={placeholder}
              className="w-full pl-10 pr-4 py-2.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40"
            />
          </div>
          <button
            onClick={() => generate()}
            disabled={loading || !seed.trim()}
            className="flex items-center gap-2 px-5 py-2.5 bg-slate-900 text-white text-sm font-semibold rounded-lg hover:bg-slate-800 disabled:opacity-50"
          >
            {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Network className="w-4 h-4" />}
            {loading ? "Mapping..." : "Generate Map"}
          </button>
        </div>

        {topQueries.length > 0 && !seed && (
          <div className="flex flex-wrap items-center gap-2 mt-3">
            <span className="text-xs text-slate-500">Suggested topics:</span>
            {topQueries.map((q) => (
              <button
                key={q.query}
                onClick={() => generate(q.query)}
                className="text-xs px-3 py-1 bg-slate-100 hover:bg-slate-200 border border-slate-200 rounded-full text-slate-700 font-medium"
              >
                {q.query}
              </button>
            ))}
          </div>
        )}

        {/* Recent searches — pills row shown alongside the map so users
            can switch between past maps with one click. Only surfaces
            when there's more than one entry (otherwise it's redundant
            with the map that's already on screen). */}
        {map && recentSearches.length > 1 && (
          <div className="flex flex-wrap items-center gap-2 mt-3">
            <span className="inline-flex items-center gap-1 text-xs text-slate-500 py-1">
              <Clock className="w-3 h-3" /> Recent:
            </span>
            {recentSearches.slice(0, 6).map((s) => {
              const isCurrent = map.center.keyword.toLowerCase() === s.keyword.toLowerCase();
              return (
                <button
                  key={s.keyword}
                  onClick={() => generate(s.keyword)}
                  className={`text-xs px-3 py-1 rounded-full border transition-colors ${
                    isCurrent
                      ? "bg-brand-600 border-brand-600 text-white"
                      : "bg-white border-slate-200 text-slate-600 hover:border-slate-300 hover:bg-slate-50"
                  }`}
                  title={`Last mapped ${new Date(s.date).toLocaleDateString()}`}
                >
                  {s.keyword}
                </button>
              );
            })}
          </div>
        )}
      </div>

      {/* Map + detail panel */}
      {svgData && (
        <div className="flex gap-4">
          {/* SVG Map */}
          <div className="flex-1 bg-white rounded-xl border border-slate-200 p-4 overflow-hidden">
            {/* Interactive legend — clicking filters the map. */}
            <div className="flex flex-wrap items-center gap-2 mb-3 text-[11px]">
              <FilterChip active={filter === "all"} onClick={() => setFilter("all")}>
                All <span className="opacity-60">({counts.all})</span>
              </FilterChip>
              <FilterChip active={filter === "ranking"} onClick={() => setFilter("ranking")} dotColor="bg-green-500">
                Ranking <span className="opacity-60">({counts.ranking})</span>
              </FilterChip>
              <FilterChip active={filter === "untapped"} onClick={() => setFilter("untapped")} dotColor="bg-slate-400">
                Untapped <span className="opacity-60">({counts.untapped})</span>
              </FilterChip>
              <FilterChip active={filter === "gaps"} onClick={() => setFilter("gaps")} dotColor="bg-amber-500">
                Content Gaps <span className="opacity-60">({counts.gaps})</span>
              </FilterChip>
              <span className="ml-auto inline-flex items-center gap-1 text-[10px] text-slate-500">
                <span className="w-2.5 h-2.5 rounded-full bg-red-500" /> Seed Topic
              </span>
            </div>

            <svg viewBox="0 0 800 600" className="w-full h-auto">
              {svgData.lines.map((l, i) => (
                <line key={i} x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2} stroke="#CBD5E1" strokeWidth="1.5" opacity={l.opacity} />
              ))}
              {svgData.nodes.map((n, i) => (
                <g
                  key={i}
                  onClick={() => {
                    if (n.type === "center") return;
                    if (n.type === "cluster" && n.cluster) {
                      setSelected({ type: "cluster", cluster: n.cluster });
                    } else if (n.type === "subtopic" && n.subtopic && n.cluster) {
                      setSelected({ type: "subtopic", subtopic: n.subtopic, cluster: n.cluster });
                    }
                  }}
                  className={n.type !== "center" ? "cursor-pointer" : ""}
                  opacity={n.opacity}
                >
                  <circle cx={n.x} cy={n.y} r={n.r} fill={n.color} opacity={0.85} stroke="white" strokeWidth="2" />
                  <text
                    x={n.x} y={n.y + n.r + 12} textAnchor="middle" fill="#475569"
                    fontSize={n.type === "center" ? 11 : n.type === "cluster" ? 10 : 8}
                    fontWeight={n.type === "center" ? 700 : n.type === "cluster" ? 600 : 400}
                  >
                    {n.label.length > 20 ? n.label.slice(0, 18) + "…" : n.label}
                  </text>
                </g>
              ))}
            </svg>
          </div>

          {/* Detail panel */}
          <aside className="w-[320px] shrink-0 bg-white rounded-xl border border-slate-200 p-4 max-h-[640px] overflow-y-auto">
            {selected ? (
              <ClusterDetail
                selected={selected}
                rankedKws={rankedKws}
                competitorDomainCounts={competitorDomainCounts}
                trackingNote={trackingNote}
                onResearch={goResearchKeyword}
                onSeoBrief={goSeoBrief}
                onLlmoBrief={goLlmoBrief}
                onWrite={goWrite}
                onTrack={trackInRankTracker}
                onCompetitors={goCompetitors}
                brandId={brand.id}
              />
            ) : (
              <div className="text-center py-8">
                <Network className="w-8 h-8 text-slate-200 mx-auto mb-2" />
                <p className="text-xs text-slate-400">Click a node to see details</p>
              </div>
            )}
          </aside>
        </div>
      )}

      {!map && !loading && (
        <div className="space-y-4">
          <div className="bg-white rounded-xl border border-slate-200 p-12 text-center">
            <Network className="w-10 h-10 text-slate-200 mx-auto mb-3" />
            <p className="text-sm font-semibold text-slate-700">Enter a topic to generate a visual research map</p>
            <p className="text-xs text-slate-500 mt-1">AI maps topic clusters, subtopics, and questions</p>
          </div>

          {/* Recent searches — list view for the empty state. Each
              row click regenerates the map for that keyword. Hidden
              completely when the brand has never generated a map. */}
          {recentSearches.length > 0 && (
            <div className="bg-white rounded-xl border border-slate-200 p-5">
              <div className="flex items-center gap-2 mb-3">
                <Clock className="w-4 h-4 text-slate-400" />
                <h3 className="text-sm font-semibold text-slate-800">Recent Searches</h3>
                <span className="text-xs text-slate-400">({recentSearches.length})</span>
              </div>
              <div className="space-y-1.5">
                {recentSearches.map((s) => (
                  <button
                    key={s.keyword}
                    onClick={() => generate(s.keyword)}
                    className="w-full flex items-center justify-between gap-3 px-4 py-2.5 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-lg text-left transition-colors"
                  >
                    <div className="flex items-center gap-2.5 min-w-0">
                      <Search className="w-3.5 h-3.5 text-slate-400 shrink-0" />
                      <span className="text-sm font-medium text-slate-800 truncate">{s.keyword}</span>
                      {s.clusters > 0 && (
                        <span className="text-[10px] font-semibold text-slate-500 bg-white border border-slate-200 rounded px-1.5 py-0.5 shrink-0">
                          {s.clusters} {s.clusters === 1 ? "cluster" : "clusters"}
                        </span>
                      )}
                    </div>
                    <span className="text-[11px] text-slate-400 shrink-0">
                      {new Date(s.date).toLocaleDateString()}
                    </span>
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Filter chip ──────────────────────────────────────────────────────────────

function FilterChip({
  active, onClick, dotColor, children,
}: {
  active: boolean;
  onClick: () => void;
  dotColor?: string;
  children: React.ReactNode;
}) {
  return (
    <button
      onClick={onClick}
      className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border transition-colors ${
        active
          ? "bg-slate-900 text-white border-slate-900"
          : "bg-white text-slate-600 border-slate-200 hover:border-slate-300"
      }`}
    >
      {dotColor && <span className={`w-2 h-2 rounded-full ${dotColor}`} />}
      {children}
    </button>
  );
}

// ─── Detail panel ─────────────────────────────────────────────────────────────

function ClusterDetail({
  selected,
  rankedKws,
  competitorDomainCounts,
  trackingNote,
  onResearch,
  onSeoBrief,
  onLlmoBrief,
  onWrite,
  onTrack,
  onCompetitors,
  brandId,
}: {
  selected: Exclude<SelectedTarget, null>;
  rankedKws: Set<string>;
  competitorDomainCounts: Array<{ domain: string; count: number }>;
  trackingNote: string | null;
  onResearch: () => void;
  onSeoBrief: () => void;
  onLlmoBrief: () => void;
  onWrite: () => void;
  onTrack: () => void;
  onCompetitors: () => void;
  brandId: string;
}) {
  const name = selected.type === "cluster" ? selected.cluster.keyword : selected.subtopic.keyword;
  const cluster = selected.cluster;
  const isRanking = rankedKws.has(name.toLowerCase());
  const isContentGap = selected.type === "cluster" && !!cluster.isContentGap;

  const clusterKeywords = useMemo(() => {
    const kws = [cluster.keyword, ...cluster.subtopics.map((s) => s.keyword)];
    return kws.map((k) => ({ type: "keyword" as const, value: k }));
  }, [cluster]);
  const aiData = useAiIndicators(brandId, clusterKeywords);
  const aiCited = Object.values(aiData.keywords).filter((d) => d.cited).length;
  const aiTotal = Object.keys(aiData.keywords).length;
  const aiCoverage = aiTotal > 0 ? Math.round((aiCited / aiTotal) * 100) : 0;

  return (
    <div>
      <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400 mb-1">{selected.type}</p>
      <h3 className="text-sm font-bold text-slate-900 mb-2">{name}</h3>

      {/* Status badges */}
      <div className="flex flex-wrap gap-1.5 mb-3">
        {isRanking && (
          <span className="inline-block text-[10px] font-semibold px-2 py-0.5 rounded-full bg-green-50 text-green-700 border border-green-200">
            Currently Ranking
          </span>
        )}
        {selected.type === "cluster" && typeof cluster.rankingCount === "number" && cluster.rankingCount > 0 && (
          <span className="inline-block text-[10px] font-semibold px-2 py-0.5 rounded-full bg-slate-100 text-slate-600 border border-slate-200">
            {cluster.rankingCount} of {cluster.subtopics.length} ranking
          </span>
        )}
        {selected.type === "subtopic" && typeof selected.subtopic.position === "number" && (
          <span className="inline-block text-[10px] font-semibold px-2 py-0.5 rounded-full bg-slate-100 text-slate-600 border border-slate-200">
            #{selected.subtopic.position}
          </span>
        )}
      </div>

      {/* Content gap callout */}
      {isContentGap && (
        <div className="flex items-start gap-2 p-3 rounded-lg bg-amber-50 border border-amber-200 mb-3">
          <AlertTriangle className="w-4 h-4 text-amber-600 shrink-0 mt-0.5" />
          <p className="text-[11px] text-amber-900 leading-relaxed">
            <span className="font-bold">Content Gap — </span>
            you have no pages ranking for any keyword in this cluster. Build one below.
          </p>
        </div>
      )}

      {/* Keywords in cluster */}
      {selected.type === "cluster" && cluster.subtopics.length > 0 && (
        <div className="mb-3">
          <p className="text-[10px] font-bold text-slate-500 uppercase tracking-wide mb-1.5">
            Keywords in this cluster ({cluster.subtopics.length})
          </p>
          <div className="space-y-0.5 max-h-36 overflow-y-auto pr-1">
            {cluster.subtopics.map((st, i) => (
              <div
                key={`${st.keyword}-${i}`}
                className="flex items-center justify-between text-[11px] py-1 px-2 rounded hover:bg-slate-50"
              >
                <span className="text-slate-700 truncate mr-2">{st.keyword}</span>
                <span className={`text-[10px] shrink-0 ${st.ranking ? "text-emerald-600 font-semibold" : "text-slate-400"}`}>
                  {st.ranking && typeof st.position === "number" ? `#${st.position}` : "—"}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Related questions (subtopic view) */}
      {selected.type === "subtopic" && selected.subtopic.questions && selected.subtopic.questions.length > 0 && (
        <div className="mb-3">
          <p className="text-[10px] font-bold text-slate-500 uppercase tracking-wide mb-1.5">Related Questions</p>
          <ul className="space-y-1">
            {selected.subtopic.questions.map((q, i) => (
              <li key={i} className="text-[11px] text-slate-600 leading-relaxed">• {q}</li>
            ))}
          </ul>
        </div>
      )}

      {/* Brand pages */}
      {cluster.brandPages && cluster.brandPages.length > 0 && (
        <div className="mb-3">
          <p className="text-[10px] font-bold text-slate-500 uppercase tracking-wide mb-1.5">
            Your pages ranking for this cluster ({cluster.brandPages.length})
          </p>
          <ul className="space-y-1">
            {cluster.brandPages.slice(0, 5).map((p) => (
              <li key={p.pageUrl} className="flex items-center justify-between text-[11px] gap-2">
                <a
                  href={p.pageUrl} target="_blank" rel="noreferrer"
                  className="font-mono text-brand-600 truncate hover:underline flex-1"
                  title={p.pageUrl}
                >
                  {safePath(p.pageUrl)}
                </a>
                <span className="text-[10px] text-slate-500 shrink-0">#{p.position}</span>
                <ExternalLink className="w-3 h-3 text-slate-400 shrink-0" />
              </li>
            ))}
          </ul>
        </div>
      )}

      {/* Competitor coverage */}
      {competitorDomainCounts.length > 0 && (
        <div className="mb-3">
          <p className="text-[10px] font-bold text-slate-500 uppercase tracking-wide mb-1.5">
            Competitors covering this cluster
          </p>
          <ul className="space-y-1">
            {competitorDomainCounts.slice(0, 5).map((c) => (
              <li key={c.domain} className="flex items-center justify-between text-[11px]">
                <span className="text-slate-700 truncate mr-2">{c.domain}</span>
                <span className="text-[10px] text-slate-500 shrink-0">{c.count} {c.count === 1 ? "page" : "pages"}</span>
              </li>
            ))}
          </ul>
        </div>
      )}

      {/* AI Coverage */}
      {!aiData.loading && aiTotal > 0 && (
        <div className="mb-3">
          <p className="text-[10px] font-bold text-slate-500 uppercase tracking-wide mb-1.5">AI Coverage</p>
          <div className="flex items-center gap-2 mb-1.5">
            <div className="flex-1 bg-slate-100 rounded-full h-2 overflow-hidden">
              <div
                className={`h-full rounded-full ${aiCoverage >= 70 ? "bg-emerald-500" : aiCoverage >= 30 ? "bg-amber-400" : "bg-red-400"}`}
                style={{ width: `${Math.max(aiCoverage, 3)}%` }}
              />
            </div>
            <span className={`text-[10px] font-bold tabular-nums ${aiCoverage >= 70 ? "text-emerald-600" : aiCoverage >= 30 ? "text-amber-600" : "text-red-500"}`}>
              {aiCoverage}%
            </span>
          </div>
          <p className="text-[10px] text-slate-500">{aiCited} of {aiTotal} keywords cited by AI</p>
          {cluster.subtopics.length > 0 && (
            <div className="mt-1.5 space-y-0.5 max-h-20 overflow-y-auto">
              {cluster.subtopics.slice(0, 6).map((st) => {
                const d = aiData.keywords[st.keyword];
                return (
                  <div key={st.keyword} className="flex items-center justify-between text-[10px] px-1.5">
                    <span className="text-slate-600 truncate mr-1">{st.keyword}</span>
                    {d?.cited ? (
                      <AiBadge type="cited" size="sm" />
                    ) : d?.aiVisits ? (
                      <AiBadge type="traffic" count={d.aiVisits} size="sm" />
                    ) : (
                      <span className="text-slate-300">—</span>
                    )}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      )}

      {/* Actions */}
      <div className="space-y-1.5 border-t border-slate-100 pt-3">
        <ActionButton icon={<Search className="w-3 h-3" />} onClick={onResearch}>Research Keyword</ActionButton>
        <ActionButton icon={<FileText className="w-3 h-3" />} onClick={onSeoBrief}>Create SEO Brief</ActionButton>
        <ActionButton icon={<Bot className="w-3 h-3" />} onClick={onLlmoBrief} color="violet">Create LLMO Brief</ActionButton>
        <ActionButton icon={<PenTool className="w-3 h-3" />} onClick={onWrite}>Write Content</ActionButton>
        <ActionButton icon={<Crosshair className="w-3 h-3" />} onClick={onTrack} color="emerald">
          Track in Rank Tracker
        </ActionButton>
        <ActionButton icon={<Users className="w-3 h-3" />} onClick={onCompetitors}>Open Competitor Intel</ActionButton>
      </div>

      {trackingNote && (
        <p className="mt-2 text-[11px] text-emerald-600 font-medium">{trackingNote}</p>
      )}
    </div>
  );
}

function ActionButton({
  icon, onClick, color = "brand", children,
}: {
  icon: React.ReactNode;
  onClick: () => void;
  color?: "brand" | "violet" | "emerald";
  children: React.ReactNode;
}) {
  const colorClass =
    color === "violet" ? "text-violet-600 hover:text-violet-700"
    : color === "emerald" ? "text-emerald-600 hover:text-emerald-700"
    : "text-brand-600 hover:text-brand-700";
  return (
    <button
      onClick={onClick}
      className={`w-full text-left flex items-center gap-1.5 text-xs font-semibold ${colorClass} hover:underline`}
    >
      {icon} {children}
    </button>
  );
}

// Show the last path segment for a URL — fits better in the narrow
// aside than the full URL, and hover shows the full path via title.
function safePath(url: string): string {
  try {
    const u = new URL(url);
    return u.pathname || "/";
  } catch {
    return url;
  }
}
