/** * Book + chapter derivation for transcriptions. * * Transcriptions carry a free-text scripture `reference` (e.g. "SB 1.2.3", * "BG Introduction", "CC Madhya-līlā 20.101") rather than structured * book/chapter columns. This module is the single source of truth for turning * those strings into a book identity and a chapter grouping, reused by the * books grid, the listing page, and the book browser. * * The book registry mirrors `TranscriptionBooks` in the Go BFF * (internal/repository/transcription_repo.go). */ export type BookCode = "SB" | "BG" | "CC" | "BS" | "NOD" | "ISO"; export const BOOKS: Record = { SB: { title: "Śrīmad-Bhāgavatam", order: 1 }, BG: { title: "Bhagavad-gītā", order: 2 }, CC: { title: "Caitanya-caritāmṛta", order: 3 }, BS: { title: "Brahma-saṁhitā", order: 4 }, NOD: { title: "The Nectar of Devotion", order: 5 }, ISO: { title: "Śrī Īśopaniṣad", order: 6 }, }; /** Display title for a (possibly unknown) book code; "" if unknown. */ export function bookTitle(code: string): string { const b = BOOKS[code.toUpperCase() as BookCode]; return b ? b.title : ""; } /** * Extract the (starting) verse number from a reference, for ordering lectures * within a chapter. The verse is the last dotted numeric component, and for a * range we take the start: "BG 2.13" → 13, "SB 1.2.3" → 3, "SB 3.27.11–16" → * 11, "CC Madhya-līlā 20.101" → 101, "ISO Mantra 4" → 4. References with no * number (e.g. bare "NOD") sort last (stable → keep their incoming order). */ export function parseVerse(reference: string | null): number { const m = (reference ?? "").trim().match(/(\d+(?:\.\d+)*)/); if (!m) return Number.MAX_SAFE_INTEGER; const parts = m[1].split("."); return Number(parts[parts.length - 1]); } /** A section is a canto (SB) or līlā (CC); null for books without that level. */ export type ChapterSection = { key: string; label: string; order: number; }; /** A chapter grouping for the accordion list. */ export type ChapterGroup = { key: string; label: string; sort: [number, number]; /** Canto/līlā this chapter belongs to (SB, CC); null for flat books. */ section: ChapterSection | null; }; // Caitanya-caritāmṛta līlā ordering (Ādi → Madhya → Antya). const LILA_ORDER: Record = { ADI: { label: "Ādi-līlā", order: 1 }, MADHYA: { label: "Madhya-līlā", order: 2 }, ANTYA: { label: "Antya-līlā", order: 3 }, }; const OTHER: ChapterGroup = { key: "other", label: "Other", sort: [9999, 0], section: null, }; /** * Parse a transcription reference into its chapter group, given the book code. * Tolerates the corpus's noise (en-dash vs hyphen, "Madhya" vs "Madhya-līlā", * a stray "SB 7", etc.). Unparseable / missing references fall into "Other". */ export function parseChapter(code: string, reference: string | null): ChapterGroup { const ref = (reference ?? "").trim(); if (!ref) return OTHER; switch (code.toUpperCase() as BookCode) { case "BG": { if (/^BG\s+Introduction/i.test(ref)) { return { key: "bg-intro", label: "Introduction", sort: [0, 0], section: null, }; } const m = ref.match(/^BG\s+(\d+)/); if (m) { const n = Number(m[1]); return { key: `bg-${n}`, label: `Chapter ${n}`, sort: [n, 0], section: null, }; } return OTHER; } case "SB": { const m = ref.match(/^SB\s+(\d+)\.(\d+)/); if (m) { const c = Number(m[1]); const ch = Number(m[2]); return { key: `sb-${c}-${ch}`, label: `Chapter ${ch}`, sort: [c, ch], section: { key: `sb-${c}`, label: `Canto ${c}`, order: c }, }; } const cantoOnly = ref.match(/^SB\s+(\d+)\b/); if (cantoOnly) { const c = Number(cantoOnly[1]); return { key: `sb-${c}-0`, label: "Other", sort: [c, 0], section: { key: `sb-${c}`, label: `Canto ${c}`, order: c }, }; } return OTHER; } case "CC": { const m = ref.match(/^CC\s+(Ādi|Adi|Madhya|Antya)(?:-līlā)?\s+(\d+)/i); if (m) { const lila = LILA_ORDER[m[1].toUpperCase().replace("Ā", "A")]; const ch = Number(m[2]); if (lila) { return { key: `cc-${lila.order}-${ch}`, label: `Chapter ${ch}`, sort: [lila.order, ch], section: { key: `cc-${lila.order}`, label: lila.label, order: lila.order, }, }; } } return OTHER; } case "BS": { const m = ref.match(/^BS\s+(\d+)/); if (m) { const n = Number(m[1]); return { key: `bs-${n}`, label: `Chapter ${n}`, sort: [n, 0], section: null, }; } return OTHER; } case "ISO": { const m = ref.match(/^ISO\s+Mantra\s+(\d+)/i); if (m) { const n = Number(m[1]); return { key: `iso-${n}`, label: `Mantra ${n}`, sort: [n, 0], section: null, }; } return { key: "iso", label: "Śrī Īśopaniṣad", sort: [0, 0], section: null }; } case "NOD": return { key: "nod", label: "The Nectar of Devotion", sort: [0, 0], section: null, }; default: return OTHER; } }