"use client"; import type { LucideIcon } from "lucide-react"; import { Code2, Database, ExternalLink, File, FileText, Globe, Newspaper } from "lucide-react"; import NextImage from "next/image"; import * as React from "react"; import { openSafeNavigationHref, sanitizeHref } from "../shared/media"; import { cn, Popover, PopoverContent, PopoverTrigger } from "./_adapter"; import type { CitationType, CitationVariant, SerializableCitation } from "./schema"; const FALLBACK_LOCALE = "en-US"; const TYPE_ICONS: Record = { webpage: Globe, document: FileText, article: Newspaper, api: Database, code: Code2, other: File, }; function extractDomain(url: string): string | undefined { try { const urlObj = new URL(url); return urlObj.hostname.replace(/^www\./, ""); } catch { return undefined; } } function formatDate(isoString: string, locale: string): string { try { const date = new Date(isoString); return date.toLocaleDateString(locale, { year: "numeric", month: "short", }); } catch { return isoString; } } function useHoverPopover(delay = 100) { const [open, setOpen] = React.useState(false); const timeoutRef = React.useRef | null>(null); const handleMouseEnter = React.useCallback(() => { if (timeoutRef.current) clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => setOpen(true), delay); }, [delay]); const handleMouseLeave = React.useCallback(() => { if (timeoutRef.current) clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => setOpen(false), delay); }, [delay]); React.useEffect(() => { return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, []); return { open, setOpen, handleMouseEnter, handleMouseLeave }; } export interface CitationProps extends SerializableCitation { variant?: CitationVariant; className?: string; onNavigate?: (href: string, citation: SerializableCitation) => void; } export function Citation(props: CitationProps) { const { variant = "default", className, onNavigate, ...serializable } = props; const { id, href: rawHref, title, snippet, domain: providedDomain, favicon, author, publishedAt, type = "webpage", locale: providedLocale, } = serializable; const locale = providedLocale ?? FALLBACK_LOCALE; const sanitizedHref = sanitizeHref(rawHref); const domain = providedDomain ?? extractDomain(rawHref); const citationData: SerializableCitation = { ...serializable, href: sanitizedHref ?? rawHref, domain, locale, }; const TypeIcon = TYPE_ICONS[type] ?? Globe; const handleClick = () => { if (!sanitizedHref) return; if (onNavigate) { onNavigate(sanitizedHref, citationData); } else { openSafeNavigationHref(sanitizedHref); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (sanitizedHref && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); handleClick(); } }; const iconElement = favicon ? (