/* tarot-database-assembly.js — Tarot card assembly helpers */ (function () { "use strict"; function createTarotDatabaseAssembly(dependencies) { const getTarotDbConfig = dependencies?.getTarotDbConfig; const canonicalCardName = dependencies?.canonicalCardName; const formatMonthDayLabel = dependencies?.formatMonthDayLabel; const applyMeaningText = dependencies?.applyMeaningText; const buildDecanMetadata = dependencies?.buildDecanMetadata; const listMonthNumbersBetween = dependencies?.listMonthNumbersBetween; const buildHebrewLetterLookup = dependencies?.buildHebrewLetterLookup; const createRelation = dependencies?.createRelation; const parseLegacyRelation = dependencies?.parseLegacyRelation; const buildHebrewLetterRelation = dependencies?.buildHebrewLetterRelation; const buildMajorDynamicRelations = dependencies?.buildMajorDynamicRelations; const buildMinorDecanRelations = dependencies?.buildMinorDecanRelations; const monthNameByNumber = dependencies?.monthNameByNumber && typeof dependencies.monthNameByNumber === "object" ? dependencies.monthNameByNumber : {}; const monthIdByNumber = dependencies?.monthIdByNumber && typeof dependencies.monthIdByNumber === "object" ? dependencies.monthIdByNumber : {}; const majorHebrewLetterIdByCard = dependencies?.majorHebrewLetterIdByCard && typeof dependencies.majorHebrewLetterIdByCard === "object" ? dependencies.majorHebrewLetterIdByCard : {}; if ( typeof getTarotDbConfig !== "function" || typeof canonicalCardName !== "function" || typeof formatMonthDayLabel !== "function" || typeof applyMeaningText !== "function" || typeof buildDecanMetadata !== "function" || typeof listMonthNumbersBetween !== "function" || typeof buildHebrewLetterLookup !== "function" || typeof createRelation !== "function" || typeof parseLegacyRelation !== "function" || typeof buildHebrewLetterRelation !== "function" || typeof buildMajorDynamicRelations !== "function" || typeof buildMinorDecanRelations !== "function" ) { throw new Error("Tarot database assembly dependencies are incomplete"); } function buildTokenDateRange(startToken, endToken) { const parseToken = (value) => { const match = String(value || "").trim().match(/^(\d{2})-(\d{2})$/); if (!match) { return null; } const month = Number(match[1]); const day = Number(match[2]); if (!Number.isFinite(month) || !Number.isFinite(day)) { return null; } return new Date(2025, month - 1, day); }; const start = parseToken(startToken); const endBase = parseToken(endToken); if (!start || !endBase) { return null; } const wraps = endBase.getTime() < start.getTime(); const end = wraps ? new Date(2026, endBase.getMonth(), endBase.getDate()) : endBase; return { start, end, startToken: startToken || null, endToken: endToken || null, label: `${formatMonthDayLabel(start)}–${formatMonthDayLabel(end)}` }; } function buildMajorCards(referenceData, magickDataset) { const tarotDb = getTarotDbConfig(referenceData); const dynamicRelations = buildMajorDynamicRelations(referenceData); const hebrewLookup = buildHebrewLetterLookup(magickDataset); return tarotDb.majorCards.map((card) => { const canonicalName = canonicalCardName(card.name); const dynamic = dynamicRelations.get(canonicalName) || []; const hebrewLetterId = majorHebrewLetterIdByCard[canonicalName] || null; const hebrewLetterRelation = buildHebrewLetterRelation(hebrewLetterId, hebrewLookup); const staticRelations = (card.relations || []) .map((relation) => parseLegacyRelation(relation)) .filter((relation) => relation.type !== "hebrewLetter" && relation.type !== "zodiac" && relation.type !== "planet"); return { arcana: "Major", name: card.name, number: card.number, suit: null, rank: null, hebrewLetterId, kabbalahPathNumber: Number.isFinite(Number(card?.number)) ? Number(card.number) + 11 : null, hebrewLetter: hebrewLetterRelation?.data || null, summary: card.summary, meanings: { upright: card.upright, reversed: card.reversed }, keywords: [...card.keywords], relations: [ ...staticRelations, ...(hebrewLetterRelation ? [hebrewLetterRelation] : []), ...dynamic ] }; }); } function buildNumberMinorCards(referenceData) { const tarotDb = getTarotDbConfig(referenceData); const decanRelations = buildMinorDecanRelations(referenceData); const cards = []; tarotDb.suits.forEach((suit) => { const suitKey = suit.toLowerCase(); const suitInfo = tarotDb.suitInfo[suitKey]; if (!suitInfo) { return; } tarotDb.numberRanks.forEach((rank) => { const rankKey = rank.toLowerCase(); const rankInfo = tarotDb.rankInfo[rankKey]; if (!rankInfo) { return; } const cardName = `${rank} of ${suit}`; const dynamicRelations = decanRelations.get(canonicalCardName(cardName)) || []; cards.push({ arcana: "Minor", name: cardName, number: null, suit, rank, summary: `${rank} energy expressed through ${suitInfo.domain}.`, meanings: { upright: `${rankInfo.upright} In ${suit}, this emphasizes ${suitInfo.domain}.`, reversed: `${rankInfo.reversed} In ${suit}, this may distort ${suitInfo.domain}.` }, keywords: [...rankInfo.keywords, ...suitInfo.keywords], relations: [ createRelation("element", suitInfo.element, `Element: ${suitInfo.element}`, { name: suitInfo.element }), createRelation("suitDomain", `${suitKey}-${rankKey}`, `Suit domain: ${suitInfo.domain}`, { suit, rank, domain: suitInfo.domain }), createRelation("numerology", rankInfo.number, `Numerology: ${rankInfo.number}`, { value: rankInfo.number }), ...dynamicRelations ] }); }); }); return cards; } function buildCourtMinorCards(referenceData) { const tarotDb = getTarotDbConfig(referenceData); const cards = []; const signs = Array.isArray(referenceData?.signs) ? referenceData.signs : []; const signById = Object.fromEntries(signs.map((sign) => [sign.id, sign])); const planets = referenceData?.planets || {}; const decanById = new Map(); const decansBySign = referenceData?.decansBySign || {}; Object.entries(decansBySign).forEach(([signId, decans]) => { const sign = signById[signId]; if (!sign || !Array.isArray(decans)) { return; } decans.forEach((decan) => { if (!decan?.id) { return; } const meta = buildDecanMetadata(decan, sign); if (meta) { decanById.set(decan.id, meta); } }); }); tarotDb.suits.forEach((suit) => { const suitKey = suit.toLowerCase(); const suitInfo = tarotDb.suitInfo[suitKey]; if (!suitInfo) { return; } tarotDb.courtRanks.forEach((rank) => { const rankKey = rank.toLowerCase(); const courtInfo = tarotDb.courtInfo[rankKey]; if (!courtInfo) { return; } const cardName = `${rank} of ${suit}`; const windowDecanIds = tarotDb.courtDecanWindows[cardName] || []; const windowDecans = windowDecanIds .map((decanId) => decanById.get(decanId) || null) .filter(Boolean); const explicitWindowRange = buildTokenDateRange( tarotDb.courtDateRanges?.[cardName]?.start, tarotDb.courtDateRanges?.[cardName]?.end ); const dynamicRelations = []; const monthKeys = new Set(); windowDecans.forEach((meta) => { dynamicRelations.push( createRelation( "decan", `${meta.signId}-${meta.index}-${rankKey}-${suitKey}`, `Decan ${meta.index}: ${meta.signSymbol} ${meta.signName} (${meta.startDegree}°–${meta.endDegree}°)${meta.dateRange ? ` · ${meta.dateRange.label}` : ""}`.trim(), { signId: meta.signId, signName: meta.signName, signSymbol: meta.signSymbol, index: meta.index, startDegree: meta.startDegree, endDegree: meta.endDegree, dateStart: meta.dateRange?.startToken || null, dateEnd: meta.dateRange?.endToken || null, dateRange: meta.dateRange?.label || null } ) ); const ruler = planets?.[meta?.decan?.rulerPlanetId] || null; if (ruler) { dynamicRelations.push( createRelation( "decanRuler", `${meta.signId}-${meta.index}-${ruler.id || meta.decan?.rulerPlanetId || rankKey}-${rankKey}-${suitKey}`, `Decan ruler: ${ruler.symbol || ""} ${ruler.name || meta.decan?.rulerPlanetId || ""}`.trim(), { signId: meta.signId, decanIndex: meta.index, planetId: ruler.id || meta.decan?.rulerPlanetId || null, symbol: ruler.symbol || "", name: ruler.name || meta.decan?.rulerPlanetId || "" } ) ); } const dateRange = meta.dateRange; if (dateRange?.start && dateRange?.end) { const monthNumbers = listMonthNumbersBetween(dateRange.start, dateRange.end); monthNumbers.forEach((monthNo) => { const monthId = monthIdByNumber[monthNo]; const monthName = monthNameByNumber[monthNo] || `Month ${monthNo}`; const monthKey = `${monthId}:${meta.signId}:${meta.index}`; if (!monthId || monthKeys.has(monthKey)) { return; } monthKeys.add(monthKey); dynamicRelations.push( createRelation( "calendarMonth", `${monthId}-${meta.signId}-${meta.index}-${rankKey}-${suitKey}`, `Calendar month: ${monthName} (${meta.signName} decan ${meta.index})`, { monthId, name: monthName, monthOrder: monthNo, signId: meta.signId, signName: meta.signName, decanIndex: meta.index, dateRange: meta.dateRange?.label || null } ) ); }); } }); if (windowDecans.length) { const firstRange = windowDecans[0].dateRange; const lastRange = windowDecans[windowDecans.length - 1].dateRange; const fallbackWindowRange = firstRange && lastRange ? { start: firstRange.start, end: lastRange.end, startToken: firstRange.startToken, endToken: lastRange.endToken, label: `${formatMonthDayLabel(firstRange.start)}–${formatMonthDayLabel(lastRange.end)}` } : null; const windowRange = explicitWindowRange || fallbackWindowRange; const windowLabel = windowRange?.label || "--"; dynamicRelations.unshift( createRelation( "courtDateWindow", `${rankKey}-${suitKey}`, `Court date window: ${windowLabel}`, { dateStart: windowRange?.startToken || null, dateEnd: windowRange?.endToken || null, dateRange: windowLabel, decanIds: windowDecanIds } ) ); } cards.push({ arcana: "Minor", name: cardName, number: null, suit, rank, summary: `${rank} as ${courtInfo.role} within ${suitInfo.domain}.`, meanings: { upright: `${courtInfo.upright} In ${suit}, this guides ${suitInfo.domain}.`, reversed: `${courtInfo.reversed} In ${suit}, this complicates ${suitInfo.domain}.` }, keywords: [...courtInfo.keywords, ...suitInfo.keywords], relations: [ createRelation("element", suitInfo.element, `Element: ${suitInfo.element}`, { name: suitInfo.element }), createRelation( "elementalFace", `${rankKey}-${suitKey}`, `${courtInfo.elementalFace} ${suitInfo.element}`, { rank, suit, elementalFace: courtInfo.elementalFace, element: suitInfo.element } ), createRelation("courtRole", rankKey, `Court role: ${courtInfo.role}`, { rank, role: courtInfo.role }), ...dynamicRelations ] }); }); }); return cards; } function buildTarotDatabase(referenceData, magickDataset = null) { const cards = [ ...buildMajorCards(referenceData, magickDataset), ...buildNumberMinorCards(referenceData), ...buildCourtMinorCards(referenceData) ]; return applyMeaningText(cards, referenceData); } return { buildCourtMinorCards, buildMajorCards, buildNumberMinorCards, buildTarotDatabase }; } window.TarotDatabaseAssembly = { createTarotDatabaseAssembly }; })();