import type { CoverageRow, FacilityStatus, GridConstraintDTO, PipelineBreakdown } from "@dci/core"; import { ExternalLink, Info } from "lucide-react"; import Link from "next/link"; import type { ReactNode } from "react"; import { Badge, ConfidenceBadge, EstimateMark, type BadgeTone } from "@/components/ui/badge"; import { cn } from "@/components/ui/cn"; import { CountryFlag } from "@/components/ui/country-flag"; import { fmtDate, fmtInt, fmtMw, fmtPct, splitMw } from "@/lib/format"; import { SOURCE_KIND_LABEL, STATUS_LABEL, statusVar } from "@/lib/labels"; import { routes } from "@/lib/routes"; /* ------------------------------------------------------------------------------------------ Intelligence primitives (server-safe). Shared by operator / market / country 2.0, the AI index, pulse, power, connectivity, coverage, compare and rankings. Everything here is honest by design: unknown = "—", estimates carry ≈, coverage is always shown next to a MW figure. ------------------------------------------------------------------------------------------ */ /** Inline coverage bar: figure + hairline track. Below 50 % the bar is dimmed (thin data). */ export function CoverageBar({ value, className, width = "w-[120px]", label, showPct = true }: { value: number | null | undefined; className?: string; width?: string; label?: string; showPct?: boolean }) { if (value === null || value === undefined || !Number.isFinite(value)) return —; const pct = Math.max(0, Math.min(1, value)); return ( {showPct && {fmtPct(pct)}} 0 ? 2 : 0, pct * 100)}%` }} /> ); } /** Sentence-style coverage caption: "12% of 137 facilities publish a MW figure". */ export function CoverageCaption({ coverage, facilities, what = "publish a MW figure", className }: { coverage: number | null | undefined; facilities?: number | null; what?: string; className?: string }) { if (coverage === null || coverage === undefined) return null; return (

{fmtPct(coverage)} {facilities !== null && facilities !== undefined ? ` of ${fmtInt(facilities)} facilities ` : " of facilities "} {what} — totals are lower bounds from published figures only.

); } /** MW value with unit and optional ≈ mark; "—" when unknown (never 0). */ export function MwFigure({ mw, estimate, className, unitClassName }: { mw: number | null | undefined; estimate?: boolean; className?: string; unitClassName?: string }) { const s = splitMw(mw); if (!s) return —; return ( {estimate && } {s.value} {s.unit} ); } /** Note rendered verbatim from the API (`note` / `methodology` fields) — never paraphrased. */ export function ApiNote({ note, label = "Note", className, children }: { note?: string | null; label?: string; className?: string; children?: ReactNode }) { if (!note && !children) return null; return (
{label} {note} {children}
); } /** "Published figures only" methodology line under MW aggregates. */ export function PublishedOnlyNote({ className, extra }: { className?: string; extra?: ReactNode }) { return (

Published figures only — nothing is imputed to facilities without a figure; estimates carry ≈. {extra} How capacity is counted

); } /** Honest empty state for a section whose data is not (yet) published or collected. */ export function HonestEmpty({ title, detail, className }: { title: string; detail?: ReactNode; className?: string }) { return (
{title} {detail && — {detail}}
); } /* ---------------------------------------------------------------- pipeline (facility lifecycle buckets) */ const PIPE_ROWS: Array<{ key: keyof Pick; label: string; status: FacilityStatus }> = [ { key: "operational", label: "Operational", status: "operational" }, { key: "construction", label: "Under construction", status: "under_construction" }, { key: "approved", label: "Approved / permitting", status: "approved" }, { key: "announced", label: "Announced / proposed", status: "announced" }, ]; /** * Pipeline bars: facility counts and known MW by lifecycle bucket (containment-aware, published figures only), * then project records by stage. Bars are proportional to counts; MW is printed, never drawn when missing. */ export function PipelineBars({ pipeline, className, showProjects = true }: { pipeline: PipelineBreakdown; className?: string; showProjects?: boolean }) { const rows = PIPE_ROWS.map((r) => ({ ...r, count: pipeline[r.key].count, mw: pipeline[r.key].mw })); const max = Math.max(1, ...rows.map((r) => r.count)); const projects = [...pipeline.projects].sort((a, b) => b.count - a.count); const pMax = Math.max(1, ...projects.map((p) => p.count)); const total = rows.reduce((a, r) => a + r.count, 0); return (
{total === 0 ? ( ) : (
    {rows.map((r) => (
  1. {r.label}
    0 ? 1.5 : 0, (r.count / max) * 100)}%`, background: statusVar(r.status) }} />
    {fmtInt(r.count)} {r.count === 1 ? "site" : "sites"}
    {r.mw === null ? MW — : fmtMw(r.mw)}
  2. ))}
)}
{showProjects && projects.length > 0 && (
Project records by stage
    {projects.map((p) => (
  1. {STATUS_LABEL[p.status]}
    {fmtInt(p.count)} {p.mw === null ? MW — : <>≈ {fmtMw(p.mw)}}
  2. ))}

Project records are announcements, not facilities; their MW are planned figures (≈) and are never added to operational capacity.

)}
); } /* ---------------------------------------------------------------- share list (geographic diversity, top operators…) */ export interface ShareItem { key: string; label: ReactNode; href?: string; /** primary count (facilities) */ count: number; /** 0..1 share of the total */ share: number; /** optional MW shown at the right */ mw?: number | null; iso2?: string | null; } /** Ranked list with share bars — "Equinix · 23 facilities · 16.8 %". */ export function ShareList({ items, unit = "facilities", className, max }: { items: ShareItem[]; unit?: string; className?: string; max?: number }) { if (!items.length) return ; const m = max ?? Math.max(0.01, ...items.map((i) => i.share)); return (
    {items.map((it, i) => { const row = (
    {i + 1}
    {it.iso2 && } {it.label} {fmtInt(it.count)} {it.count === 1 ? unit.replace(/ies$/, "y").replace(/(? · {fmtMw(it.mw)}}
    {fmtPct(it.share, it.share < 0.1 ? 1 : 0)}
    ); return (
  1. {it.href ? ( {row} ) : ( row )}
  2. ); })}
); } /* ---------------------------------------------------------------- grid constraints */ export const GRID_KIND_LABEL: Record = { moratorium: "Moratorium", grid_delay: "Grid delay", capacity_restriction: "Capacity restriction", load_cap: "Load cap", new_transmission: "New transmission", new_substation: "New substation", regulation: "Regulation", large_load_queue: "Large-load queue", power_agreement: "Power agreement", grid_connection: "Grid connection", grid_constraint: "Grid constraint", utility_event: "Utility event", }; const GRID_KIND_TONE: Record = { moratorium: "danger", grid_delay: "warn", capacity_restriction: "warn", load_cap: "warn", large_load_queue: "warn", new_transmission: "ok", new_substation: "ok", regulation: "info", power_agreement: "accent", grid_connection: "info", grid_constraint: "warn", utility_event: "neutral", }; export function GridKindBadge({ kind, size = "xs" }: { kind: string; size?: "xs" | "sm" }) { return ( {GRID_KIND_LABEL[kind] ?? kind.replace(/_/g, " ")} ); } /** Grid constraint / power event list — each item links to its source; the kind is a badge. */ export function GridConstraintList({ items, className, showPlace = true, emptyTitle = "No grid constraint or power event recorded", emptyDetail = "detected grid, utility and power-agreement events appear here; nothing is inferred" }: { items: GridConstraintDTO[]; className?: string; showPlace?: boolean; emptyTitle?: string; emptyDetail?: ReactNode }) { if (!items.length) return ; return ( ); } /* ---------------------------------------------------------------- coverage row → cells */ export const COVERAGE_COLUMNS: Array<{ key: keyof CoverageRow; label: string; short: string }> = [ { key: "capacityCoverage", label: "Capacity (any MW figure)", short: "Capacity" }, { key: "operatorCoverage", label: "Operator known", short: "Operator" }, { key: "preciseLocationCoverage", label: "Precise location (exact / parcel / street)", short: "Precise location" }, { key: "statusCoverage", label: "Lifecycle status known", short: "Status" }, { key: "openingDateCoverage", label: "Opening date published", short: "Opening date" }, { key: "multiSourceCoverage", label: "Backed by ≥ 2 sources", short: "Multi-source" }, { key: "connectivityCoverage", label: "Carriers / IXPs known", short: "Connectivity" }, { key: "primarySourceShare", label: "Share of fields from primary sources", short: "Primary sources" }, ]; /** Compact coverage strip for one entity (operator / market / country page "coverage row"). */ export function CoverageStrip({ row, className }: { row: CoverageRow; className?: string }) { return (
{COVERAGE_COLUMNS.map((c) => (
{c.short}
))}
{fmtInt(row.facilities)} facilities · {fmtInt(row.projects)} projects ({fmtInt(row.projectsWithLocation)} with a location). Coverage says how much is known, not how much exists.
); } /* ---------------------------------------------------------------- dense counter grid (pulse / momentum) */ export interface CounterItem { key: string; value: number | null | undefined; /** "new projects detected" — rendered as "17 new projects detected" */ label: string; format?: "int" | "mw"; href?: string; note?: ReactNode; } /** Dense grid of deterministic counters written as sentences; unknown values read "not published", never 0. */ export function CounterGrid({ items, suffix, className }: { items: CounterItem[]; suffix?: string; className?: string }) { return (
    {items.map((it) => { const known = it.value !== null && it.value !== undefined; const v = it.format === "mw" ? fmtMw(it.value) : fmtInt(it.value); const inner = ( <>
    {known ? (it.format === "mw" ? v.split(" ")[0] : v) : "—"} {known && it.format === "mw" && {v.split(" ")[1]}}
    {known ? it.label : {it.label} — not published} {suffix && known && {suffix}}
    {it.note &&
    {it.note}
    } ); return (
  • {it.href ? ( {inner} ) : (
    {inner}
    )}
  • ); })}
); } /** Small "Compare with…" / cross link pill. */ export function PillLink({ href, children, className }: { href: string; children: ReactNode; className?: string }) { return ( {children} ); }