import type { AiEvidence, AuthorityTier, ClaimScope, ConfidenceLevel, EventType, FacilityStatus, FacilityType, GeoPrecision, ProjectStage, Significance } from "@dci/core"; import type { ReactNode } from "react"; import { AI_EVIDENCE_LABEL, AUTHORITY_TIER_LABEL, CONFIDENCE_LABEL, CONFIDENCE_RANK, CONFIDENCE_SHORT, EVENT_LABEL, PRECISION_LABEL, PRECISION_SHORT, SCOPE_LABEL, SCOPE_SHORT, SIGNIFICANCE_LABEL, STAGE_LABEL, STATUS_LABEL, STATUS_SHORT, STATUS_TONE, TYPE_LABEL, eventTone, isImprecise, isSiteScopeLabel, semanticsLabel, significanceBand, statusVar } from "@/lib/labels"; import { cn } from "./cn"; import { StatusDot } from "./status-dot"; export type BadgeTone = "neutral" | "accent" | "ok" | "warn" | "danger" | "info" | "outline"; const tones: Record = { neutral: "border-hair bg-surface-2 text-ink-2", outline: "border-hair-2 bg-transparent text-ink-2", accent: "border-accent/30 bg-accent-soft text-accent-ink", ok: "border-ok/25 bg-ok/10 text-ok", warn: "border-warn/30 bg-warn/10 text-warn", danger: "border-danger/30 bg-danger/10 text-danger", info: "border-info/25 bg-info/10 text-info", }; export interface BadgeProps { tone?: BadgeTone; size?: "xs" | "sm"; className?: string; children: ReactNode; title?: string; mono?: boolean; } export function Badge({ tone = "neutral", size = "sm", className, children, title, mono }: BadgeProps) { return ( {children} ); } /** Facility status badge — dot colored by tone, exact label. */ export function StatusBadge({ status, short, size = "sm", className }: { status: FacilityStatus; short?: boolean; size?: "xs" | "sm"; className?: string }) { const tone = STATUS_TONE[status]; return ( {short ? STATUS_SHORT[status] : STATUS_LABEL[status]} ); } export function TypeBadge({ type, size = "sm", className }: { type: FacilityType; size?: "xs" | "sm"; className?: string }) { const emphasis = type === "ai" || type === "hyperscale"; return ( {TYPE_LABEL[type]} ); } /** Confidence meter: 4 dots + label. */ export function ConfidenceBadge({ level, short = true, size = "sm", className }: { level: ConfidenceLevel; short?: boolean; size?: "xs" | "sm"; className?: string }) { const rank = CONFIDENCE_RANK[level]; return ( {[0, 1, 2, 3].map((i) => ( ))} {short ? CONFIDENCE_SHORT[level] : CONFIDENCE_LABEL[level]} ); } /** Geo precision: filled square = exact/parcel/street, hollow = city/metro/approx. */ export function PrecisionBadge({ precision, short = true, size = "sm", className }: { precision: GeoPrecision; short?: boolean; size?: "xs" | "sm"; className?: string }) { const hollow = isImprecise(precision); return ( {short ? PRECISION_SHORT[precision] : PRECISION_LABEL[precision]} ); } export function EventBadge({ type, size = "sm", className }: { type: EventType; size?: "xs" | "sm"; className?: string }) { const tone = eventTone(type); const style = tone === "neutral" || tone === "accent" ? undefined : { borderColor: `color-mix(in oklab, ${statusVar(tone)} 45%, var(--hair))`, color: statusVar(tone) }; return ( {EVENT_LABEL[type]} ); } /** Small "AI" / "Hyperscale" flags for rows. */ export function FlagBadges({ isAi, isHyperscale, size = "xs" }: { isAi?: boolean; isHyperscale?: boolean; size?: "xs" | "sm" }) { if (!isAi && !isHyperscale) return null; return ( {isAi && ( AI )} {isHyperscale && ( HS )} ); } /** Estimated-value marker (≈) with tooltip title. */ export function EstimateMark({ className }: { className?: string }) { return ( ≈ ); } /* ------------------------------------------------------------------ claim-first visual language (2026-09-12) */ /** * Scope of a figure — building / facility / campus (site scopes, filled square) vs metro / country / portfolio / * company / unknown (non-site, hollow dashed square). Non-site scopes never populate a record's own MW. */ export function ScopeBadge({ scope, size = "xs", short = true, className }: { scope: ClaimScope | string | null | undefined; size?: "xs" | "sm"; short?: boolean; className?: string }) { if (!scope) return null; const s = (scope in SCOPE_LABEL ? scope : "unknown") as ClaimScope; const site = isSiteScopeLabel(s); return ( {short ? SCOPE_SHORT[s] : SCOPE_LABEL[s]} ); } /** What a MW figure means (IT capacity / total power / planned / grid connection / utility / ultimate build-out). */ export function SemanticsLabel({ semantics, className, prefix }: { semantics: string | null | undefined; className?: string; prefix?: string }) { const label = semanticsLabel(semantics); if (!label) return null; return ( {prefix && {prefix}} {label} ); } /** Event significance band (major / medium / minor) — 3 bars, accent only for major. */ export function SignificanceBadge({ value, band, size = "xs", className, showLabel = true }: { value: number; band?: Significance | null; size?: "xs" | "sm"; className?: string; showLabel?: boolean }) { const b = significanceBand(value, band); const level = b === "major" ? 3 : b === "medium" ? 2 : 1; return ( {[0, 1, 2].map((i) => ( ))} {showLabel && {SIGNIFICANCE_LABEL[b]}} ); } /** AI marker with evidence level (confirmed / likely / associated). Plain "AI" when the level is unknown. */ export function AiMark({ evidence, size = "xs", className }: { evidence?: AiEvidence | null; size?: "xs" | "sm"; className?: string }) { const lvl = evidence && evidence !== "unknown" ? evidence : null; return ( AI{lvl && lvl !== "confirmed" && {lvl}} ); } /** Project lifecycle stage chip (funnel vocabulary: Announced → Permitting → Approved → Construction → Opened). */ export function StageBadge({ stage, size = "xs", className }: { stage: ProjectStage; size?: "xs" | "sm"; className?: string }) { const tone = STATUS_TONE[stage as FacilityStatus] ?? "unknown"; return ( {STAGE_LABEL[stage]} ); } /** Authority tier letter (A–E) for THIS field — never a universal source rank. */ export function TierMark({ tier, className }: { tier: AuthorityTier; className?: string }) { return ( {tier} ); }