import type { CollectionEntry } from "astro:content"; export type GlossaryEntry = CollectionEntry<"glossaire">; export const GLOSSARY_COLLATOR = new Intl.Collator("fr", { sensitivity: "base", numeric: true, }); export const FAMILY_LABELS: Record = { "concept-fondamental": "Concept fondamental", scene: "Scène", dynamique: "Dynamique", pathologie: "Pathologie", topologie: "Topologie", "meta-regime": "Méta-régime", paradigme: "Paradigme", doctrine: "Doctrine", verbe: "Verbe", "dispositif-ia": "Dispositif IA", "tension-irreductible": "Tension irréductible", figure: "Figure", qualification: "Qualification", epistemologie: "Épistémologie", }; export const KIND_LABELS: Record = { concept: "Concept", diagnostic: "Diagnostic", topologie: "Topologie", verbe: "Verbe", paradigme: "Paradigme", doctrine: "Doctrine", dispositif: "Dispositif", figure: "Figure", qualification: "Qualification", epistemologie: "Épistémologie", }; export const DOMAIN_LABELS: Record = { transversal: "Transversal", theorie: "Théorie", "cas-ia": "Cas IA", }; export const LEVEL_LABELS: Record = { fondamental: "Fondamental", intermediaire: "Intermédiaire", avance: "Avancé", }; export const FONDAMENTAUX_WANTED = [ "archicratie", "arcalite", "cratialite", "archicration", "co-viabilite", "tension", ] as const; export const FAMILY_SECTION_TITLES: Record = { "concept-fondamental": "Noyau archicratique", scene: "Scènes archicratiques", dynamique: "Dynamiques archicratiques", pathologie: "Pathologies archicratiques", topologie: "Topologies voisines", "meta-regime": "Méta-régimes archicratiques", paradigme: "Paradigmes voisins", doctrine: "Doctrines fondatrices", verbe: "Verbes de la scène", "dispositif-ia": "Dispositifs IA", "tension-irreductible": "Tensions irréductibles", figure: "Figures archicratiques", qualification: "Qualifications archicratiques", epistemologie: "Outillage épistémologique", }; export function normalizeGlossarySlug(value: unknown): string { return String(value ?? "") .trim() .replace(/^\/+|\/+$/g, "") .replace(/\.(md|mdx)$/i, "") .toLowerCase(); } export function slugOfGlossaryEntry( entry: Pick | null | undefined, ): string { return normalizeGlossarySlug(entry?.id ?? ""); } export function hrefOfGlossaryEntry( entry: Pick | null | undefined, ): string { const slug = slugOfGlossaryEntry(entry); return slug ? `/glossaire/${slug}/` : "/glossaire/"; } export function buildGlossaryBySlug( entries: GlossaryEntry[] = [], ): Map { return new Map( entries.map((entry) => [slugOfGlossaryEntry(entry), entry]), ); } export function sortGlossaryEntries( entries: GlossaryEntry[] = [], ): GlossaryEntry[] { return [...entries].sort((a, b) => GLOSSARY_COLLATOR.compare(a.data.term, b.data.term), ); } export function uniqueGlossaryEntries( entries: GlossaryEntry[] = [], ): GlossaryEntry[] { const seen = new Set(); const out: GlossaryEntry[] = []; for (const entry of entries) { const slug = slugOfGlossaryEntry(entry); if (!slug || seen.has(slug)) continue; seen.add(slug); out.push(entry); } return out; } export function resolveGlossaryEntriesInSourceOrder( slugs: string[] = [], allEntries: GlossaryEntry[] = [], ): GlossaryEntry[] { const bySlug = buildGlossaryBySlug(allEntries); const seen = new Set(); const resolved: GlossaryEntry[] = []; for (const rawSlug of slugs) { const slug = normalizeGlossarySlug(rawSlug); if (!slug || seen.has(slug)) continue; const entry = bySlug.get(slug); if (!entry) continue; seen.add(slug); resolved.push(entry); } return resolved; } export function resolveGlossaryEntries( slugs: string[] = [], allEntries: GlossaryEntry[] = [], ): GlossaryEntry[] { return sortGlossaryEntries( resolveGlossaryEntriesInSourceOrder(slugs, allEntries), ); } export function rawFamilyOf( entry: GlossaryEntry | null | undefined, ): string { return String(entry?.data?.family ?? ""); } export function kindOf( entry: GlossaryEntry | null | undefined, ): string { return String(entry?.data?.kind ?? ""); } export function familyOf( entry: GlossaryEntry | null | undefined, ): string { const explicit = rawFamilyOf(entry); if (explicit) return explicit; const slug = slugOfGlossaryEntry(entry); const kind = kindOf(entry); if (kind === "paradigme") return "paradigme"; if (kind === "doctrine") return "doctrine"; if (kind === "verbe") return "verbe"; if (slug === "scene-depreuve") return "scene"; if (slug === "autarchicratie") return "pathologie"; if (slug === "obliteration-archicratique") return "dynamique"; if (FONDAMENTAUX_WANTED.includes(slug as (typeof FONDAMENTAUX_WANTED)[number])) { return "concept-fondamental"; } if (slug === "archicrations-differentielles-et-formes-hybrides") { return "topologie"; } if (kind === "topologie" && slug.startsWith("archicrations-")) { return "meta-regime"; } return ""; } export function getDisplayFamily( entry: GlossaryEntry | null | undefined, ): string { const familyKey = rawFamilyOf(entry) || familyOf(entry); return FAMILY_LABELS[familyKey] ?? KIND_LABELS[kindOf(entry)] ?? "Fiche"; } export function getDisplayDomain( entry: GlossaryEntry | null | undefined, ): string { const key = String(entry?.data?.domain ?? ""); return key ? (DOMAIN_LABELS[key] ?? key) : ""; } export function getDisplayLevel( entry: GlossaryEntry | null | undefined, ): string { const key = String(entry?.data?.level ?? ""); return key ? (LEVEL_LABELS[key] ?? key) : ""; } export function getFondamentaux( entries: GlossaryEntry[] = [], ): GlossaryEntry[] { const bySlug = buildGlossaryBySlug(entries); return sortGlossaryEntries( FONDAMENTAUX_WANTED .map((slug) => bySlug.get(slug)) .filter(Boolean) as GlossaryEntry[], ); } export function getGlossaryEntriesByFamily( entries: GlossaryEntry[] = [], familyKey: string, ): GlossaryEntry[] { return sortGlossaryEntries( entries.filter((entry) => familyOf(entry) === familyKey), ); } export function countGlossaryEntriesByKind( entries: GlossaryEntry[] = [], kindKey: string, ): number { return entries.filter((entry) => kindOf(entry) === kindKey).length; } export function countGlossaryEntriesByFamily( entries: GlossaryEntry[] = [], familyKey: string, ): number { return entries.filter((entry) => familyOf(entry) === familyKey).length; } export function getEntriesOfSameFamily( entry: GlossaryEntry, allEntries: GlossaryEntry[] = [], ): GlossaryEntry[] { const familyKey = familyOf(entry); if (!familyKey) return []; if (familyKey === "concept-fondamental") { return getFondamentaux(allEntries); } return getGlossaryEntriesByFamily(allEntries, familyKey); } export function getSameFamilyTitle( entry: GlossaryEntry, ): string { return FAMILY_SECTION_TITLES[familyOf(entry)] ?? "Même famille"; } export type GlossaryRelationBlock = { title: string; items: GlossaryEntry[]; className: string; }; export function getRelationBlocks( entry: GlossaryEntry, allEntries: GlossaryEntry[] = [], ): GlossaryRelationBlock[] { const currentSlug = slugOfGlossaryEntry(entry); const relatedEntries = resolveGlossaryEntriesInSourceOrder( entry.data.related ?? [], allEntries, ).filter((item) => slugOfGlossaryEntry(item) !== currentSlug); const opposedEntries = resolveGlossaryEntriesInSourceOrder( entry.data.opposedTo ?? [], allEntries, ).filter((item) => slugOfGlossaryEntry(item) !== currentSlug); const seeAlsoEntries = resolveGlossaryEntriesInSourceOrder( entry.data.seeAlso ?? [], allEntries, ).filter((item) => slugOfGlossaryEntry(item) !== currentSlug); return [ { title: "Concepts liés", items: relatedEntries, className: "is-related", }, { title: "En tension avec", items: opposedEntries, className: "is-opposed", }, { title: "Voir aussi", items: seeAlsoEntries, className: "is-see-also", }, ].filter((block) => block.items.length > 0); } export function getRelationSections( entry: GlossaryEntry, allEntries: GlossaryEntry[] = [], ): Array<{ title: string; items: GlossaryEntry[] }> { return getRelationBlocks(entry, allEntries).map(({ title, items }) => ({ title, items, })); } function isTheoryEntry(entry: GlossaryEntry): boolean { const familyKey = familyOf(entry); const kindKey = kindOf(entry); return ( familyKey === "paradigme" || familyKey === "doctrine" || kindKey === "paradigme" || kindKey === "doctrine" ); } const PREFERRED_PARADIGME_SLUGS = [ "gouvernementalite", "gouvernementalite-algorithmique", "cybernetique", "biopolitique", "domination-legale-rationnelle", "democratie-deliberative", "gouvernance-des-communs", "agencement-machinique", "pharmacologie-technique", "preemption-algorithmique", "dissensus-politique", "lieu-vide-du-pouvoir", "habitus-et-violence-symbolique", "theorie-de-la-resonance", "conatus-et-multitude", "configuration-et-interdependance", "technodiversite-et-cosmotechnie", "grammatisation-et-proletarisation-cognitive", ] as const; const PREFERRED_DOCTRINE_SLUGS = [ "contractualisme-hobbesien", "droit-naturel-et-propriete", "volonte-generale", "decisionnisme-souverain", ] as const; export function getContextualTheory( entry: GlossaryEntry, allEntries: GlossaryEntry[] = [], ): GlossaryEntry[] { const currentSlug = slugOfGlossaryEntry(entry); const bySlug = buildGlossaryBySlug(allEntries); const fromRelations = uniqueGlossaryEntries([ ...resolveGlossaryEntries(entry.data.related ?? [], allEntries), ...resolveGlossaryEntries(entry.data.seeAlso ?? [], allEntries), ...resolveGlossaryEntries(entry.data.opposedTo ?? [], allEntries), ]) .filter((item) => slugOfGlossaryEntry(item) !== currentSlug) .filter((item) => isTheoryEntry(item)); if (fromRelations.length > 0) { return sortGlossaryEntries(fromRelations).slice(0, 6); } if (familyOf(entry) === "paradigme") { return uniqueGlossaryEntries( PREFERRED_PARADIGME_SLUGS .filter((slug) => slug !== currentSlug) .map((slug) => bySlug.get(slug)) .filter(Boolean) as GlossaryEntry[], ).slice(0, 8); } if (familyOf(entry) === "doctrine") { return uniqueGlossaryEntries( PREFERRED_DOCTRINE_SLUGS .filter((slug) => slug !== currentSlug) .map((slug) => bySlug.get(slug)) .filter(Boolean) as GlossaryEntry[], ).slice(0, 6); } return []; }