"use client";
import { ExternalLink, Globe } from "lucide-react";
import NextImage from "next/image";
import { Button } from "@/components/ui/button";
import { openSafeNavigationHref, sanitizeHref } from "../shared/media";
import { cn } from "./_adapter";
import { CitationHoverPopover } from "./citation-hover-popover";
import type { CitationVariant, SerializableCitation } from "./schema";
import { TYPE_ICONS } from "./type-icons";
const FALLBACK_LOCALE = "en-US";
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;
}
}
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 iconElement = favicon ? (
) : (
);
// Inline variant: compact chip with hover popover
if (variant === "inline") {
return (
{title}
{snippet}
{snippet}
)}