import { ArrowUpRight, ChevronDown, MapPin } from 'lucide-react'; import Link from 'next/link'; import type { ReactNode } from 'react'; import { Chip } from '@/components/ui/badges'; import { Empty, Note } from '@/components/ui/section'; import { cn } from '@/lib/cn'; import { countryName } from '@/lib/countries'; import { fmtDate, fmtDateTime, fmtInt, fmtMoney, plural } from '@/lib/format'; import { descriptionOf, groupRelationships, logoCandidates, relationshipName, sourceFor, sourceLabel } from '@/lib/profile'; import { routes } from '@/lib/site'; import type { CompanyCard, CompanyDetail, CompanyFact, MoneyFact, Relationship } from '@/lib/types'; import { CompanyLogo } from './company-logo'; /* ------------------------------------------------------------------------------------------------------- source chip */ /** Small provenance chip; `title` carries the retrieval time so every fact is auditable on hover / long-press. */ export function SourceChip({ source, url, retrievedAt, className }: { source: string | null | undefined; url?: string | null; retrievedAt?: string | null; className?: string }) { if (!source) return null; const label = sourceLabel(source); const title = retrievedAt ? `${label} · retrieved ${fmtDateTime(retrievedAt)}` : label; const llm = source.toLowerCase() === 'llm'; const chip = ( {label} ); if (!url) return chip; return ( {chip} ); } /* ------------------------------------------------------------------------------------------------ description credit */ /** Attribution line under the description — the wording depends on where the paragraph came from. */ export function DescriptionAttribution({ c, className }: { c: CompanyCard; className?: string }) { const d = descriptionOf(c); if (!d || !d.source) return null; const ext = { target: '_blank', rel: 'noopener nofollow noreferrer' } as const; if (d.source === 'wikipedia') { const license = d.license ?? 'CC BY-SA 4.0'; return (

Source:{' '} {d.url ? ( Wikipedia ) : ( 'Wikipedia' )}{' '} · {license}

); } if (d.source === 'wikidata') { return (

Source:{' '} {d.url ? ( Wikidata ) : ( 'Wikidata' )} {d.license ? ` · ${d.license}` : ' · CC0'}

); } if (d.source === 'homepage') { return (

{d.url ? ( From the company’s website ) : ( 'From the company’s website' )}

); } return (

Generated from the company’s public pages Model-written summary of observed first-party text — not a statement by the company.

); } /* ----------------------------------------------------------------------------------------------------- key facts */ type FactRow = { key: string; label: ReactNode; value: ReactNode; source: string | null; url: string | null; retrievedAt: string | null }; const MONEY_LABEL: Record<'revenue' | 'net_income' | 'total_assets', string> = { revenue: 'Revenue', net_income: 'Net income', total_assets: 'Total assets' }; function money(m: MoneyFact | null): ReactNode { if (!m || typeof m.value !== 'number') return null; return ( {fmtMoney(m.value, m.currency)} · FY{m.year} ); } /** Deterministic public registry links for identifiers (no lookup, no inference). */ const EDGAR = (cik: string) => `https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=${encodeURIComponent(cik)}`; const GLEIF = (lei: string) => `https://search.gleif.org/#/record/${encodeURIComponent(lei)}`; /** Builds the sourced fact rows: structured profile fields first (typed formatting), then any extra `facts[]` entries. */ export function buildFactRows(c: CompanyDetail): FactRow[] { const p = c.profile ?? null; const rows: FactRow[] = []; const covered = new Set(); const push = (key: string, label: ReactNode, value: ReactNode, fields: string[] = [key]) => { if (value === null || value === undefined || value === '' || value === false) return; const s = sourceFor(p, ...fields); const f = (c.facts ?? []).find((x) => x.key === key || fields.includes(x.key)); rows.push({ key, label, value, source: s?.source ?? f?.source ?? null, url: s?.url ?? f?.url ?? null, retrievedAt: s?.retrieved_at ?? f?.retrieved_at ?? null }); covered.add(key); fields.forEach((x) => covered.add(x)); }; if (p) { if (p.founded_year) push('founded_year', 'Founded', {p.founded_year}, ['founded_year', 'founded', 'inception']); const hqParts = [p.hq?.city, p.hq?.region, p.hq?.country ? countryName(p.hq.country) : null].filter(Boolean) as string[]; if (hqParts.length) push( 'hq', 'Headquarters', {hqParts.join(', ')} map , ['hq', 'headquarters', 'hq_city', 'hq_country'], ); if (typeof p.employees === 'number') push( 'employees', 'Employees', {fmtInt(p.employees)} {p.employees_year ? · {p.employees_year} : null} , ['employees', 'employees_year'], ); for (const k of ['revenue', 'net_income', 'total_assets'] as const) push(k, MONEY_LABEL[k], money(p[k])); if (p.legal_form) push('legal_form', 'Legal form', p.legal_form); if (p.public_company && (p.ticker || c.ticker)) push( 'listing', 'Listing', {p.ticker ?? c.ticker} {(p.exchange ?? c.exchange) && · {p.exchange ?? c.exchange}} , ['ticker', 'exchange', 'listing'], ); if (p.isin) push('isin', 'ISIN', {p.isin}); if (p.lei) push( 'lei', 'LEI', {p.lei} , ); if (p.sec_cik) push( 'sec_cik', 'SEC CIK', {p.sec_cik} , ['sec_cik', 'cik'], ); if (p.industry_labels?.length) push('industry_labels', 'Industry (Wikidata)', p.industry_labels.join(', '), ['industry_labels', 'industries']); if (p.phone) push('phone', 'Phone', {p.phone}); if (p.wikipedia_url || p.wikidata_url) push( 'references', 'References', {p.wikipedia_url && ( Wikipedia )} {p.wikidata_url && ( Wikidata )} , ['wikipedia_url', 'wikidata_url'], ); } // extra flattened facts not already rendered from the structured profile for (const f of c.facts ?? []) { if (covered.has(f.key) || !f.value) continue; rows.push({ key: `fact:${f.key}`, label: f.label || f.key, value: f.value, source: f.source, url: f.url, retrievedAt: f.retrieved_at }); covered.add(f.key); } return rows; } /** Overview → first block. Hides null facts; each row carries a source chip whose tooltip is the retrieval time. */ export function KeyFactsPanel({ c, className }: { c: CompanyDetail; className?: string }) { const rows = buildFactRows(c); const enriched = c.profile?.enriched_at ?? null; return (

Key facts

{enriched && enriched {fmtDate(enriched)}}
{rows.length === 0 ? ( Facts appear here once Wikidata, Wikipedia or registry lookups have been reconciled for this company. ) : (
{rows.map((r) => (
{r.label}
{r.value}
))}
)} {rows.length > 0 && Facts are quoted from their source as retrieved; figures are not converted or adjusted, and a missing fact is simply not shown.}
); } /* ------------------------------------------------------------------------------------------- corporate structure */ function validity(r: Relationship): string | null { if (r.valid_from && r.valid_to) return `${fmtDate(r.valid_from)} – ${fmtDate(r.valid_to)}`; if (r.valid_from) return `since ${fmtDate(r.valid_from)}`; if (r.valid_to) return `until ${fmtDate(r.valid_to)}`; return null; } function RelationshipRow({ r, showKind = false }: { r: Relationship; showKind?: boolean }) { const name = relationshipName(r); const v = validity(r); const ended = !!r.valid_to; return (
  • {r.company ? ( {name} ) : ( {name} )} {showKind && {(r.kind ?? '').toLowerCase().replace(/_/g, ' ')}} {ended && former} {v && {v}} {typeof r.confidence === 'number' && ( {Math.round(r.confidence * 100)} % )}
  • ); } /** Parent / owners / subsidiaries / holdings / acquisitions, grouped; collapsible when there are more than 8 rows. */ export function CorporateStructurePanel({ relationships, className, collapseAfter = 8 }: { relationships: Relationship[]; className?: string; collapseAfter?: number }) { const rels = relationships ?? []; const groups = groupRelationships(rels); const total = rels.length; const body = (
    {groups.map(({ group, rows }) => (

    {group.label} ({rows.length})

      {rows.map((r, i) => ( ))}
    ))} Structure as recorded by the cited sources (Wikidata statements, registries, first-party pages). “Former” marks a relationship with a recorded end date; the platform does not infer ownership changes.
    ); return (

    Corporate structure

    {total === 0 ? ( ) : total > collapseAfter ? (
    {fmtInt(total)} recorded {plural(total, 'relationship')} · {groups.map((g) => `${g.rows.length} ${g.group.label.toLowerCase()}`).join(' · ')}
    {body}
    ) : ( body )}
    ); } /* ---------------------------------------------------------------------------------------------- products (Wikidata) */ /** Fallback for the Products tab when no catalogue surface has been reconciled: products/services as stated on Wikidata. */ export function WikidataProducts({ products, source, className }: { products: string[]; source?: { url: string | null; retrieved_at: string } | null; className?: string }) { if (!products?.length) return null; return (

    Products & services listed on Wikidata ({products.length})

    These names come from Wikidata “product or material produced” statements — not from a monitored catalogue page, so they carry no first-seen / last-seen dates.
    ); } export type { CompanyFact };