import type { ReactNode } from 'react';
import { cn } from '@/lib/cn';
import { eventStyle, subtypeLabel } from '@/lib/event-styles';
import { importanceSteps, significanceBand } from '@/lib/format';
import { CONFIDENCE_LABELS, SENSOR_TIER_LABELS } from '@/lib/site';
const base = 'inline-flex items-center gap-1 whitespace-nowrap rounded-[3px] px-1.5 py-[1px] text-[11px] font-medium leading-4 tracking-wide';
/** Event type chip with its fixed hue; `sub` appends the careful subtype label. */
export function EventTypeBadge({ type, subtype, className, small = false }: { type: string; subtype?: string | null; className?: string; small?: boolean }) {
const s = eventStyle(type);
return (
{s.label}
{subtype && · {subtypeLabel(subtype)}}
);
}
const CONF: Record = {
VERIFIED: 'text-positive bg-positive-soft',
HIGH_CONFIDENCE: 'text-positive bg-positive-soft',
LIKELY: 'text-accent bg-accent-soft',
INFERRED: 'text-warning bg-warning-soft',
LOW_CONFIDENCE: 'text-danger bg-danger-soft',
};
/** Confidence label chip (spec §50). Renders nothing when missing — never invents a status. */
export function ConfidenceBadge({ label, value, className, withValue = false }: { label: string | null | undefined; value?: number | null; className?: string; withValue?: boolean }) {
if (!label) return null;
const k = label.toUpperCase();
return (
{CONFIDENCE_LABELS[k] ?? label}
{withValue && value !== null && value !== undefined && {Math.round(value * 100)} %}
);
}
/** Importance as a stepped meter (▮▮▮▯). */
export function ImportanceMeter({ importance, className }: { importance: number | null | undefined; className?: string }) {
const n = importanceSteps(importance);
const pct = importance === null || importance === undefined ? null : Math.round((importance > 1 ? importance : importance * 100));
return (
{[1, 2, 3].map((i) => (
))}
);
}
const SIG: Record = { noise: 'text-ink-3 bg-surface-2', minor: 'text-ink-2 bg-surface-2', meaningful: 'text-accent bg-accent-soft', major: 'text-warning bg-warning-soft', critical: 'text-danger bg-danger-soft' };
export function SignificanceBadge({ value, className }: { value: number | null | undefined; className?: string }) {
const band = significanceBand(value);
if (!band) return null;
return (
{band} {(value as number).toFixed(2)}
);
}
/** Sensor tier A–E (crawl cadence). */
export function SensorTierBadge({ tier, className, withLabel = false }: { tier: string | null | undefined; className?: string; withLabel?: boolean }) {
if (!tier) return null;
const t = tier.toLowerCase();
return (
{tier}
{withLabel && · {SENSOR_TIER_LABELS[tier]}}
);
}
/** Company tier 1–4 (importance band). */
export function CompanyTierBadge({ tier, className }: { tier: number | null | undefined; className?: string }) {
if (!tier) return null;
return (
T{tier}
);
}
const STATUS: Record = {
active: 'text-positive bg-positive-soft',
ok: 'text-positive bg-positive-soft',
healthy: 'text-positive bg-positive-soft',
open: 'text-positive bg-positive-soft',
listed: 'text-positive bg-positive-soft',
current: 'text-positive bg-positive-soft',
pending: 'text-accent bg-accent-soft',
running: 'text-accent bg-accent-soft',
review: 'text-warning bg-warning-soft',
paused: 'text-warning bg-warning-soft',
stale: 'text-warning bg-warning-soft',
possibly_inactive: 'text-warning bg-warning-soft',
failing: 'text-danger bg-danger-soft',
failed: 'text-danger bg-danger-soft',
dead: 'text-danger bg-danger-soft',
blocked: 'text-danger bg-danger-soft',
website_unavailable: 'text-danger bg-danger-soft',
retracted: 'text-danger bg-danger-soft',
retired: 'text-ink-3 bg-surface-2',
duplicate: 'text-ink-3 bg-surface-2',
no_longer_listed: 'text-ink-3 bg-surface-2',
removed: 'text-ink-3 bg-surface-2',
superseded: 'text-ink-3 bg-surface-2',
done: 'text-ink-2 bg-surface-2',
};
const STATUS_LABEL: Record = { no_longer_listed: 'no longer listed', removed: 'no longer listed', possibly_inactive: 'possibly inactive', website_unavailable: 'website unavailable' };
export function StatusBadge({ status, className }: { status: string | null | undefined; className?: string }) {
if (!status) return null;
const k = status.toLowerCase();
return {STATUS_LABEL[k] ?? k.replace(/_/g, ' ')};
}
/** ISO country chip (no flag emoji — consistent across platforms). */
export function CountryChip({ code, name, className, link = true }: { code: string | null | undefined; name?: string | null; className?: string; link?: boolean }) {
if (!code) return null;
const inner = (
{code.toUpperCase()}
);
if (!link) return inner;
return (
{inner}
);
}
export function Chip({ children, className, tone = 'neutral', title }: { children: ReactNode; className?: string; tone?: 'neutral' | 'accent' | 'positive' | 'warning' | 'danger' | 'outline'; title?: string }) {
return (
{children}
);
}
export function OriginBadge({ origin, model, prompt }: { origin: string | null | undefined; model?: string | null; prompt?: string | null }) {
if (!origin) return null;
const llm = origin === 'llm' || origin === 'hybrid';
return (
{origin}
{llm && model && · {model}}
);
}