'use client';
import { ArrowRight, ExternalLink, Scale, X } from 'lucide-react';
import Link from 'next/link';
import { t } from '@/i18n';
import { cn } from '@/lib/cn';
import { formatValue, grouped, ordinal } from '@/lib/format';
import { regionShort } from '@/lib/regions';
import { routes } from '@/lib/site';
import type { FormatSpec, Provenance } from '@/lib/types';
import type { PointCountry } from '@/lib/types-analytics';
import { seqVar } from '@/components/charts/palette';
import { Sparkline } from '@/components/charts/sparkline';
import type { SeriesPoint } from '@/components/charts/scales';
import { useProvenance } from '@/components/data/provenance-context';
import { stepFor } from './geo';
export interface CountryYearInfo {
country: PointCountry;
value: number | null;
year: number;
rankWorld: number | null;
nWorld: number;
rankRegion: number | null;
nRegion: number;
series: SeriesPoint[];
firstYear: number | null;
lastYear: number | null;
latestValue: number | null;
latestYear: number | null;
}
/** Legend for the pooled quantile classes (stable while scrubbing). */
export function ClassLegend({ breaks, min, max, spec, k, compact, className }: { breaks: number[]; min: number | null; max: number | null; spec: FormatSpec; k: number; compact?: boolean; className?: string }) {
const items = Array.from({ length: k }, (_, i) => {
const lo = i === 0 ? min : breaks[i - 1]!;
const hi = i === k - 1 ? max : breaks[i]!;
return { cls: i, lo, hi };
});
return (
{items.map((it) => (
-
{compact ? (it.cls === 0 ? formatValue(it.lo, spec) : it.cls === k - 1 ? formatValue(it.hi, spec) : '') : `${formatValue(it.lo, spec)} – ${formatValue(it.hi, spec)}`}
))}
-
{t('chart.legend.noData')}
);
}
/** Floating hover label on the map: country · value · year · world / regional rank. */
export function MapTooltip({ info, spec, x, y, sticky, onOpen }: { info: CountryYearInfo; spec: FormatSpec; x: number; y: number; sticky: boolean; onOpen: () => void }) {
return (
{info.country.flag ? {info.country.flag} : null}
{info.country.name}
{formatValue(info.value, spec)}
· {info.year}
{info.value != null && info.rankWorld != null ? (
{t('explorer.rank.world', { rank: ordinal(info.rankWorld), n: grouped(info.nWorld) })}
{info.rankRegion != null && info.country.region ? ` · ${t('explorer.rank.region', { rank: ordinal(info.rankRegion), region: regionShort(info.country.region) ?? info.country.region })}` : ''}
) : info.value == null ? (
{t('explorer.noValueYear', { year: info.year })}
) : null}
{sticky ? (
) : null}
);
}
/** Country drawer content (right panel on desktop, bottom sheet on phones). */
export function CountryPanel({ info, spec, indicatorSlug, indicatorName, provenance, onClose, onCompareHref, trajectoriesHref, closeClassName }: { info: CountryYearInfo; spec: FormatSpec; indicatorSlug: string; indicatorName: string; provenance: Provenance | null; onClose: () => void; onCompareHref: string; trajectoriesHref: string; closeClassName?: string }) {
const { open } = useProvenance();
const c = info.country;
const slug = c.slug ?? c.id.toLowerCase();
const dir = info.series.length >= 2 ? Math.sign((info.series[info.series.length - 1]!.value ?? 0) - (info.series[info.series.length - 2]!.value ?? 0)) : 0;
return (
{c.flag}
{c.name}
{regionShort(c.region) ?? c.region ?? ''}
{c.income ? ` · ${c.income}` : ''}
{indicatorName}
{info.value != null && info.rankWorld != null ? (
{t('explorer.rank.world', { rank: ordinal(info.rankWorld), n: grouped(info.nWorld) })}
{info.rankRegion != null && c.region ? · {t('explorer.rank.region', { rank: ordinal(info.rankRegion), region: regionShort(c.region) ?? c.region })} : null}
) : null}
{info.series.length >= 2 ? (
{t('explorer.drawer.history', { y0: info.firstYear ?? '', y1: info.lastYear ?? '' })}
0 ? 'up' : dir < 0 ? 'down' : 'flat'} ariaLabel={t('explorer.drawer.sparkAria', { name: c.name ?? c.id, indicator: indicatorName })} />
{info.firstYear}
{info.lastYear}
) : null}
{t('explorer.drawer.open')}
{t('explorer.drawer.compare')}
{t('explorer.drawer.trajectories')}
{t('explorer.drawer.series')} →
);
}
/** Best-effort topic for the country topic page link (falls back to the indicator page when unknown). */
function topicOf(slug: string): string {
return TOPIC_HINT[slug] ?? 'economy';
}
const TOPIC_HINT: Record = {
population: 'population', 'population-growth': 'population', 'median-age': 'population', 'fertility-rate': 'population', 'urban-population-share': 'population',
gdp: 'economy', 'gdp-per-capita': 'economy', 'gdp-per-capita-ppp': 'economy', 'gdp-growth': 'economy', inflation: 'economy',
'unemployment-rate': 'labor', 'life-expectancy': 'health', 'infant-mortality-rate': 'health', 'general-government-gross-debt-pct-gdp': 'government',
'co2-per-capita': 'climate', 'co2-emissions': 'climate', 'renewable-electricity-share': 'energy', 'energy-use-per-capita': 'energy', 'internet-users': 'digital',
};